Bug fixes and refactoring

This commit is contained in:
AG
2025-12-06 16:14:28 +02:00
parent 4106f3b783
commit 890f4f0958
13 changed files with 188 additions and 214 deletions

View File

@@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
<div id='root'></div>
</body>
</html>
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,UEsDBBQAAAgIAFsGgluswruJZgEAADIDAAAZAAAAYzM0ZWJiZWIzMjllNzhjNTQ4NWYuanNvbs2RMU/DMBCF/0p0sxO1btOm3lBZKiEWKiFBMrjOJTV17Mi+AFXV/45isiDBgFjYzrbefe89X6DRBnc1CFCLJR4OeFjwDa4LlS+LvAEW3+9lhyBAKtKvmAYMQTub1miQMDWubbFOA1IWelQZBWBAGCiAeL7E6cf96WbVcOT1Zr7iy4UqsDgs1CjXZEbiNktuIjR5+IQmaXIbscldxCYPSMCg9+4FFU0+1dG7Tg8dMDBOSdLOgrjEJL9LYbRFEGsGypmhsyCKK4N68NPKGQNpraN4nNKe+5HR6PcOv8ffirLculrbtizbc9cY91aWsa6y/Ju1a8WAZDsaqRi4gZSLdYST7nusx5okHUE8w263y5JH509uoGTvpTpp20LFwGMYzPRxX3IGkp72Ou7jM56nc57O5nvORT4TeZ7N+fIJRj358yTAfnKC3js/zYEkDeGLqf9WoSSS6tihjT1U45U7gWikCXitrh9QSwMEFAAACAgAWwaCW5FEcBSjAQAAcgMAAAsAAAByZXBvcnQuanNvbq2ST2+cMBDFvwqas1mFPwusb1VyWanqJZF6CHvwmoF11mBkD21WiO9e2RA1lZJD1fo0T/L49+aNZ+iRRCNIAJ9BSJqE/m7sFa0DniwMHAlLT6pH4ElZ5EV6yPdZXlUMmskKUmYAnhR5usuS8rCe8i5l0CqNDvjzHKpjAxxkluP5jOcsPWBZyX1e7VtYb34THuD56gfGDp1TZogb1EgYa9N12MQOaedGlDtywIDQ0fq+rz59Pz4UbYppc0iKNM9khdU5k75dkfbE+130JUCjxxUaxdFDwEZfAzZ6RAIGozUvKGnzKS/W9GrqgYE2ckthnfTvptBqQOAlA2n01A/Aq+V9sHcMxDAYCnKb9jZ6Rqtee/wY/8Dr+t40aujqurv1rTY/6zrEVdf/Zm05MSDReSMnBmYiaUIc7qrGERsfk6AL8Gc4Ho+7yP8jM1H0ZIW8qqED33QF3grtkIFFN+lth4JIyEuPQ9Cn5eRRjoSXM5AhoYEnDPB1REnYhGim4Q/ZanG9herNj+/wQLITBvPvtug5v/f432kM0Fpj36IatwXOy/ILUEsBAj8DFAAACAgAWwaCW6zCu4lmAQAAMgMAABkAAAAAAAAAAAAAALSBAAAAAGMzNGViYmViMzI5ZTc4YzU0ODVmLmpzb25QSwECPwMUAAAICABbBoJbkURwFKMBAAByAwAACwAAAAAAAAAAAAAAtIGdAQAAcmVwb3J0Lmpzb25QSwUGAAAAAAIAAgCAAAAAaQMAAAAA</script>
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>

View File

@@ -11,6 +11,11 @@ export default defineConfig({
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
webServer: {
command: 'npm run dev:full',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
projects: [
{
name: 'chromium',

125
server/package-lock.json generated
View File

@@ -13,7 +13,7 @@
"@prisma/client": "^7.1.0",
"@types/better-sqlite3": "^7.6.13",
"bcryptjs": "3.0.3",
"better-sqlite3": "^12.5.0",
"better-sqlite3": "^11.0.0",
"cors": "2.8.5",
"dotenv": "17.2.3",
"express": "5.1.0",
@@ -182,6 +182,20 @@
"better-sqlite3": "^12.4.5"
}
},
"node_modules/@prisma/adapter-better-sqlite3/node_modules/better-sqlite3": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz",
"integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
},
"engines": {
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
}
},
"node_modules/@prisma/client": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.1.0.tgz",
@@ -434,15 +448,15 @@
}
},
"node_modules/@types/express": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz",
"integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^1"
"@types/serve-static": "^2"
}
},
"node_modules/@types/express-serve-static-core": {
@@ -476,13 +490,6 @@
"@types/node": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -535,25 +542,13 @@
}
},
"node_modules/@types/serve-static": {
"version": "1.15.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
"integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
"@types/send": "<1"
}
},
"node_modules/@types/serve-static/node_modules/@types/send": {
"version": "0.17.6",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
"integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
@@ -671,17 +666,14 @@
}
},
"node_modules/better-sqlite3": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz",
"integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==",
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
},
"engines": {
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
}
},
"node_modules/binary-extensions": {
@@ -1404,9 +1396,9 @@
}
},
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
@@ -1417,7 +1409,11 @@
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/foreground-child": {
@@ -1672,28 +1668,23 @@
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-status-codes": {
@@ -2046,15 +2037,19 @@
}
},
"node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/mimic-response": {
@@ -2536,15 +2531,15 @@
}
},
"node_modules/raw-body": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.7.0",
"unpipe": "1.0.0"
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"

View File

