import { NextResponse } from 'next/server'; import { getClients, resetClients } from '@/lib/clients'; import { getFirms } from '@/lib/db'; import axios from 'axios'; export async function GET() { try { const clients = getClients(); const firms = getFirms(); const debug = firms.map((firm) => { const client = clients.get(firm.id); if (!client) return { firmId: firm.id, firmName: firm.name, status: 'no_client' }; return { firmId: firm.id, firmName: firm.name, status: 'connected', accountCount: client.accountList.length, fetchDaysComplete: (client as any).fetchDaysComplete ?? 'n/a (old instance)', lastFetchErrors: (client as any).lastFetchErrors ?? {}, lastFetchRaw: (client as any).lastFetchRaw ?? {}, accounts: client.accountList.map((acc) => ({ id: acc.id, name: acc.name, cash: client.accountCashBalances[acc.id] ?? null, daysTraded: client.daysTraded[acc.id] ?? '(not set)', dailyPnLEntries: (client.dailyPnL[acc.id] ?? []).length, dailyPnL: client.dailyPnL[acc.id] ?? [], })), recentEntityEvents: client.recentEntityEvents.slice(-10).map((e) => ({ ts: new Date(e.ts).toISOString(), entityType: e.entityType, eventType: e.eventType, entity: e.entity, })), }; }); return NextResponse.json({ ok: true, ts: new Date().toISOString(), firms: debug }); } catch (err) { console.error('[GET /api/debug]', err); return NextResponse.json({ error: String(err) }, { status: 500 }); } } /** POST /api/debug — manually trigger fetchDaysTraded on all clients and return results */ export async function POST() { try { const clients = getClients(); const firms = getFirms(); const results = await Promise.all( firms.map(async (firm) => { const client = clients.get(firm.id); if (!client || client.accountList.length === 0) { return { firmId: firm.id, firmName: firm.name, status: 'skipped' }; } try { await (client as any).fetchDaysTraded(); } catch (err) { return { firmId: firm.id, firmName: firm.name, status: 'error', error: String(err) }; } return { firmId: firm.id, firmName: firm.name, status: 'done', fetchDaysComplete: (client as any).fetchDaysComplete ?? 'n/a', errors: (client as any).lastFetchErrors ?? {}, rawSamples: (client as any).lastFetchRaw ?? {}, daysTraded: client.daysTraded, dailyPnLCounts: Object.fromEntries( Object.entries(client.dailyPnL).map(([k, v]) => [k, (v as any[]).length]) ), }; }) ); return NextResponse.json({ ok: true, ts: new Date().toISOString(), results }); } catch (err) { console.error('[POST /api/debug]', err); return NextResponse.json({ error: String(err) }, { status: 500 }); } } /** * PATCH /api/debug — probe Tradovate APIs using a firm's access token. * * Generic proxy: { firmId, path?, url?, method?, body? } * Full report run: { firmId, action: 'report', account, startDate, endDate } * → does requestreport + getreport polling in one server-side call, returns raw data string */ export async function PATCH(req: Request) { try { const payload = await req.json(); const { firmId } = payload; const client = getClients().get(firmId) as any; if (!client?.accessInfo?.accessToken) { return NextResponse.json({ error: 'no token for firmId ' + firmId }, { status: 400 }); } const headers = { Authorization: `Bearer ${client.accessInfo.accessToken}` }; // Full report cycle if (payload.action === 'report') { const { account, startDate, endDate, name: reportName = 'Fills' } = payload; try { let reportData = (await axios.post( 'https://rpt-demo.tradovateapi.com/v1/reports/requestreport', { name: reportName, representationType: 'json', timezone: -300, params: [ { name: 'startDate', value: startDate }, { name: 'endDate', value: endDate }, { name: 'startTime', value: '00:00:00' }, { name: 'endTime', value: '00:00:00' }, ...(account ? [{ name: 'account', value: account }] : []), ], }, { headers } )).data; let attempts = 0; while (reportData?.['p-ticket'] && attempts < 30) { const ticket: string = reportData['p-ticket']; const wait: number = Math.max(1, reportData['p-time'] ?? 1); await new Promise((r) => setTimeout(r, wait * 1000)); reportData = (await axios.get( 'https://rpt-demo.tradovateapi.com/v1/reports/getreport', { params: { 'p-ticket': ticket }, headers } )).data; attempts++; } const raw: string = typeof reportData?.data === 'string' ? reportData.data : JSON.stringify(reportData); // Parse to count fills and unique tradeDates let fillCount = 0; let uniqueDates: string[] = []; try { const fixed = raw.replace(/"_tradeDate":\s*(\d{4}-\d{2}-\d{2})/g, '"_tradeDate": "$1"'); const parsed: any[] = JSON.parse(fixed); fillCount = parsed.length; uniqueDates = [...new Set(parsed.map((f) => f._tradeDate as string))].sort(); } catch { /* not parseable yet */ } return NextResponse.json({ ok: true, attempts, rawLen: raw.length, fillCount, uniqueDates, preview: raw.slice(0, 400) }); } catch (err: any) { return NextResponse.json({ ok: false, status: err?.response?.status, data: err?.response?.data, message: err?.message, }); } } // Generic proxy const { path, url: rawUrl, method = 'GET', body: reqBody } = payload; const url = rawUrl ?? `https://demo.tradovateapi.com/v1/${path}`; try { const res = method === 'POST' ? await axios.post(url, reqBody, { headers }) : await axios.get(url, { headers }); return NextResponse.json({ ok: true, status: res.status, data: res.data }); } catch (err: any) { return NextResponse.json({ ok: false, status: err?.response?.status, data: err?.response?.data, message: err?.message, }); } } catch (err) { return NextResponse.json({ error: String(err) }, { status: 500 }); } } /** * DELETE /api/debug — force-reinitialize all Tradovate clients with fresh instances * Clears the global pool so getClients() recreates everything from DB on next call. */ export async function DELETE() { try { const g = global as any; const oldCount = (g.__tradovateClients as Map | undefined)?.size ?? 0; // Disconnect all existing WebSocket connections before clearing resetClients(); // Re-initialize immediately with fresh instances getClients(); return NextResponse.json({ ok: true, message: `Disconnected ${oldCount} old client(s) — fresh instances initializing`, ts: new Date().toISOString(), }); } catch (err) { console.error('[DELETE /api/debug]', err); return NextResponse.json({ error: String(err) }, { status: 500 }); } }