dc6a3b1116
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.
131 lines
3.2 KiB
TypeScript
131 lines
3.2 KiB
TypeScript
import { getCurrentUser } from "@/lib/auth";
|
|
import { getTimestampFileSuffix } from "@/lib/db-file";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { NextResponse } from "next/server";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
function csvCell(value: unknown): string {
|
|
if (value === null || value === undefined) return "";
|
|
const text = String(value);
|
|
if (text.includes(",") || text.includes("\"") || text.includes("\n")) {
|
|
return `"${text.replace(/"/g, "\"\"")}"`;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* GET /api/settings/export-csv
|
|
* Exports workout + set-level rows to CSV for offline backup.
|
|
*/
|
|
export async function GET() {
|
|
try {
|
|
const user = await getCurrentUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const setLogs = await prisma.setLog.findMany({
|
|
where: {
|
|
workout: {
|
|
userId: user.id,
|
|
deletedAt: null,
|
|
},
|
|
},
|
|
include: {
|
|
exercise: true,
|
|
workout: {
|
|
select: {
|
|
id: true,
|
|
date: true,
|
|
name: true,
|
|
notes: true,
|
|
durationMinutes: true,
|
|
difficulty: true,
|
|
caloriesBurned: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: [
|
|
{ workout: { date: "desc" } },
|
|
{ setNumber: "asc" },
|
|
],
|
|
});
|
|
|
|
const header = [
|
|
"workoutId",
|
|
"workoutDate",
|
|
"workoutName",
|
|
"workoutNotes",
|
|
"workoutDurationMinutes",
|
|
"workoutDifficulty",
|
|
"workoutCaloriesBurned",
|
|
"exerciseId",
|
|
"exerciseName",
|
|
"setNumber",
|
|
"reps",
|
|
"weight",
|
|
"weightUnit",
|
|
"durationMinutes",
|
|
"distance",
|
|
"distanceUnit",
|
|
"setCalories",
|
|
"rpe",
|
|
"setNotes",
|
|
"customMetricsJson",
|
|
];
|
|
|
|
const lines = [header.join(",")];
|
|
|
|
for (const set of setLogs) {
|
|
const durationMinutes =
|
|
typeof set.durationSeconds === "number"
|
|
? (set.durationSeconds / 60).toFixed(2)
|
|
: "";
|
|
|
|
const row = [
|
|
set.workout.id,
|
|
set.workout.date.toISOString(),
|
|
set.workout.name ?? "",
|
|
set.workout.notes ?? "",
|
|
set.workout.durationMinutes ?? "",
|
|
set.workout.difficulty ?? "",
|
|
set.workout.caloriesBurned ?? "",
|
|
set.exerciseId,
|
|
set.exercise.name,
|
|
set.setNumber,
|
|
set.reps ?? "",
|
|
set.weight ?? "",
|
|
set.weightUnit ?? "",
|
|
durationMinutes,
|
|
set.distance ?? "",
|
|
set.distanceUnit ?? "",
|
|
set.calories ?? "",
|
|
set.rpe ?? "",
|
|
set.notes ?? "",
|
|
set.customMetrics ?? "",
|
|
];
|
|
|
|
lines.push(row.map(csvCell).join(","));
|
|
}
|
|
|
|
const csv = lines.join("\n");
|
|
const fileName = `proof-of-work-export-${getTimestampFileSuffix()}.csv`;
|
|
|
|
return new NextResponse(csv, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/csv; charset=utf-8",
|
|
"Content-Disposition": `attachment; filename="${fileName}"`,
|
|
"Cache-Control": "no-store",
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("CSV export error:", error);
|
|
return NextResponse.json(
|
|
{ error: "Failed to export CSV" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|