Scope all Drizzle queries by user_id from authenticated session
Every data API route now filters SELECT, INSERT, UPDATE, and DELETE queries by the authenticated user's ID, ensuring full multi-tenant data isolation. Candle queries are scoped via chart_id ownership. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9901d0f3f1
commit
5f727d84c6
15 changed files with 75 additions and 60 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { annotationTypes } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
type RouteContext = {
|
||||
|
|
@ -51,7 +51,7 @@ export async function PATCH(
|
|||
const result = await db
|
||||
.update(annotationTypes)
|
||||
.set(updateData)
|
||||
.where(eq(annotationTypes.id, idNum))
|
||||
.where(and(eq(annotationTypes.id, idNum), eq(annotationTypes.user_id, user.id)))
|
||||
.returning();
|
||||
|
||||
if (result.length === 0) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { annotationTypes, annotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET - List all annotation types
|
||||
|
|
@ -12,7 +12,7 @@ export async function GET() {
|
|||
}
|
||||
|
||||
try {
|
||||
const types = await db.select().from(annotationTypes);
|
||||
const types = await db.select().from(annotationTypes).where(eq(annotationTypes.user_id, user.id));
|
||||
return NextResponse.json(types);
|
||||
} catch (error) {
|
||||
console.error('Error fetching annotation types:', error);
|
||||
|
|
@ -35,8 +35,8 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// Special case: seed default types
|
||||
if (body.action === 'seed') {
|
||||
const existing = await db.select().from(annotationTypes);
|
||||
|
||||
const existing = await db.select().from(annotationTypes).where(eq(annotationTypes.user_id, user.id));
|
||||
|
||||
if (existing.length > 0) {
|
||||
return NextResponse.json({ message: 'Types already seeded' });
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ export async function POST(request: NextRequest) {
|
|||
category: 'marker',
|
||||
icon: 'arrowUp',
|
||||
is_active: true,
|
||||
user_id: user.id,
|
||||
},
|
||||
{
|
||||
name: 'break_down',
|
||||
|
|
@ -57,6 +58,7 @@ export async function POST(request: NextRequest) {
|
|||
category: 'marker',
|
||||
icon: 'arrowDown',
|
||||
is_active: true,
|
||||
user_id: user.id,
|
||||
},
|
||||
{
|
||||
name: 'line',
|
||||
|
|
@ -65,6 +67,7 @@ export async function POST(request: NextRequest) {
|
|||
category: 'line',
|
||||
icon: 'line',
|
||||
is_active: true,
|
||||
user_id: user.id,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -91,6 +94,7 @@ export async function POST(request: NextRequest) {
|
|||
category,
|
||||
icon: icon || null,
|
||||
is_active: true,
|
||||
user_id: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
|
@ -130,11 +134,11 @@ export async function DELETE(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
// Check if the type exists
|
||||
// Check if the type exists and belongs to user
|
||||
const type = await db
|
||||
.select()
|
||||
.from(annotationTypes)
|
||||
.where(eq(annotationTypes.id, parseInt(id)))
|
||||
.where(and(eq(annotationTypes.id, parseInt(id)), eq(annotationTypes.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (type.length === 0) {
|
||||
|
|
@ -144,11 +148,11 @@ export async function DELETE(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
// Check if any annotations use this type
|
||||
// Check if any of this user's annotations use this type
|
||||
const existingAnnotations = await db
|
||||
.select()
|
||||
.from(annotations)
|
||||
.where(eq(annotations.label_type, type[0].name))
|
||||
.where(and(eq(annotations.label_type, type[0].name), eq(annotations.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (existingAnnotations.length > 0) {
|
||||
|
|
@ -158,7 +162,7 @@ export async function DELETE(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
await db.delete(annotationTypes).where(eq(annotationTypes.id, parseInt(id)));
|
||||
await db.delete(annotationTypes).where(and(eq(annotationTypes.id, parseInt(id)), eq(annotationTypes.user_id, user.id)));
|
||||
|
||||
return NextResponse.json({ message: 'Annotation type deleted successfully' });
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { annotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function PATCH(
|
||||
|
|
@ -37,7 +37,7 @@ export async function PATCH(
|
|||
const result = await db
|
||||
.update(annotations)
|
||||
.set({ geometry })
|
||||
.where(eq(annotations.id, id))
|
||||
.where(and(eq(annotations.id, id), eq(annotations.user_id, user.id)))
|
||||
.returning();
|
||||
|
||||
if (result.length === 0) {
|
||||
|
|
@ -79,7 +79,7 @@ export async function DELETE(
|
|||
|
||||
const result = await db
|
||||
.delete(annotations)
|
||||
.where(eq(annotations.id, id))
|
||||
.where(and(eq(annotations.id, id), eq(annotations.user_id, user.id)))
|
||||
.returning();
|
||||
|
||||
if (result.length === 0) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
// 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);
|
||||
const latest = await db.select({ id: charts.id }).from(charts).where(eq(charts.user_id, user.id)).orderBy(desc(charts.created_at)).limit(1);
|
||||
if (latest.length === 0) {
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ export async function GET(request: NextRequest) {
|
|||
const allAnnotations = await db
|
||||
.select()
|
||||
.from(annotations)
|
||||
.where(eq(annotations.chart_id, chartIdNum));
|
||||
.where(and(eq(annotations.chart_id, chartIdNum), eq(annotations.user_id, user.id)));
|
||||
|
||||
const normalized = allAnnotations.map((a) => ({
|
||||
...a,
|
||||
|
|
@ -71,8 +71,8 @@ export async function POST(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
// Verify chart exists
|
||||
const chartExists = await db.select({ id: charts.id }).from(charts).where(eq(charts.id, chart_id)).limit(1);
|
||||
// Verify chart exists and belongs to user
|
||||
const chartExists = await db.select({ id: charts.id }).from(charts).where(and(eq(charts.id, chart_id), eq(charts.user_id, user.id))).limit(1);
|
||||
if (chartExists.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Chart not found' },
|
||||
|
|
@ -89,6 +89,7 @@ export async function POST(request: NextRequest) {
|
|||
.insert(annotations)
|
||||
.values({
|
||||
chart_id,
|
||||
user_id: user.id,
|
||||
timestamp: timestampDate,
|
||||
label_type,
|
||||
geometry: geometry || null,
|
||||
|
|
@ -139,7 +140,7 @@ export async function DELETE(request: NextRequest) {
|
|||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
result = await db.delete(annotations).where(eq(annotations.chart_id, chartIdNum)).returning();
|
||||
result = await db.delete(annotations).where(and(eq(annotations.chart_id, chartIdNum), eq(annotations.user_id, user.id))).returning();
|
||||
} else if (type) {
|
||||
const types = type.split(',').map((t) => t.trim());
|
||||
if (chartId) {
|
||||
|
|
@ -152,12 +153,12 @@ export async function DELETE(request: NextRequest) {
|
|||
}
|
||||
result = await db
|
||||
.delete(annotations)
|
||||
.where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, chartIdNum)))
|
||||
.where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, chartIdNum), eq(annotations.user_id, user.id)))
|
||||
.returning();
|
||||
} else {
|
||||
result = await db
|
||||
.delete(annotations)
|
||||
.where(inArray(annotations.label_type, types))
|
||||
.where(and(inArray(annotations.label_type, types), eq(annotations.user_id, user.id)))
|
||||
.returning();
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { candles, charts } from '@/lib/db/schema';
|
||||
import { asc, desc, eq } from 'drizzle-orm';
|
||||
import { asc, desc, eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
|
|
@ -16,7 +16,7 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
// 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);
|
||||
const latest = await db.select({ id: charts.id }).from(charts).where(eq(charts.user_id, user.id)).orderBy(desc(charts.created_at)).limit(1);
|
||||
if (latest.length === 0) {
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
|
|
@ -31,6 +31,12 @@ export async function GET(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
// Verify chart belongs to user
|
||||
const chartOwner = await db.select({ id: charts.id }).from(charts).where(and(eq(charts.id, chartIdNum), eq(charts.user_id, user.id))).limit(1);
|
||||
if (chartOwner.length === 0) {
|
||||
return NextResponse.json({ error: 'Chart not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const allCandles = await db
|
||||
.select({
|
||||
time: candles.time,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { charts, candles, annotations, spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET(
|
||||
|
|
@ -20,7 +20,7 @@ export async function GET(
|
|||
return NextResponse.json({ error: 'Invalid chart ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await db.select().from(charts).where(eq(charts.id, chartId)).limit(1);
|
||||
const result = await db.select().from(charts).where(and(eq(charts.id, chartId), eq(charts.user_id, user.id))).limit(1);
|
||||
|
||||
if (result.length === 0) {
|
||||
return NextResponse.json({ error: 'Chart not found' }, { status: 404 });
|
||||
|
|
@ -45,7 +45,7 @@ export async function DELETE(
|
|||
return NextResponse.json({ error: 'Invalid chart ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await db.select().from(charts).where(eq(charts.id, chartId)).limit(1);
|
||||
const existing = await db.select().from(charts).where(and(eq(charts.id, chartId), eq(charts.user_id, user.id))).limit(1);
|
||||
if (existing.length === 0) {
|
||||
return NextResponse.json({ error: 'Chart not found' }, { status: 404 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { charts } from '@/lib/db/schema';
|
||||
import { desc } from 'drizzle-orm';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET() {
|
||||
|
|
@ -10,6 +10,6 @@ export async function GET() {
|
|||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const allCharts = await db.select().from(charts).orderBy(desc(charts.created_at));
|
||||
const allCharts = await db.select().from(charts).where(eq(charts.user_id, user.id)).orderBy(desc(charts.created_at));
|
||||
return NextResponse.json(allCharts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
// 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);
|
||||
const latest = await db.select({ id: charts.id }).from(charts).where(eq(charts.user_id, user.id)).orderBy(desc(charts.created_at)).limit(1);
|
||||
if (latest.length === 0) {
|
||||
return new NextResponse('timestamp,label_type,price\n', {
|
||||
headers: {
|
||||
|
|
@ -46,7 +46,7 @@ export async function GET(request: NextRequest) {
|
|||
const allAnnotations = await db
|
||||
.select()
|
||||
.from(annotations)
|
||||
.where(eq(annotations.chart_id, chartIdNum));
|
||||
.where(and(eq(annotations.chart_id, chartIdNum), eq(annotations.user_id, user.id)));
|
||||
|
||||
// Build CSV content
|
||||
const csvRows = ['timestamp,label_type,price'];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// PATCH - Update span annotation
|
||||
|
|
@ -29,11 +29,11 @@ export async function PATCH(
|
|||
|
||||
const { label, confidence, outcome, notes, sub_spans } = body;
|
||||
|
||||
// Check if the span exists
|
||||
// Check if the span exists and belongs to user
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(spanAnnotations)
|
||||
.where(eq(spanAnnotations.id, idNum))
|
||||
.where(and(eq(spanAnnotations.id, idNum), eq(spanAnnotations.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length === 0) {
|
||||
|
|
@ -54,7 +54,7 @@ export async function PATCH(
|
|||
const result = await db
|
||||
.update(spanAnnotations)
|
||||
.set(updates)
|
||||
.where(eq(spanAnnotations.id, idNum))
|
||||
.where(and(eq(spanAnnotations.id, idNum), eq(spanAnnotations.user_id, user.id)))
|
||||
.returning();
|
||||
|
||||
const s = result[0];
|
||||
|
|
@ -94,11 +94,11 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
|
||||
// Check if the span exists
|
||||
// Check if the span exists and belongs to user
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(spanAnnotations)
|
||||
.where(eq(spanAnnotations.id, idNum))
|
||||
.where(and(eq(spanAnnotations.id, idNum), eq(spanAnnotations.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length === 0) {
|
||||
|
|
@ -108,7 +108,7 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
|
||||
await db.delete(spanAnnotations).where(eq(spanAnnotations.id, idNum));
|
||||
await db.delete(spanAnnotations).where(and(eq(spanAnnotations.id, idNum), eq(spanAnnotations.user_id, user.id)));
|
||||
|
||||
return NextResponse.json({ message: 'Span annotation deleted successfully' });
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations, charts } from '@/lib/db/schema';
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
import { eq, desc, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
function sanitizeCsvCell(value: string): string {
|
||||
|
|
@ -53,6 +53,7 @@ export async function GET(request: NextRequest) {
|
|||
const latest = await db
|
||||
.select({ id: charts.id })
|
||||
.from(charts)
|
||||
.where(eq(charts.user_id, user.id))
|
||||
.orderBy(desc(charts.created_at))
|
||||
.limit(1);
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ export async function GET(request: NextRequest) {
|
|||
const spans = await db
|
||||
.select()
|
||||
.from(spanAnnotations)
|
||||
.where(eq(spanAnnotations.chart_id, chartIdNum))
|
||||
.where(and(eq(spanAnnotations.chart_id, chartIdNum), eq(spanAnnotations.user_id, user.id)))
|
||||
.orderBy(spanAnnotations.start_time);
|
||||
|
||||
if (format === 'json') {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export async function GET(request: NextRequest) {
|
|||
const spans = await db
|
||||
.select()
|
||||
.from(spanAnnotations)
|
||||
.where(eq(spanAnnotations.chart_id, chartIdNum))
|
||||
.where(and(eq(spanAnnotations.chart_id, chartIdNum), eq(spanAnnotations.user_id, user.id)))
|
||||
.orderBy(desc(spanAnnotations.start_time));
|
||||
|
||||
const normalized = spans.map((s) => ({
|
||||
|
|
@ -91,6 +91,7 @@ export async function POST(request: NextRequest) {
|
|||
.insert(spanAnnotations)
|
||||
.values({
|
||||
chart_id,
|
||||
user_id: user.id,
|
||||
start_time: new Date(actualStartTime * 1000),
|
||||
end_time: new Date(actualEndTime * 1000),
|
||||
label,
|
||||
|
|
@ -148,8 +149,8 @@ export async function DELETE(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
// Build filter conditions
|
||||
const conditions = [eq(spanAnnotations.chart_id, chartIdInt)];
|
||||
// Build filter conditions (always scoped to user)
|
||||
const conditions = [eq(spanAnnotations.chart_id, chartIdInt), eq(spanAnnotations.user_id, user.id)];
|
||||
if (source) conditions.push(eq(spanAnnotations.source, source));
|
||||
if (label) conditions.push(eq(spanAnnotations.label, label));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { spanLabelTypes, spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// PATCH - Update span label type
|
||||
|
|
@ -29,11 +29,11 @@ export async function PATCH(
|
|||
|
||||
const { name, display_name, color, hotkey, is_active, sort_order } = body;
|
||||
|
||||
// Check if the type exists
|
||||
// Check if the type exists and belongs to user
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(spanLabelTypes)
|
||||
.where(eq(spanLabelTypes.id, idNum))
|
||||
.where(and(eq(spanLabelTypes.id, idNum), eq(spanLabelTypes.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length === 0) {
|
||||
|
|
@ -55,7 +55,7 @@ export async function PATCH(
|
|||
const result = await db
|
||||
.update(spanLabelTypes)
|
||||
.set(updates)
|
||||
.where(eq(spanLabelTypes.id, idNum))
|
||||
.where(and(eq(spanLabelTypes.id, idNum), eq(spanLabelTypes.user_id, user.id)))
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(result[0]);
|
||||
|
|
@ -97,11 +97,11 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
|
||||
// Check if the type exists
|
||||
// Check if the type exists and belongs to user
|
||||
const type = await db
|
||||
.select()
|
||||
.from(spanLabelTypes)
|
||||
.where(eq(spanLabelTypes.id, idNum))
|
||||
.where(and(eq(spanLabelTypes.id, idNum), eq(spanLabelTypes.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (type.length === 0) {
|
||||
|
|
@ -111,11 +111,11 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
|
||||
// Check if any span annotations use this label
|
||||
// Check if any of this user's span annotations use this label
|
||||
const existingSpans = await db
|
||||
.select()
|
||||
.from(spanAnnotations)
|
||||
.where(eq(spanAnnotations.label, type[0].name))
|
||||
.where(and(eq(spanAnnotations.label, type[0].name), eq(spanAnnotations.user_id, user.id)))
|
||||
.limit(1);
|
||||
|
||||
if (existingSpans.length > 0) {
|
||||
|
|
@ -125,7 +125,7 @@ export async function DELETE(
|
|||
);
|
||||
}
|
||||
|
||||
await db.delete(spanLabelTypes).where(eq(spanLabelTypes.id, idNum));
|
||||
await db.delete(spanLabelTypes).where(and(eq(spanLabelTypes.id, idNum), eq(spanLabelTypes.user_id, user.id)));
|
||||
|
||||
return NextResponse.json({ message: 'Span label type deleted successfully' });
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { spanLabelTypes } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET - List all span label types (active only, sorted by sort_order)
|
||||
|
|
@ -15,7 +15,7 @@ export async function GET() {
|
|||
const types = await db
|
||||
.select()
|
||||
.from(spanLabelTypes)
|
||||
.where(eq(spanLabelTypes.is_active, true))
|
||||
.where(and(eq(spanLabelTypes.is_active, true), eq(spanLabelTypes.user_id, user.id)))
|
||||
.orderBy(spanLabelTypes.sort_order);
|
||||
|
||||
return NextResponse.json(types);
|
||||
|
|
@ -55,6 +55,7 @@ export async function POST(request: NextRequest) {
|
|||
hotkey: hotkey || null,
|
||||
is_active: true,
|
||||
sort_order: sort_order ?? 0,
|
||||
user_id: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import Papa from 'papaparse';
|
||||
import { db } from '@/lib/db';
|
||||
import { candles, charts } from '@/lib/db/schema';
|
||||
import { eq, like } from 'drizzle-orm';
|
||||
import { eq, like, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
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);
|
||||
async function getUniqueChartName(baseName: string, userId: string): Promise<string> {
|
||||
// Check if the base name is already taken for this user
|
||||
const existing = await db.select().from(charts).where(and(eq(charts.user_id, userId), 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));
|
||||
const suffixed = await db.select({ name: charts.name }).from(charts).where(and(eq(charts.user_id, userId), like(charts.name, pattern)));
|
||||
|
||||
let maxSuffix = 1;
|
||||
for (const row of suffixed) {
|
||||
|
|
@ -124,11 +124,12 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
|||
}
|
||||
|
||||
// Get unique chart name (handle duplicates)
|
||||
const chartName = await getUniqueChartName(baseName);
|
||||
const chartName = await getUniqueChartName(baseName, user.id);
|
||||
|
||||
// Create the chart
|
||||
const [newChart] = await db.insert(charts).values({
|
||||
name: chartName,
|
||||
user_id: user.id,
|
||||
}).returning();
|
||||
|
||||
// Parse and prepare candle data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue