diff --git a/openspec/changes/multi-chart-management/tasks.md b/openspec/changes/multi-chart-management/tasks.md index 767daf6..be610ff 100644 --- a/openspec/changes/multi-chart-management/tasks.md +++ b/openspec/changes/multi-chart-management/tasks.md @@ -14,10 +14,10 @@ ## 3. Upload Endpoint Changes -- [ ] 3.1 Modify `POST /api/upload` to create a new chart named from the uploaded filename (strip `.csv` extension) -- [ ] 3.2 Add duplicate name handling — append numeric suffix if chart name already exists -- [ ] 3.3 Insert candles with the new chart's `chart_id` instead of deleting all existing candles -- [ ] 3.4 Return chart `id` and `name` in the upload response JSON +- [x] 3.1 Modify `POST /api/upload` to create a new chart named from the uploaded filename (strip `.csv` extension) +- [x] 3.2 Add duplicate name handling — append numeric suffix if chart name already exists +- [x] 3.3 Insert candles with the new chart's `chart_id` instead of deleting all existing candles +- [x] 3.4 Return chart `id` and `name` in the upload response JSON ## 4. Candles & Annotations API Scoping diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 19528c9..528147c 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,8 +1,28 @@ import { NextRequest, NextResponse } from 'next/server'; import Papa from 'papaparse'; import { db } from '@/lib/db'; -import { candles } from '@/lib/db/schema'; -import { sql } from 'drizzle-orm'; +import { candles, charts } from '@/lib/db/schema'; +import { eq, like } from 'drizzle-orm'; + +async function getUniqueChartName(baseName: string): Promise { + // Check if the base name is already taken + const existing = await db.select().from(charts).where(eq(charts.name, baseName)).limit(1); + if (existing.length === 0) return baseName; + + // Find existing charts with this base name pattern (e.g., "btc-daily-2", "btc-daily-3") + const pattern = `${baseName}-%`; + const suffixed = await db.select({ name: charts.name }).from(charts).where(like(charts.name, pattern)); + + let maxSuffix = 1; + for (const row of suffixed) { + const match = row.name.match(/-(\d+)$/); + if (match) { + maxSuffix = Math.max(maxSuffix, parseInt(match[1], 10)); + } + } + + return `${baseName}-${maxSuffix + 1}`; +} export async function POST(request: NextRequest): Promise { try { @@ -18,6 +38,9 @@ export async function POST(request: NextRequest): Promise { const text = await file.text(); + // Derive chart name from filename (strip .csv extension) + const baseName = file.name.replace(/\.csv$/i, ''); + return new Promise((resolve) => { Papa.parse(text, { header: true, @@ -56,6 +79,15 @@ export async function POST(request: NextRequest): Promise { return; } + // Get unique chart name (handle duplicates) + const chartName = await getUniqueChartName(baseName); + + // Create the chart + const [newChart] = await db.insert(charts).values({ + name: chartName, + created_at: Math.floor(Date.now() / 1000), + }).returning(); + // Parse and prepare candle data const candleData = rows.map((row) => { let timestamp: number; @@ -75,6 +107,7 @@ export async function POST(request: NextRequest): Promise { } return { + chart_id: newChart.id, time: timestamp, open: Number(row.open), high: Number(row.high), @@ -83,10 +116,7 @@ export async function POST(request: NextRequest): Promise { }; }); - // Delete all old candles before inserting new ones - await db.delete(candles); - - // Insert new candles + // Insert candles for this chart const count = candleData.length; if (count > 0) { await db.insert(candles).values(candleData); @@ -96,6 +126,7 @@ export async function POST(request: NextRequest): Promise { NextResponse.json({ success: true, count, + chart: { id: newChart.id, name: newChart.name }, }) ); } catch (error: any) {