borsa / nextjs-app /src /app /api /sentiment /route.ts
bot
feat: breakeven trailing stop, tighter TP/SL (5/10), min_confidence 20; Mac workers offline
0465873
import { NextRequest, NextResponse } from 'next/server'
import { API_BASE } from '@/lib/runtime-config'
import { requireAuth } from '@/lib/api-auth'
export const dynamic = 'force-dynamic'
export async function POST(req: NextRequest) {
const auth = await requireAuth(req)
if (!auth.authenticated) return auth.response
if (!API_BASE) {
return NextResponse.json(
{ ok: false, error: 'API URL yapılandırılmamış. NEXT_PUBLIC_API_URL tanımlayın.' },
{ status: 500 }
)
}
let body: Record<string, unknown> | null = null
try {
body = await req.json()
} catch {
body = null
}
const text = typeof body?.text === 'string' ? body.text : ''
if (!text.trim()) {
return NextResponse.json({ ok: false, error: 'text is required' }, { status: 400 })
}
// Limit text size to prevent DoS on upstream service (max 50KB)
const MAX_TEXT_LENGTH = 50000
if (text.length > MAX_TEXT_LENGTH) {
return NextResponse.json(
{ ok: false, error: `Text too long (max ${MAX_TEXT_LENGTH} characters, received ${text.length})` },
{ status: 400 }
)
}
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 20000)
let resp: Response
try {
resp = await fetch(`${API_BASE}/api/sentiment`, {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
cache: 'no-store',
body: JSON.stringify({ text }),
signal: controller.signal,
})
} finally {
clearTimeout(timeout)
}
const payload = await resp.json().catch(() => null)
if (!resp.ok) {
return NextResponse.json(
{
ok: false,
error: `Upstream sentiment error: ${resp.status}`,
detail: payload,
},
{ status: resp.status }
)
}
return NextResponse.json(payload ?? { ok: false, error: 'Invalid upstream payload' }, { status: 200 })
} catch (e: unknown) {
const message = e instanceof Error ? e.message : 'Unknown error'
return NextResponse.json(
{ ok: false, error: message },
{ status: 502 }
)
}
}