feat: scope candles/annotations/export APIs by chartId query param

- GET /api/candles accepts ?chartId= with fallback to most recent chart
- GET /api/annotations accepts ?chartId= with fallback to most recent chart
- POST /api/annotations now requires chart_id in request body
- GET /api/export accepts ?chartId= to scope exported annotations
- DELETE /api/annotations supports optional chartId scoping
This commit is contained in:
Marko Djordjevic 2026-02-13 00:14:22 +01:00
parent 98e91b047a
commit 90e1e179cc
4 changed files with 107 additions and 35 deletions

View file

@ -1,11 +1,32 @@
import { NextResponse } from 'next/server';
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { annotations, candles } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';
import { annotations, candles, charts } from '@/lib/db/schema';
import { eq, and, desc } from 'drizzle-orm';
export async function GET() {
export async function GET(request: NextRequest) {
try {
const allAnnotations = await db.select().from(annotations);
const { searchParams } = request.nextUrl;
let chartId = searchParams.get('chartId');
// Fall back to most recent chart if no chartId provided
if (!chartId) {
const latest = await db.select({ id: charts.id }).from(charts).orderBy(desc(charts.created_at)).limit(1);
if (latest.length === 0) {
return new NextResponse('timestamp,label_type,price\n', {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename="annotations.csv"',
},
});
}
chartId = String(latest[0].id);
}
const chartIdNum = parseInt(chartId, 10);
const allAnnotations = await db
.select()
.from(annotations)
.where(eq(annotations.chart_id, chartIdNum));
// Build CSV content
const csvRows = ['timestamp,label_type,price'];
@ -14,18 +35,16 @@ export async function GET() {
let price: number | null = null;
if (annotation.label_type === 'break_up' || annotation.label_type === 'break_down') {
// For marker annotations, look up the candle's close price
const candleResult = await db
.select()
.from(candles)
.where(eq(candles.time, annotation.timestamp))
.where(and(eq(candles.chart_id, chartIdNum), eq(candles.time, annotation.timestamp)))
.limit(1);
if (candleResult.length > 0) {
price = candleResult[0].close;
}
} else if (annotation.label_type === 'line' && annotation.geometry) {
// For line annotations, use startPrice from geometry
const geometry = JSON.parse(annotation.geometry);
price = geometry.startPrice || null;
}