Code maintainability fixes
This commit is contained in:
783
server/package-lock.json
generated
783
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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.
@@ -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;
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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' });
|
||||
|
||||
42
server/src/scripts/migratePlans.ts
Normal file
42
server/src/scripts/migratePlans.ts
Normal 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();
|
||||
});
|
||||
Reference in New Issue
Block a user