feat: add frontend + backend + database to retrieve and compute news from Yahoo
This commit is contained in:
52
frontend/src/api/admin.ts
Normal file
52
frontend/src/api/admin.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { api } from './client'
|
||||
|
||||
export interface AIProvider {
|
||||
id: string; name: string; model: string; endpoint: string
|
||||
is_active: boolean; has_key: boolean
|
||||
}
|
||||
export interface Source { id: string; name: string; type: string; enabled: boolean }
|
||||
export interface ScrapeJob {
|
||||
id: string; source_id: string; source_name: string; status: string
|
||||
started_at: { Time: string; Valid: boolean } | null
|
||||
finished_at: { Time: string; Valid: boolean } | null
|
||||
articles_found: number; error_msg: string; created_at: string
|
||||
}
|
||||
export interface Setting { key: string; value: string }
|
||||
export interface AdminUser { id: string; email: string; role: string; created_at: string }
|
||||
export interface Credential { source_id: string; source_name: string; username: string; has_password: boolean }
|
||||
|
||||
export const adminApi = {
|
||||
// Credentials
|
||||
getCredentials: () => api.get<Credential[]>('/admin/credentials'),
|
||||
updateCredentials: (data: { source_id: string; username: string; password?: string }) =>
|
||||
api.put<void>('/admin/credentials', data),
|
||||
|
||||
// AI Providers
|
||||
listProviders: () => api.get<AIProvider[]>('/admin/ai-providers'),
|
||||
createProvider: (data: { name: string; api_key?: string; model?: string; endpoint?: string }) =>
|
||||
api.post<AIProvider>('/admin/ai-providers', data),
|
||||
updateProvider: (id: string, data: { name: string; api_key?: string; model?: string; endpoint?: string }) =>
|
||||
api.put<void>(`/admin/ai-providers/${id}`, data),
|
||||
activateProvider: (id: string) => api.post<void>(`/admin/ai-providers/${id}/activate`),
|
||||
deleteProvider: (id: string) => api.delete<void>(`/admin/ai-providers/${id}`),
|
||||
listModels: (id: string) => api.get<string[]>(`/admin/ai-providers/${id}/models`),
|
||||
|
||||
// Sources
|
||||
listSources: () => api.get<Source[]>('/admin/sources'),
|
||||
updateSource: (id: string, enabled: boolean) => api.put<void>(`/admin/sources/${id}`, { enabled }),
|
||||
|
||||
// Jobs
|
||||
listJobs: () => api.get<ScrapeJob[]>('/admin/scrape-jobs'),
|
||||
triggerJob: (source_id: string) => api.post<void>('/admin/scrape-jobs/trigger', { source_id }),
|
||||
|
||||
// Settings
|
||||
listSettings: () => api.get<Setting[]>('/admin/settings'),
|
||||
updateSettings: (settings: Setting[]) => api.put<void>('/admin/settings', { settings }),
|
||||
getDefaultPrompt: () => api.get<{ prompt: string }>('/admin/settings/default-prompt'),
|
||||
|
||||
// Users
|
||||
listUsers: () => api.get<AdminUser[]>('/admin/users'),
|
||||
updateUser: (id: string, email: string, role: string) =>
|
||||
api.put<AdminUser>(`/admin/users/${id}`, { email, role }),
|
||||
deleteUser: (id: string) => api.delete<void>(`/admin/users/${id}`),
|
||||
}
|
||||
25
frontend/src/api/articles.ts
Normal file
25
frontend/src/api/articles.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { api } from './client'
|
||||
|
||||
export interface Article {
|
||||
id: string
|
||||
source_id: string
|
||||
source_name: string
|
||||
title: string
|
||||
content: string
|
||||
url: string
|
||||
published_at: { Time: string; Valid: boolean } | null
|
||||
created_at: string
|
||||
symbols?: string[]
|
||||
}
|
||||
|
||||
export const articlesApi = {
|
||||
list: (params?: { symbol?: string; limit?: number; offset?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.symbol) q.set('symbol', params.symbol)
|
||||
if (params?.limit) q.set('limit', String(params.limit))
|
||||
if (params?.offset) q.set('offset', String(params.offset))
|
||||
const qs = q.toString()
|
||||
return api.get<Article[]>(`/articles${qs ? `?${qs}` : ''}`)
|
||||
},
|
||||
get: (id: string) => api.get<Article>(`/articles/${id}`),
|
||||
}
|
||||
9
frontend/src/api/assets.ts
Normal file
9
frontend/src/api/assets.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { api } from './client'
|
||||
|
||||
export interface Asset { id: string; user_id: string; symbol: string; name: string; created_at: string }
|
||||
|
||||
export const assetsApi = {
|
||||
list: () => api.get<Asset[]>('/me/assets'),
|
||||
add: (symbol: string, name: string) => api.post<Asset>('/me/assets', { symbol, name }),
|
||||
remove: (symbol: string) => api.delete<void>(`/me/assets/${symbol}`),
|
||||
}
|
||||
12
frontend/src/api/auth.ts
Normal file
12
frontend/src/api/auth.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { api } from './client'
|
||||
|
||||
export interface User { id: string; email: string; role: 'admin' | 'user' }
|
||||
interface AuthResponse { token: string; user: User }
|
||||
|
||||
export const authApi = {
|
||||
login: (email: string, password: string) =>
|
||||
api.post<AuthResponse>('/auth/login', { email, password }),
|
||||
register: (email: string, password: string) =>
|
||||
api.post<AuthResponse>('/auth/register', { email, password }),
|
||||
me: () => api.get<User>('/me'),
|
||||
}
|
||||
33
frontend/src/api/client.ts
Normal file
33
frontend/src/api/client.ts
Normal file
@ -0,0 +1,33 @@
|
||||
const BASE = '/api'
|
||||
|
||||
function getToken() {
|
||||
return localStorage.getItem('token')
|
||||
}
|
||||
|
||||
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const token = getToken()
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new Error(err.error || res.statusText)
|
||||
}
|
||||
if (res.status === 204) return undefined as T
|
||||
const json = await res.json()
|
||||
return json.data !== undefined ? json.data : json
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
post: <T>(path: string, body?: unknown) =>
|
||||
request<T>(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }),
|
||||
put: <T>(path: string, body?: unknown) =>
|
||||
request<T>(path, { method: 'PUT', body: body ? JSON.stringify(body) : undefined }),
|
||||
delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
|
||||
}
|
||||
14
frontend/src/api/summaries.ts
Normal file
14
frontend/src/api/summaries.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { api } from './client'
|
||||
|
||||
export interface Summary {
|
||||
id: string
|
||||
user_id: string
|
||||
content: string
|
||||
ai_provider_id: string | null
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export const summariesApi = {
|
||||
list: (limit = 10) => api.get<Summary[]>(`/summaries?limit=${limit}`),
|
||||
generate: () => api.post<Summary>('/summaries/generate'),
|
||||
}
|
||||
Reference in New Issue
Block a user