@@ -14,7 +14,7 @@
"@prisma/client": "^7.1.0",
"@types/better-sqlite3": "^7.6.13",
"bcryptjs": "3.0.3",
"better-sqlite3": "^12.5.0",
"better-sqlite3": "^11.0.0",
"cors": "2.8.5",
"dotenv": "17.2.3",
"express": "5.1.0",

BIN
server/prisma/prisma/dev.db Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,118 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
role String @default("USER") // USER, ADMIN
isFirstLogin Boolean @default(true)
isBlocked Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile UserProfile?
sessions WorkoutSession[]
exercises Exercise[]
plans WorkoutPlan[]
weightRecords BodyWeightRecord[]
}
model BodyWeightRecord {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
weight Float
date DateTime @default(now())
dateStr String // YYYY-MM-DD for unique constraint
@@unique([userId, dateStr])
}
model UserProfile {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
weight Float?
height Float?
gender String?
birthDate DateTime?
language String? @default("en")
}
model Exercise {
id String @id @default(uuid())
userId String? // Null means system default
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
type String // STRENGTH, CARDIO, BODYWEIGHT, STATIC
bodyWeightPercentage Float? @default(0)
isArchived Boolean @default(false)
isUnilateral Boolean @default(false)
sets WorkoutSet[]
planExercises PlanExercise[]
}
model WorkoutSession {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
startTime DateTime
endTime DateTime?
userBodyWeight Float?
note String?
planId String?
planName String?
type String @default("STANDARD") // STANDARD, QUICK_LOG
sets WorkoutSet[]
}
model WorkoutSet {
id String @id @default(uuid())
sessionId String
session WorkoutSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
exerciseId String
exercise Exercise @relation(fields: [exerciseId], references: [id])
order Int
weight Float?
reps Int?
distanceMeters Float?
durationSeconds Int?
height Float?
bodyWeightPercentage Float?
completed Boolean @default(true)
side String? // LEFT, RIGHT, or null for bilateral
timestamp DateTime @default(now())
}
model WorkoutPlan {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
description String?
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

@@ -1,3 +1,4 @@
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import authRoutes from './routes/auth';

View File

@@ -1,7 +1,5 @@
import { PrismaClient } from '@prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import BetterSqlite3 from 'better-sqlite3';
import path from 'path';
// Ensure env vars are loaded
import 'dotenv/config';
@@ -18,6 +16,7 @@ if (!dbUrl) {
throw new Error("DATABASE_URL environment variable is not set. Please check your .env file.");
}
console.log('Initializing Prisma Adapter with URL:', dbUrl);
const adapter = new PrismaBetterSqlite3({ url: dbUrl });
const prisma =

View File

@@ -57,6 +57,7 @@ router.post('/login', async (req, res) => {
res.json({ success: true, user: userSafe, token });
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Server error' });
}
});

View File

@@ -1,26 +0,0 @@
import { PrismaClient } from '@prisma/client';
import fs from 'fs';
import path from 'path';
const prisma = new PrismaClient();
async function backup() {
try {
console.log('Starting backup...');
// Backup Quick Log sessions and their sets (formerly SporadicSets)
const quickLogSessions = await prisma.workoutSession.findMany({
where: { type: 'QUICK_LOG' },
include: { sets: true }
});
const backupPath = path.join(__dirname, '../../sporadic_backup.json');
fs.writeFileSync(backupPath, JSON.stringify(quickLogSessions, null, 2));
console.log(`Backed up ${quickLogSessions.length} quick log sessions to ${backupPath}`);
} catch (error) {
console.error('Backup failed:', error);
} finally {
await prisma.$disconnect();
}
}
backup();

View File

@@ -1,42 +0,0 @@
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();
});

View File

@@ -1,77 +0,0 @@
import { PrismaClient } from '@prisma/client';
import fs from 'fs';
import path from 'path';
const prisma = new PrismaClient();
async function restore() {
try {
const backupPath = path.join(__dirname, '../../sporadic_backup.json');
if (!fs.existsSync(backupPath)) {
console.error('Backup file not found!');
return;
}
const sporadicSets = JSON.parse(fs.readFileSync(backupPath, 'utf-8'));
console.log(`Found ${sporadicSets.length} sporadic sets to restore.`);
for (const set of sporadicSets) {
const date = new Date(set.timestamp);
const startOfDay = new Date(date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
// Find or create a QUICK_LOG session for this day
let session = await prisma.workoutSession.findFirst({
where: {
userId: set.userId,
type: 'QUICK_LOG',
startTime: {
gte: startOfDay,
lte: endOfDay
}
}
});
if (!session) {
session = await prisma.workoutSession.create({
data: {
userId: set.userId,
startTime: startOfDay, // Use start of day as session start
type: 'QUICK_LOG',
note: 'Daily Quick Log'
}
});
console.log(`Created new QUICK_LOG session for ${startOfDay.toISOString()}`);
}
// Create the WorkoutSet
await prisma.workoutSet.create({
data: {
sessionId: session.id,
exerciseId: set.exerciseId,
order: 0, // Order doesn't matter much for sporadic sets, or we could increment
weight: set.weight,
reps: set.reps,
distanceMeters: set.distanceMeters,
durationSeconds: set.durationSeconds,
height: set.height,
bodyWeightPercentage: set.bodyWeightPercentage,
side: set.side,
timestamp: new Date(set.timestamp),
completed: true
}
});
}
console.log('Restoration complete!');
} catch (error) {
console.error('Restoration failed:', error);
} finally {
await prisma.$disconnect();
}
}
restore();