feat: SAPIENS Mobile App - Initial commit
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>
This commit is contained in:
128
sapiense-ai-app/lib/api.ts
Normal file
128
sapiense-ai-app/lib/api.ts
Normal file
@ -0,0 +1,128 @@
|
||||
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',
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user