React Native mobile application for SAPIENS news platform. Consolidated all previous history into single commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
import { API_BASE_URL, API_TIMEOUT } from './config';
|
|
|
|
// Generic API request helper
|
|
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT);
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
},
|
|
signal: controller.signal,
|
|
...options,
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`API Error (${response.status}): ${errorText || response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
} catch (error) {
|
|
clearTimeout(timeoutId);
|
|
if (error instanceof Error) {
|
|
if (error.name === 'AbortError') {
|
|
throw new Error('Request timeout');
|
|
}
|
|
throw error;
|
|
}
|
|
throw new Error('Unknown error occurred');
|
|
}
|
|
}
|
|
|
|
// Outlets API
|
|
export const outletsApi = {
|
|
getAll: async (category?: string, language = 'ko') => {
|
|
const params = new URLSearchParams();
|
|
if (category) params.append('category', category);
|
|
if (language) params.append('language', language);
|
|
const response = await apiRequest<any>(`/api/v1/outlets?${params}`);
|
|
|
|
// API returns {"people": [...]} when category is specified
|
|
// Extract the array from the categorized response
|
|
if (category && response[category]) {
|
|
return response[category];
|
|
}
|
|
return response;
|
|
},
|
|
|
|
getById: (id: string, language = 'ko') => {
|
|
const params = new URLSearchParams({ language });
|
|
return apiRequest(`/api/v1/outlets/${id}?${params}`);
|
|
},
|
|
};
|
|
|
|
// Articles API
|
|
export const articlesApi = {
|
|
getAll: (language = 'ko') => {
|
|
const params = new URLSearchParams({ page_size: '50' });
|
|
return apiRequest(`/api/v1/${language}/articles?${params}`);
|
|
},
|
|
|
|
getByOutlet: (outletId: string, language = 'ko') => {
|
|
const params = new URLSearchParams({ page_size: '50' });
|
|
return apiRequest(`/api/v1/${language}/outlets/${encodeURIComponent(outletId)}/articles?${params}`);
|
|
},
|
|
|
|
getFeatured: (limit = 10, language = 'ko') => {
|
|
const params = new URLSearchParams({ page_size: limit.toString() });
|
|
return apiRequest(`/api/v1/${language}/articles?${params}`);
|
|
},
|
|
|
|
getById: (id: string, language = 'ko', useNewsId = false) => {
|
|
return apiRequest(`/api/v1/${language}/articles/${id}`);
|
|
},
|
|
};
|
|
|
|
// Search API
|
|
export const searchApi = {
|
|
search: async (
|
|
query: string,
|
|
type: 'all' | 'articles' | 'outlets' = 'all',
|
|
language = 'en',
|
|
outletId?: string
|
|
) => {
|
|
const params = new URLSearchParams({
|
|
q: query,
|
|
type,
|
|
language,
|
|
});
|
|
|
|
if (outletId) {
|
|
params.append('outletId', outletId);
|
|
}
|
|
|
|
return apiRequest(`/api/v1/search?${params}`);
|
|
},
|
|
|
|
articles: (query: string, language = 'en') => {
|
|
const params = new URLSearchParams({ q: query, language, limit: '20' });
|
|
return apiRequest(`/api/v1/search/articles?${params}`);
|
|
},
|
|
};
|
|
|
|
// Feed API for YouTube-style interface
|
|
export const feedApi = {
|
|
getFeed: (params: {
|
|
cursor?: string;
|
|
limit?: number;
|
|
filter?: 'all' | 'people' | 'topics' | 'companies';
|
|
}) => {
|
|
const searchParams = new URLSearchParams();
|
|
if (params.cursor) searchParams.set('cursor', params.cursor);
|
|
if (params.limit) searchParams.set('limit', params.limit.toString());
|
|
if (params.filter) searchParams.set('filter', params.filter);
|
|
|
|
return apiRequest(`/api/v1/feed?${searchParams.toString()}`);
|
|
},
|
|
|
|
incrementView: (articleId: string) =>
|
|
apiRequest(`/api/v1/articles/${articleId}/view`, {
|
|
method: 'POST',
|
|
}),
|
|
};
|