Default Exercises Set for new User

This commit is contained in:
AG
2025-12-15 20:02:39 +02:00
parent 003c045621
commit 170e32d36c
7 changed files with 196 additions and 2 deletions

22
.env Normal file
View File

@@ -0,0 +1,22 @@
# Generic
# DEV
DATABASE_URL_DEV="file:./prisma/dev.db"
ADMIN_EMAIL_DEV="admin@gymflow.ai"
ADMIN_PASSWORD_DEV="admin123"
# TEST
DATABASE_URL_TEST="file:./prisma/test.db"
ADMIN_EMAIL_TEST="admin@gymflow.ai"
ADMIN_PASSWORD_TEST="admin123"
# PROD
DATABASE_URL_PROD="file:./prisma/prod.db"
ADMIN_EMAIL_PROD="admin-prod@gymflow.ai"
ADMIN_PASSWORD_PROD="secure-prod-password-change-me"
# Fallback for Prisma CLI (Migrate default)
DATABASE_URL="file:./prisma/dev.db"
GEMINI_API_KEY=AIzaSyC88SeFyFYjvSfTqgvEyr7iqLSvEhuadoE
DEFAULT_EXERCISES_CSV_PATH='default_exercises.csv'

4
.gitignore vendored
View File

@@ -9,13 +9,13 @@ lerna-debug.log*
node_modules node_modules
dist dist
dist-ssr dist-ssr
.env #.env
*.local *.local
server/prisma/dev.db server/prisma/dev.db
test-results/ test-results/
playwright-report/ playwright-report/
.vscode/* #.vscode/*
!.vscode/extensions.json !.vscode/extensions.json
.idea .idea
.DS_Store .DS_Store

13
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"servers": {
"playwright-test": {
"type": "stdio",
"command": "npx",
"args": [
"playwright",
"run-test-mcp-server"
]
}
},
"inputs": []
}

View File

@@ -0,0 +1,43 @@
name,type,bodyWeightPercentage,isUnilateral
Air Squats,BODYWEIGHT,1.0,false
Barbell Row,STRENGTH,0,false
Bench Press,STRENGTH,0,false
Bicep Curl,STRENGTH,0,true
Bulgarian Split-Squat Jumps,BODYWEIGHT,1.0,true
Bulgarian Split-Squats,BODYWEIGHT,1.0,true
Burpees,BODYWEIGHT,1.0,false
Calf Raise,STRENGTH,0,true
Chin-Ups,BODYWEIGHT,1.0,false
Cycling,CARDIO,0,false
Deadlift,STRENGTH,0,false
Dips,BODYWEIGHT,1.0,false
Dumbbell Curl,STRENGTH,0,true
Dumbbell Shoulder Press,STRENGTH,0,true
Face Pull,STRENGTH,0,false
Front Squat,STRENGTH,0,false
Hammer Curl,STRENGTH,0,true
Handstand,BODYWEIGHT,1.0,false
Hip Thrust,STRENGTH,0,false
Jump Rope,CARDIO,0,false
Lat Pulldown,STRENGTH,0,false
Leg Extension,STRENGTH,0,true
Leg Press,STRENGTH,0,false
Lunges,BODYWEIGHT,1.0,true
Mountain Climbers,CARDIO,0,false
Muscle-Up,BODYWEIGHT,1.0,false
Overhead Press,STRENGTH,0,false
Plank,STATIC,0,false
Pull-Ups,BODYWEIGHT,1.0,false
Push-Ups,BODYWEIGHT,0.65,false
Romanian Deadlift,STRENGTH,0,false
Rowing,CARDIO,0,false
Running,CARDIO,0,false
Russian Twist,BODYWEIGHT,0,false
Seated Cable Row,STRENGTH,0,false
Side Plank,STATIC,0,true
Sissy Squats,BODYWEIGHT,1.0,false
Sprint,CARDIO,0,false
Squat,STRENGTH,0,false
Treadmill,CARDIO,0,false
Tricep Extension,STRENGTH,0,false
Wall-Sit,STATIC,0,false
1 name type bodyWeightPercentage isUnilateral
2 Air Squats BODYWEIGHT 1.0 false
3 Barbell Row STRENGTH 0 false
4 Bench Press STRENGTH 0 false
5 Bicep Curl STRENGTH 0 true
6 Bulgarian Split-Squat Jumps BODYWEIGHT 1.0 true
7 Bulgarian Split-Squats BODYWEIGHT 1.0 true
8 Burpees BODYWEIGHT 1.0 false
9 Calf Raise STRENGTH 0 true
10 Chin-Ups BODYWEIGHT 1.0 false
11 Cycling CARDIO 0 false
12 Deadlift STRENGTH 0 false
13 Dips BODYWEIGHT 1.0 false
14 Dumbbell Curl STRENGTH 0 true
15 Dumbbell Shoulder Press STRENGTH 0 true
16 Face Pull STRENGTH 0 false
17 Front Squat STRENGTH 0 false
18 Hammer Curl STRENGTH 0 true
19 Handstand BODYWEIGHT 1.0 false
20 Hip Thrust STRENGTH 0 false
21 Jump Rope CARDIO 0 false
22 Lat Pulldown STRENGTH 0 false
23 Leg Extension STRENGTH 0 true
24 Leg Press STRENGTH 0 false
25 Lunges BODYWEIGHT 1.0 true
26 Mountain Climbers CARDIO 0 false
27 Muscle-Up BODYWEIGHT 1.0 false
28 Overhead Press STRENGTH 0 false
29 Plank STATIC 0 false
30 Pull-Ups BODYWEIGHT 1.0 false
31 Push-Ups BODYWEIGHT 0.65 false
32 Romanian Deadlift STRENGTH 0 false
33 Rowing CARDIO 0 false
34 Running CARDIO 0 false
35 Russian Twist BODYWEIGHT 0 false
36 Seated Cable Row STRENGTH 0 false
37 Side Plank STATIC 0 true
38 Sissy Squats BODYWEIGHT 1.0 false
39 Sprint CARDIO 0 false
40 Squat STRENGTH 0 false
41 Treadmill CARDIO 0 false
42 Tricep Extension STRENGTH 0 false
43 Wall-Sit STATIC 0 false

Binary file not shown.

View File

@@ -1,6 +1,9 @@
import prisma from '../lib/prisma'; import prisma from '../lib/prisma';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
const JWT_SECRET = process.env.JWT_SECRET || 'secret'; const JWT_SECRET = process.env.JWT_SECRET || 'secret';
@@ -64,6 +67,74 @@ export class AuthService {
include: { profile: true } include: { profile: true }
}); });
// Seed default exercises
try {
// Ensure env is loaded (in case server didn't restart)
if (!process.env.DEFAULT_EXERCISES_CSV_PATH) {
dotenv.config({ path: path.resolve(process.cwd(), '.env'), override: true });
if (!process.env.DEFAULT_EXERCISES_CSV_PATH) {
// Try root if CWD is server
dotenv.config({ path: path.resolve(process.cwd(), '../.env'), override: true });
}
}
const csvPath = process.env.DEFAULT_EXERCISES_CSV_PATH;
if (csvPath) {
// ... logic continues
let resolvedPath = path.resolve(process.cwd(), csvPath);
// Try to handle if resolvedPath doesn't exist but relative to root does (if CWD is server)
if (!fs.existsSync(resolvedPath) && !path.isAbsolute(csvPath)) {
const altPath = path.resolve(process.cwd(), '..', csvPath);
if (fs.existsSync(altPath)) {
resolvedPath = altPath;
}
}
if (fs.existsSync(resolvedPath)) {
const content = fs.readFileSync(resolvedPath, 'utf-8');
const lines = content.split(/\r?\n/).filter(l => l.trim().length > 0);
if (lines.length > 1) {
const headers = lines[0].split(',').map(h => h.trim());
const exercisesToCreate = [];
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(',').map(c => c.trim());
if (cols.length < headers.length) continue;
const row: any = {};
headers.forEach((h, idx) => row[h] = cols[idx]);
if (row.name && row.type) {
exercisesToCreate.push({
userId: user.id,
name: row.name,
type: row.type,
bodyWeightPercentage: row.bodyWeightPercentage ? parseFloat(row.bodyWeightPercentage) : 0,
isUnilateral: row.isUnilateral === 'true',
isArchived: false
});
}
}
if (exercisesToCreate.length > 0) {
await prisma.exercise.createMany({
data: exercisesToCreate
});
}
}
} else {
console.warn(`[AuthService] Default exercises CSV configured but not found at: ${resolvedPath}`);
}
}
} catch (error) {
try { fs.appendFileSync(path.join(process.cwd(), 'auth_debug_custom.log'), `[${new Date().toISOString()}] ERROR: ${error}\n`); } catch (e) { }
console.error('[AuthService] Failed to seed default exercises:', error);
// Non-blocking error
}
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET); const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
const { password: _, ...userSafe } = user; const { password: _, ...userSafe } = user;

View File

@@ -0,0 +1,45 @@
import { test, expect } from './fixtures';
import { request as playwrightRequest } from '@playwright/test';
import path from 'path';
import fs from 'fs';
test('Default Exercises Creation', async ({ createUniqueUser }) => {
// 1. Create a user
const user = await createUniqueUser();
// 2. Fetch exercises for the user
// Create authenticated context
const apiContext = await playwrightRequest.newContext({
baseURL: 'http://127.0.0.1:3001',
extraHTTPHeaders: {
'Authorization': `Bearer ${user.token}`
}
});
const exercisesRes = await apiContext.get('/api/exercises');
await expect(exercisesRes).toBeOK();
const responseJson = await exercisesRes.json();
console.log('DEBUG: Fetched exercises response:', JSON.stringify(responseJson, null, 2));
const exercises = responseJson.data;
// 3. Verify default exercises are present
// Checking a subset of influential exercises from the populated list
const expectedNames = ['Bench Press', 'Squat', 'Deadlift', 'Push-Ups', 'Pull-Ups', 'Running', 'Plank', 'Handstand', 'Sprint', 'Bulgarian Split-Squats'];
for (const name of expectedNames) {
const found = exercises.find((e: any) => e.name === name);
expect(found, `Exercise ${name} should exist`).toBeDefined();
}
// 4. Verify properties
const dumbbellCurl = exercises.find((e: any) => e.name === 'Dumbbell Curl');
expect(dumbbellCurl.isUnilateral).toBe(true);
expect(dumbbellCurl.type).toBe('STRENGTH');
const handstand = exercises.find((e: any) => e.name === 'Handstand');
expect(handstand.type).toBe('BODYWEIGHT');
expect(handstand.bodyWeightPercentage).toBe(1.0);
const pushUps = exercises.find((e: any) => e.name === 'Push-Ups');
expect(pushUps.bodyWeightPercentage).toBe(0.65);
});