Default exercises are created in selected language. Initial GUI added

This commit is contained in:
AG
2025-12-18 22:16:39 +02:00
parent 78d4a10f33
commit abffb52af1
12 changed files with 332 additions and 117 deletions

View File

@@ -1,43 +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
name,name_ru,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
Can't render this file because it contains an unexpected character in line 18 and column 30.

Binary file not shown.

View File

@@ -83,6 +83,21 @@ export class AuthController {
}
}
static async initializeAccount(req: any, res: Response) {
try {
const userId = req.user.userId;
const { language } = req.body;
if (!language) {
return sendError(res, 'Language is required', 400);
}
const user = await AuthService.initializeUser(userId, language);
return sendSuccess(res, { user });
} catch (error: any) {
logger.error('Error in initializeAccount', { error });
return sendError(res, error.message || 'Server error', 500);
}
}
static async getAllUsers(req: any, res: Response) {
try {
if (req.user.role !== 'ADMIN') {

View File

@@ -65,7 +65,7 @@ async function ensureAdminUser() {
console.info(` Admin user exists but with different email: ${existingAdmin.email}. Expected: ${adminEmail}`);
}
// Even if admin exists, ensure exercises are seeded (will skip if already has them)
await AuthService.seedDefaultExercises(existingAdmin.id);
await AuthService.seedDefaultExercises(existingAdmin.id, 'en');
await prisma.$disconnect();
return;
}
@@ -77,12 +77,12 @@ async function ensureAdminUser() {
email: adminEmail,
password: hashed,
role: 'ADMIN',
profile: { create: { weight: 70 } },
profile: { create: { weight: 70, language: 'en' } },
},
});
// Seed exercises for new admin
await AuthService.seedDefaultExercises(admin.id);
await AuthService.seedDefaultExercises(admin.id, 'en');
console.info(`✅ Admin user created and exercises seeded (email: ${adminEmail})`);
await prisma.$disconnect();

View File

@@ -16,6 +16,7 @@ router.use(authenticateToken); // Standard middleware now
router.get('/me', AuthController.getCurrentUser);
router.post('/change-password', validate(changePasswordSchema), AuthController.changePassword);
router.patch('/profile', validate(updateProfileSchema), AuthController.updateProfile);
router.post('/initialize', AuthController.initializeAccount);
// Admin routes
router.get('/users', AuthController.getAllUsers);

View File

@@ -67,17 +67,13 @@ export class AuthService {
include: { profile: true }
});
// Seed default exercises
// Seed default exercises
await this.seedDefaultExercises(user.id);
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
const { password: _, ...userSafe } = user;
return { user: userSafe, token };
}
static async seedDefaultExercises(userId: string) {
static async seedDefaultExercises(userId: string, language: string = 'en') {
try {
// Ensure env is loaded from root (in case server didn't restart)
if (!process.env.DEFAULT_EXERCISES_CSV_PATH) {
@@ -110,6 +106,8 @@ export class AuthService {
const headers = lines[0].split(',').map(h => h.trim());
const exercisesToCreate = [];
const nameColumn = language === 'ru' ? 'name_ru' : 'name';
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(',').map(c => c.trim());
if (cols.length < headers.length) continue;
@@ -117,10 +115,12 @@ export class AuthService {
const row: any = {};
headers.forEach((h, idx) => row[h] = cols[idx]);
if (row.name && row.type) {
const exerciseName = row[nameColumn] || row['name'];
if (exerciseName && row.type) {
exercisesToCreate.push({
userId,
name: row.name,
name: exerciseName,
type: row.type,
bodyWeightPercentage: row.bodyWeightPercentage ? parseFloat(row.bodyWeightPercentage) : 0,
isUnilateral: row.isUnilateral === 'true',
@@ -153,14 +153,34 @@ export class AuthService {
}
}
static async initializeUser(userId: string, language: string) {
// Update profile language
await prisma.userProfile.upsert({
where: { userId },
update: { language },
create: { userId, language, weight: 70 }
});
// Seed exercises in that language
await this.seedDefaultExercises(userId, language);
// Mark as first login done
await prisma.user.update({
where: { id: userId },
data: { isFirstLogin: false }
});
// Return updated user
return this.getUser(userId);
}
static async changePassword(userId: string, newPassword: string) {
const hashed = await bcrypt.hash(newPassword, 10);
await prisma.user.update({
where: { id: userId },
data: {
password: hashed,
isFirstLogin: false
password: hashed
}
});
}