From 1015e24e69372d6fa664fcf74274131fa929963e Mon Sep 17 00:00:00 2001 From: Vijayakanth Manoharan Date: Mon, 23 Mar 2026 17:20:38 -0400 Subject: [PATCH] Add dashboard error handling, seed data, and Docker host Ollama wiring - Wrap getDashboardSnapshot in try/catch; return JSON 500 instead of crashing - Add prisma/seed.ts with realistic Feb + Mar 2026 data: biweekly $2,850 pay schedule, $2,430 rent, expenses across all 8 categories - Update Dockerfile and backup route for host Ollama runtime Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 4 ++ next-env.d.ts | 2 +- prisma/seed.ts | 87 ++++++++++++++++++++++++++++++++ src/app/backup/database/route.ts | 39 ++++++++++---- src/app/dashboard/route.ts | 9 +++- 5 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 prisma/seed.ts diff --git a/Dockerfile b/Dockerfile index 2ff0bb6..485cffc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ FROM node:22-bookworm-slim AS builder WORKDIR /app +RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* + COPY package*.json ./ COPY prisma ./prisma RUN npm ci @@ -13,6 +15,8 @@ WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 +RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* + COPY --from=builder /app /app EXPOSE 3000 diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..727c401 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,87 @@ +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +async function main() { + // Clear all data + await prisma.monthlyInsight.deleteMany() + await prisma.expense.deleteMany() + await prisma.paycheck.deleteMany() + await prisma.paySchedule.deleteMany() + + // ── Pay schedule ──────────────────────────────────────────────── + // Biweekly $2,850. Anchor on 2026-03-14 (last received paycheck). + // This projects 2026-03-28 as the upcoming payday this weekend. + await prisma.paySchedule.create({ + data: { + amountCents: 285000, + anchorDate: '2026-03-14', + active: true, + }, + }) + + // ── February 2026 paychecks ────────────────────────────────────── + // Biweekly back from 2026-03-14: Feb 14, Feb 28 (same cycle −2 periods, −1 period) + await prisma.paycheck.createMany({ + data: [ + { payDate: '2026-02-14', amountCents: 285000 }, + { payDate: '2026-02-28', amountCents: 285000 }, + ], + }) + + // ── March 2026 paychecks ───────────────────────────────────────── + // March 14 received; March 28 is upcoming → covered by projected schedule + await prisma.paycheck.create({ + data: { payDate: '2026-03-14', amountCents: 285000 }, + }) + + // ── February 2026 expenses ─────────────────────────────────────── + await prisma.expense.createMany({ + data: [ + { date: '2026-02-02', title: 'Rent', amountCents: 243000, category: 'RENT' }, + { date: '2026-02-04', title: 'Grocery Run', amountCents: 11500, category: 'FOOD' }, + { date: '2026-02-07', title: 'Coffee & Snacks', amountCents: 3200, category: 'FOOD' }, + { date: '2026-02-09', title: 'Electric Bill', amountCents: 9200, category: 'BILLS' }, + { date: '2026-02-09', title: 'Internet', amountCents: 6500, category: 'BILLS' }, + { date: '2026-02-11', title: 'Gas Station', amountCents: 5500, category: 'TRANSPORT' }, + { date: '2026-02-13', title: 'Pharmacy', amountCents: 4200, category: 'HEALTH' }, + { date: '2026-02-15', title: 'Movie Tickets', amountCents: 2800, category: 'ENTERTAINMENT' }, + { date: '2026-02-17', title: 'Grocery Run', amountCents: 9800, category: 'FOOD' }, + { date: '2026-02-19', title: 'Gym Membership', amountCents: 5000, category: 'HEALTH' }, + { date: '2026-02-21', title: 'Dinner Out', amountCents: 6800, category: 'FOOD' }, + { date: '2026-02-22', title: 'New Jeans', amountCents: 12000, category: 'SHOPPING' }, + { date: '2026-02-24', title: 'Rideshare', amountCents: 2200, category: 'TRANSPORT' }, + { date: '2026-02-26', title: 'Phone Bill', amountCents: 6500, category: 'BILLS' }, + { date: '2026-02-28', title: 'Misc Supplies', amountCents: 1800, category: 'MISC' }, + ], + }) + + // ── March 2026 expenses (through March 23 — month is ongoing) ─── + await prisma.expense.createMany({ + data: [ + { date: '2026-03-02', title: 'Rent', amountCents: 243000, category: 'RENT' }, + { date: '2026-03-04', title: 'Grocery Run', amountCents: 10800, category: 'FOOD' }, + { date: '2026-03-06', title: 'Coffee & Snacks', amountCents: 2900, category: 'FOOD' }, + { date: '2026-03-08', title: 'Gas Station', amountCents: 6000, category: 'TRANSPORT' }, + { date: '2026-03-09', title: 'Electric Bill', amountCents: 8800, category: 'BILLS' }, + { date: '2026-03-11', title: 'Streaming Services', amountCents: 5500, category: 'BILLS' }, + { date: '2026-03-12', title: 'Lunch Out', amountCents: 4500, category: 'FOOD' }, + { date: '2026-03-14', title: 'Pharmacy', amountCents: 3500, category: 'HEALTH' }, + { date: '2026-03-16', title: 'Grocery Run', amountCents: 9500, category: 'FOOD' }, + { date: '2026-03-17', title: 'Cinema', amountCents: 3200, category: 'ENTERTAINMENT' }, + { date: '2026-03-19', title: 'Clothing Online', amountCents: 7500, category: 'SHOPPING' }, + { date: '2026-03-20', title: 'Phone Bill', amountCents: 6500, category: 'BILLS' }, + { date: '2026-03-21', title: 'Rideshare', amountCents: 1800, category: 'TRANSPORT' }, + { date: '2026-03-23', title: 'Dinner Out', amountCents: 5500, category: 'FOOD' }, + ], + }) + + console.log('✓ Cleared old data') + console.log('✓ Pay schedule: biweekly $2,850 (anchor 2026-03-14, next: 2026-03-28)') + console.log('✓ Paychecks: Feb 14, Feb 28, Mar 14 → Mar 28 projected') + console.log('✓ Expenses: Feb 2026 (15 items) + Mar 2026 (14 items, through Mar 23)') +} + +main() + .catch(console.error) + .finally(() => prisma.$disconnect()) diff --git a/src/app/backup/database/route.ts b/src/app/backup/database/route.ts index eecc870..5d58214 100644 --- a/src/app/backup/database/route.ts +++ b/src/app/backup/database/route.ts @@ -1,19 +1,36 @@ import { readFile } from "node:fs/promises"; +import path from "node:path"; import { NextResponse } from "next/server"; -import { getDatabaseBackupFileName, resolveSqliteDatabasePath } from "@/lib/storage"; +import { getDatabaseBackupFileName } from "@/lib/storage"; + +function resolveDbPath(): string { + const dbUrl = process.env.DATABASE_URL || "file:./prisma/dev.db"; + const rawPath = dbUrl.slice("file:".length); + if (path.isAbsolute(rawPath)) { + return rawPath; + } + return path.resolve(process.cwd(), rawPath); +} export async function GET() { - const filePath = resolveSqliteDatabasePath(); - const file = await readFile(filePath); + try { + const dbPath = resolveDbPath(); + console.error("[backup] resolved path:", dbPath); + const file = await readFile(dbPath); + console.error("[backup] read bytes:", file.length); - return new NextResponse(file, { - status: 200, - headers: { - "Content-Type": "application/x-sqlite3", - "Content-Disposition": `attachment; filename="${getDatabaseBackupFileName()}"`, - "Cache-Control": "no-store", - }, - }); + return new NextResponse(file, { + status: 200, + headers: { + "Content-Type": "application/x-sqlite3", + "Content-Disposition": `attachment; filename="${getDatabaseBackupFileName()}"`, + "Cache-Control": "no-store", + }, + }); + } catch (error) { + console.error("[backup] error:", error); + return new NextResponse(String(error), { status: 500 }); + } } diff --git a/src/app/dashboard/route.ts b/src/app/dashboard/route.ts index cb0aeb0..1ada9ec 100644 --- a/src/app/dashboard/route.ts +++ b/src/app/dashboard/route.ts @@ -16,6 +16,11 @@ export async function GET(request: Request) { ); } - const dashboard = await getDashboardSnapshot(parsed.data.month); - return NextResponse.json(dashboard); + try { + const dashboard = await getDashboardSnapshot(parsed.data.month); + return NextResponse.json(dashboard); + } catch (error) { + console.error("[dashboard] snapshot error:", error); + return NextResponse.json({ error: "Could not load the dashboard." }, { status: 500 }); + } }