/** * Shared API route utility functions. * Eliminates duplication across stocks, trading-signals, and analysis routes. */ /** * Safely converts an unknown value to a finite number, or returns null. */ export function toNumber(value: unknown): number | null { const n = typeof value === 'number' ? value : Number(value) return Number.isFinite(n) ? n : null } /** * Picks the first present key from an object. * Useful when upstream APIs return data under varying field names. */ export function pick>( obj: T | null | undefined, keys: string[] ): unknown { if (!obj) return undefined for (const k of keys) { if (k in obj) return obj[k] } return undefined } /** * Extracts and validates a stock symbol from Next.js dynamic route params. * Returns uppercase sanitized symbol or null if invalid. */ export async function getSymbolFromParams( params: Promise<{ symbol: string }> | { symbol: string } ): Promise { const resolved = await params const raw = resolved?.symbol if (!raw || typeof raw !== 'string') return null const symbol = raw.toUpperCase().trim() // Only allow alphanumeric + dot (e.g. "THYAO", "XU100.IS") if (!/^[A-Z0-9.]{1,20}$/.test(symbol)) return null return symbol } /** * Sanitizes upstream error messages to prevent leaking internal details. * Strips file paths, stack traces, and internal URLs. */ export function sanitizeErrorMessage(error: unknown): string { if (!error) return 'Bilinmeyen hata' const message = error instanceof Error ? error.message : String(error) // Strip file paths (unix/windows) let sanitized = message.replace(/\/[\w./\\-]+\.(ts|js|tsx|jsx|py|json)/gi, '[path]') // Strip stack traces sanitized = sanitized.replace(/at\s+\w+.*\(.*\)/g, '') // Strip internal URLs (keep only the error context) sanitized = sanitized.replace(/https?:\/\/[^\s"')]+/g, '[url]') // Truncate to 200 chars if (sanitized.length > 200) { sanitized = sanitized.slice(0, 200) + '...' } return sanitized.trim() || 'Bilinmeyen hata' }