Code maintainability fixes

This commit is contained in:
AG
2025-12-06 11:32:40 +02:00
parent a13ef9f479
commit 4106f3b783
23 changed files with 1775 additions and 796 deletions

783
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@prisma/adapter-better-sqlite3": "^7.1.0",
"@prisma/client": "^6.19.0",
"@prisma/client": "^7.1.0",
"@types/better-sqlite3": "^7.6.13",
"bcryptjs": "3.0.3",
"better-sqlite3": "^12.5.0",
@@ -28,8 +28,8 @@
"@types/jsonwebtoken": "*",
"@types/node": "*",
"nodemon": "*",
"prisma": "*",
"prisma": "^7.1.0",
"ts-node": "*",
"typescript": "*"
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,29 @@
-- CreateTable
CREATE TABLE "PlanExercise" (
"id" TEXT NOT NULL PRIMARY KEY,
"planId" TEXT NOT NULL,
"exerciseId" TEXT NOT NULL,
"order" INTEGER NOT NULL,
"isWeighted" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "PlanExercise_planId_fkey" FOREIGN KEY ("planId") REFERENCES "WorkoutPlan" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "PlanExercise_exerciseId_fkey" FOREIGN KEY ("exerciseId") REFERENCES "Exercise" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_WorkoutPlan" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"exercises" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "WorkoutPlan_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_WorkoutPlan" ("createdAt", "description", "exercises", "id", "name", "updatedAt", "userId") SELECT "createdAt", "description", "exercises", "id", "name", "updatedAt", "userId" FROM "WorkoutPlan";
DROP TABLE "WorkoutPlan";
ALTER TABLE "new_WorkoutPlan" RENAME TO "WorkoutPlan";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -7,7 +7,6 @@ generator client {
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
@@ -60,6 +59,7 @@ model Exercise {
isUnilateral Boolean @default(false)
sets WorkoutSet[]
planExercises PlanExercise[]
}
model WorkoutSession {
@@ -102,8 +102,19 @@ model WorkoutPlan {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
description String?
exercises String // JSON string of exercise IDs
exercises String? // JSON string of exercise IDs (Deprecated, to be removed)
planExercises PlanExercise[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model PlanExercise {
id String @id @default(uuid())
planId String
plan WorkoutPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
exerciseId String
exercise Exercise @relation(fields: [exerciseId], references: [id])
order Int
isWeighted Boolean @default(false)
}

View File

@@ -25,12 +25,26 @@ router.get('/', async (req: any, res) => {
try {
const userId = req.user.userId;
const plans = await prisma.workoutPlan.findMany({
where: { userId }
where: { userId },
include: {
planExercises: {
include: { exercise: true },
orderBy: { order: 'asc' }
}
},
orderBy: { createdAt: 'desc' }
});
const mappedPlans = plans.map((p: any) => ({
...p,
steps: p.exercises ? JSON.parse(p.exercises) : []
steps: p.planExercises.map((pe: any) => ({
id: pe.id,
exerciseId: pe.exerciseId,
exerciseName: pe.exercise.name,
exerciseType: pe.exercise.type,
isWeighted: pe.isWeighted,
// Add default properties if needed by PlannedSet interface
}))
}));
res.json(mappedPlans);
@@ -46,28 +60,71 @@ router.post('/', async (req: any, res) => {
const userId = req.user.userId;
const { id, name, description, steps } = req.body;
const exercisesJson = JSON.stringify(steps || []);
// Steps array contains PlannedSet items
// We need to transact: create/update plan, then replace exercises
const existing = await prisma.workoutPlan.findUnique({ where: { id } });
await prisma.$transaction(async (tx) => {
// Upsert plan
let plan = await tx.workoutPlan.findUnique({ where: { id } });
if (existing) {
const updated = await prisma.workoutPlan.update({
where: { id },
data: { name, description, exercises: exercisesJson }
});
res.json({ ...updated, steps: steps || [] });
} else {
const created = await prisma.workoutPlan.create({
data: {
id,
userId,
name,
description,
exercises: exercisesJson
if (plan) {
await tx.workoutPlan.update({
where: { id },
data: { name, description }
});
// Delete existing plan exercises
await tx.planExercise.deleteMany({ where: { planId: id } });
} else {
await tx.workoutPlan.create({
data: {
id,
userId,
name,
description
}
});
}
// Create new plan exercises
if (steps && steps.length > 0) {
await tx.planExercise.createMany({
data: steps.map((step: any, index: number) => ({
planId: id,
exerciseId: step.exerciseId,
order: index,
isWeighted: step.isWeighted || false
}))
});
}
});
// Return the updated plan structure
// Since we just saved it, we can mirror back what was sent or re-fetch.
// Re-fetching ensures DB state consistency.
const savedPlan = await prisma.workoutPlan.findUnique({
where: { id },
include: {
planExercises: {
include: { exercise: true },
orderBy: { order: 'asc' }
}
});
res.json({ ...created, steps: steps || [] });
}
}
});
if (!savedPlan) throw new Error("Plan failed to save");
const mappedPlan = {
...savedPlan,
steps: savedPlan.planExercises.map((pe: any) => ({
id: pe.id,
exerciseId: pe.exerciseId,
exerciseName: pe.exercise.name,
exerciseType: pe.exercise.type,
isWeighted: pe.isWeighted
}))
};
res.json(mappedPlan);
} catch (error) {
console.error('Error saving plan:', error);
res.status(500).json({ error: 'Server error' });

View File

@@ -0,0 +1,42 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function migrate() {
console.log('Starting migration...');
const plans = await prisma.workoutPlan.findMany();
console.log(`Found ${plans.length} plans.`);
for (const plan of plans) {
if (plan.exercises) {
try {
const steps = JSON.parse(plan.exercises);
console.log(`Migrating plan ${plan.name} (${plan.id}) with ${steps.length} steps.`);
let order = 0;
for (const step of steps) {
await prisma.planExercise.create({
data: {
planId: plan.id,
exerciseId: step.exerciseId,
order: order++,
isWeighted: step.isWeighted || false
}
});
}
} catch (e) {
console.error(`Error parsing JSON for plan ${plan.id}:`, e);
}
}
}
console.log('Migration complete.');
}
migrate()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});