From dc6a3b111642647afae73a688f80f889b1ee3f36 Mon Sep 17 00:00:00 2001 From: Keysat Date: Sat, 9 May 2026 19:42:45 -0500 Subject: [PATCH] =?UTF-8?q?v1.0.0:5=20=E2=80=94=20remove=20caloriesBurned?= =?UTF-8?q?=20raw-SQL=20workaround?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three exported helpers in lib/prisma.ts (getCaloriesBurned, setCaloriesBurned, getCaloriesBurnedBulk) existed because an early Prisma client generation didn't include the column. Schema and client have been aligned for several releases — the workaround is dead weight. Removed: the helpers from lib/prisma.ts (~30 lines of $queryRawUnsafe / $executeRawUnsafe). Updated callers to use plain caloriesBurned field references: - app/api/workouts/route.ts (GET list + POST create) - app/api/workouts/[id]/route.ts (GET detail + PATCH update) - app/api/settings/export-csv/route.ts (CSV export) All call sites now go through normal type-safe Prisma queries. Net effect for users: zero. Net effect for the codebase: cleaner read paths, stronger TS coverage on caloriesBurned, fewer SQL strings to audit. No schema changes, no migration. Existing /data is untouched. v1.0.0:5 promoted to current; :1, :2, :3, :4 in other. --- .../app/api/settings/export-csv/route.ts | 8 ++--- proof-of-work/app/api/workouts/[id]/route.ts | 30 ++++------------ proof-of-work/app/api/workouts/route.ts | 28 +++------------ proof-of-work/lib/prisma.ts | 34 ------------------- start9/0.4/startos/versions/index.ts | 21 ++++++------ start9/0.4/startos/versions/v1.0.0.5.ts | 31 +++++++++++++++++ 6 files changed, 54 insertions(+), 98 deletions(-) create mode 100644 start9/0.4/startos/versions/v1.0.0.5.ts diff --git a/proof-of-work/app/api/settings/export-csv/route.ts b/proof-of-work/app/api/settings/export-csv/route.ts index 259e893..01ce22c 100644 --- a/proof-of-work/app/api/settings/export-csv/route.ts +++ b/proof-of-work/app/api/settings/export-csv/route.ts @@ -1,6 +1,6 @@ import { getCurrentUser } from "@/lib/auth"; import { getTimestampFileSuffix } from "@/lib/db-file"; -import { getCaloriesBurnedBulk, prisma } from "@/lib/prisma"; +import { prisma } from "@/lib/prisma"; import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; @@ -42,6 +42,7 @@ export async function GET() { notes: true, durationMinutes: true, difficulty: true, + caloriesBurned: true, }, }, }, @@ -51,9 +52,6 @@ export async function GET() { ], }); - const workoutIds = Array.from(new Set(setLogs.map((s) => s.workout.id))); - const caloriesMap = await getCaloriesBurnedBulk(workoutIds); - const header = [ "workoutId", "workoutDate", @@ -92,7 +90,7 @@ export async function GET() { set.workout.notes ?? "", set.workout.durationMinutes ?? "", set.workout.difficulty ?? "", - caloriesMap[set.workout.id] ?? "", + set.workout.caloriesBurned ?? "", set.exerciseId, set.exercise.name, set.setNumber, diff --git a/proof-of-work/app/api/workouts/[id]/route.ts b/proof-of-work/app/api/workouts/[id]/route.ts index e93be76..e466c54 100644 --- a/proof-of-work/app/api/workouts/[id]/route.ts +++ b/proof-of-work/app/api/workouts/[id]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { getCurrentUser } from "@/lib/auth"; -import { prisma, getCaloriesBurned, setCaloriesBurned } from "@/lib/prisma"; +import { prisma } from "@/lib/prisma"; import { z } from "zod"; // GET: Get workout by ID @@ -36,10 +36,7 @@ export async function GET( return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } - // Prisma client doesn't know about caloriesBurned — fetch via raw SQL - const caloriesBurned = await getCaloriesBurned(workout.id); - - return NextResponse.json({ ...workout, caloriesBurned }); + return NextResponse.json(workout); } catch (error) { console.error("Failed to fetch workout:", error); return NextResponse.json( @@ -101,11 +98,6 @@ export async function PATCH( const body = await request.json(); const validated = updateWorkoutSchema.parse(body); - // Extract caloriesBurned separately — handled via raw SQL - const caloriesValue = validated.caloriesBurned; - const hasCaloriesUpdate = validated.caloriesBurned !== undefined; - - // Build the Prisma-compatible workout update data (no caloriesBurned) const workoutData: Record = {}; if (validated.name !== undefined) workoutData.name = validated.name; if (validated.notes !== undefined) workoutData.notes = validated.notes || null; @@ -114,6 +106,8 @@ export async function PATCH( workoutData.durationMinutes = validated.durationMinutes; if (validated.difficulty !== undefined) workoutData.difficulty = validated.difficulty; + if (validated.caloriesBurned !== undefined) + workoutData.caloriesBurned = validated.caloriesBurned; // If sets are provided, do a full replace inside a transaction if (validated.sets) { @@ -164,13 +158,7 @@ export async function PATCH( }); }); - // Update caloriesBurned via raw SQL (outside transaction since Prisma doesn't know this column) - if (hasCaloriesUpdate) { - await setCaloriesBurned(params.id, caloriesValue ?? null); - } - const calories = await getCaloriesBurned(params.id); - - return NextResponse.json({ ...result, caloriesBurned: calories }); + return NextResponse.json(result); } // Metadata-only update @@ -181,11 +169,6 @@ export async function PATCH( }); } - // Update caloriesBurned via raw SQL - if (hasCaloriesUpdate) { - await setCaloriesBurned(params.id, caloriesValue ?? null); - } - const updated = await prisma.workout.findFirst({ where: { id: params.id, deletedAt: null }, include: { @@ -195,9 +178,8 @@ export async function PATCH( }, }, }); - const calories = await getCaloriesBurned(params.id); - return NextResponse.json({ ...updated, caloriesBurned: calories }); + return NextResponse.json(updated); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( diff --git a/proof-of-work/app/api/workouts/route.ts b/proof-of-work/app/api/workouts/route.ts index 595fad2..12a056d 100644 --- a/proof-of-work/app/api/workouts/route.ts +++ b/proof-of-work/app/api/workouts/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { Prisma } from "@prisma/client"; import { getCurrentUser } from "@/lib/auth"; -import { prisma, setCaloriesBurned, getCaloriesBurnedBulk } from "@/lib/prisma"; +import { prisma } from "@/lib/prisma"; // Schema now supports creating empty workouts (just date) or with sets const createWorkoutSchema = z.object({ @@ -90,16 +90,8 @@ export async function GET(request: NextRequest) { prisma.workout.count({ where }), ]); - // Supplement with caloriesBurned from raw SQL - const ids = workouts.map((w) => w.id); - const caloriesMap = await getCaloriesBurnedBulk(ids); - const enriched = workouts.map((w) => ({ - ...w, - caloriesBurned: caloriesMap[w.id] ?? null, - })); - return NextResponse.json({ - data: enriched, + data: workouts, meta: { total, limit, @@ -129,20 +121,13 @@ export async function POST(request: NextRequest) { const workoutDate = validated.date ? new Date(validated.date) : new Date(); - // Extract caloriesBurned — handled via raw SQL after creation - const caloriesValue = validated.caloriesBurned; - - // Note: caloriesBurned was historically handled via raw SQL because - // older Prisma client generations didn't include the column. Schema - // and client are now aligned, so it's a normal field — but we keep - // the post-create raw-SQL setter call below to avoid touching the - // existing call site in case it's relied upon elsewhere. const createData: Prisma.WorkoutCreateInput = { user: { connect: { id: user.id } }, name: validated.name || null, notes: validated.notes, durationMinutes: validated.durationMinutes, difficulty: validated.difficulty, + caloriesBurned: validated.caloriesBurned, date: workoutDate, setLogs: validated.sets.length > 0 @@ -177,12 +162,7 @@ export async function POST(request: NextRequest) { const workout = await prisma.workout.create({ data: createData, include: includeOpts }); - // Set caloriesBurned via raw SQL - if (caloriesValue !== undefined) { - await setCaloriesBurned(workout.id, caloriesValue); - } - - return NextResponse.json({ ...workout, caloriesBurned: caloriesValue ?? null }, { status: 201 }); + return NextResponse.json(workout, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( diff --git a/proof-of-work/lib/prisma.ts b/proof-of-work/lib/prisma.ts index 56323a7..3a62c14 100644 --- a/proof-of-work/lib/prisma.ts +++ b/proof-of-work/lib/prisma.ts @@ -11,37 +11,3 @@ export const prisma = }); if (process.env.NODE_ENV !== "production") global.prisma = prisma; - -/** - * caloriesBurned is in the DB schema but NOT in the generated Prisma client. - * These helpers use raw SQL to read/write it until Prisma client can be regenerated. - */ -export async function getCaloriesBurned(workoutId: string): Promise { - const rows = await prisma.$queryRawUnsafe>( - `SELECT caloriesBurned FROM Workout WHERE id = ?`, - workoutId - ); - return rows[0]?.caloriesBurned ?? null; -} - -export async function setCaloriesBurned(workoutId: string, calories: number | null): Promise { - await prisma.$executeRawUnsafe( - `UPDATE Workout SET caloriesBurned = ? WHERE id = ?`, - calories, - workoutId - ); -} - -export async function getCaloriesBurnedBulk(workoutIds: string[]): Promise> { - if (workoutIds.length === 0) return {}; - const placeholders = workoutIds.map(() => "?").join(","); - const rows = await prisma.$queryRawUnsafe>( - `SELECT id, caloriesBurned FROM Workout WHERE id IN (${placeholders})`, - ...workoutIds - ); - const map: Record = {}; - for (const r of rows) { - map[r.id] = r.caloriesBurned; - } - return map; -} diff --git a/start9/0.4/startos/versions/index.ts b/start9/0.4/startos/versions/index.ts index d1dd639..fb61d46 100644 --- a/start9/0.4/startos/versions/index.ts +++ b/start9/0.4/startos/versions/index.ts @@ -3,25 +3,24 @@ import { v_1_0_0_1 } from './v1.0.0.1' import { v_1_0_0_2 } from './v1.0.0.2' import { v_1_0_0_3 } from './v1.0.0.3' import { v_1_0_0_4 } from './v1.0.0.4' +import { v_1_0_0_5 } from './v1.0.0.5' /** * Version graph for the `proof-of-work` package. * * v1.0.0:1 — initial release, seeded cutover from the legacy * `workout-log` package. - * v1.0.0:2 — CSP fix (reverted the over-strict nonce-based CSP that - * broke first paint in v1.0.0:1). - * v1.0.0:3 — post-cutover seed strip (baked /data snapshot removed - * from the image now that the cutover is verified done). - * v1.0.0:4 — removes the default admin@local / workout123 credentials - * from fresh installs. Operator must run the StartOS Action - * "Set admin credentials" before login is possible. + * v1.0.0:2 — CSP fix. + * v1.0.0:3 — post-cutover seed strip. + * v1.0.0:4 — removes the default admin@local credentials; operator + * must run the StartOS Action to bootstrap the first admin. + * v1.0.0:5 — internal cleanup (removes caloriesBurned raw-SQL + * workaround). No user-facing change. * * StartOS picks `current` as the install target; `other` lists every - * node that can upgrade into `current`. Hosts on v1.0.0:1, :2, or :3 - * upgrade to :4 via no-op up migrations; fresh installs land on :4. + * node that can upgrade into `current`. */ export const versionGraph = VersionGraph.of({ - current: v_1_0_0_4, - other: [v_1_0_0_1, v_1_0_0_2, v_1_0_0_3], + current: v_1_0_0_5, + other: [v_1_0_0_1, v_1_0_0_2, v_1_0_0_3, v_1_0_0_4], }) diff --git a/start9/0.4/startos/versions/v1.0.0.5.ts b/start9/0.4/startos/versions/v1.0.0.5.ts new file mode 100644 index 0000000..ca08c7b --- /dev/null +++ b/start9/0.4/startos/versions/v1.0.0.5.ts @@ -0,0 +1,31 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +/** + * v1.0.0:5 — internal cleanup, no user-facing change. + * + * Removes the `caloriesBurned` raw-SQL workaround from lib/prisma.ts. + * That workaround was a vestige of an early Prisma client generation + * that didn't include the column; schema and client have been aligned + * for several releases. The three exported helpers + * (getCaloriesBurned, setCaloriesBurned, getCaloriesBurnedBulk) and + * every caller now use normal type-safe Prisma queries. + * + * Net effect for users: zero. Net effect for the codebase: ~30 lines + * of $queryRawUnsafe / $executeRawUnsafe deleted, three call sites + * (workouts list, workout detail GET/PATCH, settings/export-csv) + * simplified to plain `caloriesBurned` field references with full + * TS type checking. + * + * No schema changes, no migration, no config changes. + */ +export const v_1_0_0_5 = VersionInfo.of({ + version: '1.0.0:5', + releaseNotes: { + en_US: + 'Internal cleanup: removes the legacy caloriesBurned raw-SQL workaround from lib/prisma.ts and switches every caller to type-safe Prisma queries. No user-facing changes; no migration; existing /data is untouched.', + }, + migrations: { + up: async () => {}, + down: IMPOSSIBLE, + }, +})