v1.0.0:5 — remove caloriesBurned raw-SQL workaround
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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<string, unknown> = {};
|
||||
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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<number | null> {
|
||||
const rows = await prisma.$queryRawUnsafe<Array<{ caloriesBurned: number | null }>>(
|
||||
`SELECT caloriesBurned FROM Workout WHERE id = ?`,
|
||||
workoutId
|
||||
);
|
||||
return rows[0]?.caloriesBurned ?? null;
|
||||
}
|
||||
|
||||
export async function setCaloriesBurned(workoutId: string, calories: number | null): Promise<void> {
|
||||
await prisma.$executeRawUnsafe(
|
||||
`UPDATE Workout SET caloriesBurned = ? WHERE id = ?`,
|
||||
calories,
|
||||
workoutId
|
||||
);
|
||||
}
|
||||
|
||||
export async function getCaloriesBurnedBulk(workoutIds: string[]): Promise<Record<string, number | null>> {
|
||||
if (workoutIds.length === 0) return {};
|
||||
const placeholders = workoutIds.map(() => "?").join(",");
|
||||
const rows = await prisma.$queryRawUnsafe<Array<{ id: string; caloriesBurned: number | null }>>(
|
||||
`SELECT id, caloriesBurned FROM Workout WHERE id IN (${placeholders})`,
|
||||
...workoutIds
|
||||
);
|
||||
const map: Record<string, number | null> = {};
|
||||
for (const r of rows) {
|
||||
map[r.id] = r.caloriesBurned;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user