feat: upload creates new chart from filename with duplicate handling

- POST /api/upload now creates a chart named from the CSV filename
- Duplicate names get numeric suffix (e.g., btc-daily-2)
- Candles inserted with chart_id instead of replacing all data
- Response includes chart id and name
This commit is contained in:
Marko Djordjevic 2026-02-13 00:13:23 +01:00
parent b53cb1b7d1
commit 98e91b047a
2 changed files with 41 additions and 10 deletions

View file

@ -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

View file

@ -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<string> {
// 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<NextResponse> {
try {
@ -18,6 +38,9 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
const text = await file.text();
// Derive chart name from filename (strip .csv extension)
const baseName = file.name.replace(/\.csv$/i, '');
return new Promise<NextResponse>((resolve) => {
Papa.parse(text, {
header: true,
@ -56,6 +79,15 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
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<NextResponse> {
}
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<NextResponse> {
};
});
// 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> {
NextResponse.json({
success: true,
count,
chart: { id: newChart.id, name: newChart.name },
})
);
} catch (error: any) {