import { NextRequest, NextResponse } from "next/server"; import { getTransactionById, canAccessTransactions } from "@/lib/queries"; import { getCurrentUser } from "@/lib/auth"; import { prisma } from "@/lib/db"; import { queryRaw } from "@/lib/db"; const VALID_TYPES = ["debit", "credit", "payment", "refund", "fee", "interest", "transfer"]; export async function GET( req: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const user = await getCurrentUser(req); if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); const { id } = await params; if (!(await canAccessTransactions(user.id, [Number(id)]))) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } const txn = await getTransactionById(Number(id)); if (!txn) return NextResponse.json({ error: "Not found" }, { status: 404 }); return NextResponse.json(txn); } export async function PATCH( req: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const user = await getCurrentUser(req); if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); const { id } = await params; const transactionId = Number(id); if (!(await canAccessTransactions(user.id, [transactionId]))) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } const body = await req.json(); const { category, merchant_normalized, notes, transaction_type, my_share_percent, description, amount, transaction_date, trip_id, payment_method } = body as { category?: string; merchant_normalized?: string; notes?: string; transaction_type?: string; payment_method?: string | null; my_share_percent?: number | null; description?: string; amount?: number; transaction_date?: string; trip_id?: number | null; }; if (my_share_percent !== undefined && my_share_percent !== null) { if (typeof my_share_percent !== "number" || my_share_percent <= 0 || my_share_percent > 100) { return NextResponse.json({ error: "my_share_percent must be between 1 and 100" }, { status: 400 }); } } // Direct field edits — only allowed for manual transactions (statement_id IS NULL) const directFields = [description, amount, transaction_date].filter((v) => v !== undefined); if (directFields.length > 0) { const txRows = await queryRaw<{ statement_id: number | null }>( `SELECT statement_id FROM transactions WHERE id = $1`, [transactionId] ); if (!txRows[0]?.statement_id) { const setClauses: string[] = []; const params: unknown[] = []; let idx = 1; if (description !== undefined) { setClauses.push(`description = $${idx++}`); params.push(description); } if (amount !== undefined) { setClauses.push(`amount = $${idx++}`); params.push(amount); } if (transaction_date !== undefined) { setClauses.push(`transaction_date = $${idx++}`); params.push(transaction_date); } if (setClauses.length) { params.push(transactionId); await queryRaw(`UPDATE transactions SET ${setClauses.join(", ")} WHERE id = $${idx}`, params); } } } // transaction_type is a direct correction on the transactions table if (transaction_type !== undefined) { if (!VALID_TYPES.includes(transaction_type)) { return NextResponse.json({ error: "Invalid transaction_type" }, { status: 400 }); } await queryRaw( `UPDATE transactions SET transaction_type = $1 WHERE id = $2`, [transaction_type, transactionId] ); } // payment_method is a property of the transaction itself, not a user override // of extracted data, so it lives on the transactions table. if (payment_method !== undefined) { const VALID_METHODS = ["card", "cash", "bank_transfer", "other"]; if (payment_method !== null && !VALID_METHODS.includes(payment_method)) { return NextResponse.json({ error: "Invalid payment_method" }, { status: 400 }); } await queryRaw( `UPDATE transactions SET payment_method = $1 WHERE id = $2`, [payment_method, transactionId] ); } // category/merchant/notes/my_share_percent/trip_id go through the overrides table const hasOverride = category !== undefined || merchant_normalized !== undefined || notes !== undefined || my_share_percent !== undefined || trip_id !== undefined; if (!hasOverride) { return NextResponse.json({ ok: true }); } const data: Record = { updated_at: new Date() }; if (category !== undefined) data.category_override = category; if (merchant_normalized !== undefined) data.merchant_normalized = merchant_normalized; if (notes !== undefined) data.notes = notes; if (my_share_percent !== undefined) data.my_share_percent = my_share_percent; if (trip_id !== undefined) data.trip_id = trip_id; const override = await prisma.transaction_overrides.upsert({ where: { transaction_id: transactionId }, update: data, create: { transaction_id: transactionId, category_override: category || null, merchant_normalized: merchant_normalized || null, notes: notes || null, my_share_percent: my_share_percent != null ? String(my_share_percent) : null, trip_id: trip_id ?? null, }, }); return NextResponse.json(override); }