| import { API_BASE } from '@/lib/runtime-config' |
| import { fetchJson } from '@/lib/http' |
|
|
| |
| |
| |
| |
| |
| |
|
|
| export interface NewsArticle { |
| title: string |
| description: string |
| url: string |
| source: string |
| publishedAt: string |
| image?: string |
| symbol?: string |
| } |
|
|
| export interface KAPAnnouncement { |
| id: string |
| title: string |
| company: string |
| symbol: string |
| category: string |
| publishedAt: string |
| url: string |
| isImportant: boolean |
| } |
|
|
| type BackendNewsItem = { |
| id?: number | string |
| symbol?: string |
| title?: string |
| content?: string |
| source?: string |
| published_at?: string |
| url?: string |
| } |
|
|
| function normalizeBackendNews(item: BackendNewsItem): NewsArticle | null { |
| if (!item) return null |
| const title = String(item.title ?? '').trim() |
| const description = String(item.content ?? '').trim() |
| const url = String(item.url ?? '').trim() |
| const source = String(item.source ?? '').trim() |
| const publishedAt = String(item.published_at ?? '').trim() |
| if (!title || !source || !publishedAt) return null |
|
|
| return { |
| title, |
| description, |
| url: url || '', |
| source, |
| publishedAt, |
| symbol: item.symbol ? String(item.symbol).toUpperCase() : undefined, |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function getTurkishFinanceNews(limit: number = 20): Promise<NewsArticle[]> { |
| try { |
| const data = await fetchJson<Record<string, unknown>>(`${API_BASE}/api/news?limit=${encodeURIComponent(String(limit))}`, { method: 'GET' }, { timeoutMs: 15000, retries: 1 }) |
| const items: BackendNewsItem[] = Array.isArray(data?.data) ? data.data : Array.isArray(data) ? data : [] |
| return items.map(normalizeBackendNews).filter(Boolean) as NewsArticle[] |
| } catch (error) { |
| console.error('Error fetching news:', error) |
| return [] |
| } |
| } |
|
|
| |
| |
| |
| export async function getStockNews(symbol: string, limit: number = 10): Promise<NewsArticle[]> { |
| const sym = String(symbol || '').toUpperCase().trim() |
| if (!sym) return [] |
|
|
| |
| try { |
| const data = await fetchJson<Record<string, unknown>>( |
| `${API_BASE}/api/news?symbol=${encodeURIComponent(sym)}&limit=${encodeURIComponent(String(limit))}`, |
| { method: 'GET' }, |
| { timeoutMs: 15000, retries: 1 } |
| ) |
| const items: BackendNewsItem[] = Array.isArray(data?.data) ? data.data : Array.isArray(data) ? data : [] |
| const normalized = items.map(normalizeBackendNews).filter(Boolean) as NewsArticle[] |
| if (normalized.length) return normalized.slice(0, limit) |
| } catch (error) { |
| |
| console.error('Error fetching stock news:', error) |
| } |
|
|
| const all = await getTurkishFinanceNews(Math.max(limit * 5, 50)) |
| const filtered = all.filter((n) => (n.symbol ? n.symbol === sym : false) || n.title.toUpperCase().includes(sym) || n.description.toUpperCase().includes(sym)) |
| return filtered.slice(0, limit) |
| } |
|
|
| |
| |
| |
| |
| export async function getKAPAnnouncements(_symbol?: string, _limit: number = 20): Promise<KAPAnnouncement[]> { |
| return [] |
| } |
|
|
| |
| |
| |
| |
| export async function getEconomicCalendar(_days: number = 7): Promise<Record<string, unknown>[]> { |
| return [] |
| } |
|
|
| |
| |
| |
| export function formatTimeAgo(date: string | Date): string { |
| const now = new Date() |
| const past = new Date(date) |
| const diffMs = now.getTime() - past.getTime() |
| const diffMins = Math.floor(diffMs / 60000) |
| const diffHours = Math.floor(diffMs / 3600000) |
| const diffDays = Math.floor(diffMs / 86400000) |
|
|
| if (diffMins < 1) return 'Az önce' |
| if (diffMins < 60) return `${diffMins} dakika önce` |
| if (diffHours < 24) return `${diffHours} saat önce` |
| if (diffDays < 30) return `${diffDays} gün önce` |
| |
| return past.toLocaleDateString('tr-TR', { |
| day: 'numeric', |
| month: 'short', |
| year: 'numeric' |
| }) |
| } |
|
|
| |
| |
| |
| export async function getCombinedFeed( |
| symbol?: string, |
| limit: number = 30 |
| ): Promise<Array<NewsArticle | KAPAnnouncement>> { |
| try { |
| const news = symbol ? await getStockNews(symbol, limit) : await getTurkishFinanceNews(limit) |
| return news.slice(0, limit) |
| } catch (error) { |
| console.error('Error fetching combined feed:', error) |
| return [] |
| } |
| } |
|
|
| |
| |
| |
| export async function searchNews( |
| query: string, |
| limit: number = 20 |
| ): Promise<NewsArticle[]> { |
| try { |
| const allNews = await getTurkishFinanceNews(100) |
| |
| const filtered = allNews.filter(news => { |
| const searchText = `${news.title} ${news.description}`.toLowerCase() |
| return searchText.includes(query.toLowerCase()) |
| }) |
|
|
| return filtered.slice(0, limit) |
| } catch (error) { |
| console.error('Error searching news:', error) |
| return [] |
| } |
| } |
|
|
| |
| |
| |
| export async function getTrendingStocksFromNews(): Promise<Array<{ |
| symbol: string |
| mentions: number |
| sentiment: 'positive' | 'negative' | 'neutral' |
| }>> { |
| try { |
| const news = await getTurkishFinanceNews(200) |
| const counts = new Map<string, number>() |
|
|
| for (const n of news) { |
| if (!n.symbol) continue |
| counts.set(n.symbol, (counts.get(n.symbol) || 0) + 1) |
| } |
|
|
| return Array.from(counts.entries()) |
| .sort((a, b) => b[1] - a[1]) |
| .slice(0, 10) |
| .map(([symbol, mentions]) => ({ symbol, mentions, sentiment: 'neutral' })) |
| } catch (error) { |
| console.error('Error getting trending stocks:', error) |
| return [] |
| } |
| } |
|
|