NAS deployment fixed

This commit is contained in:
aodulov
2025-12-18 13:03:12 +02:00
parent 97b4e5de32
commit e9aec9a65d
14 changed files with 278 additions and 76 deletions

View File

@@ -1,22 +0,0 @@
# 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"

View File

@@ -4,17 +4,17 @@
"description": "Backend for GymFlow AI",
"main": "src/index.ts",
"scripts": {
"start": "npm run start:prod",
"start:prod": "cross-env APP_MODE=prod DATABASE_URL=file:./prod.db npx prisma db push && cross-env APP_MODE=prod DATABASE_URL_PROD=file:./prod.db ts-node-dev -r dotenv/config --respawn --transpile-only src/index.ts",
"start:test": "cross-env APP_MODE=test DATABASE_URL=file:./test.db DATABASE_URL_TEST=file:./test.db npx prisma db push --accept-data-loss && cross-env APP_MODE=test DATABASE_URL_TEST=file:./test.db ts-node-dev -r dotenv/config --respawn --transpile-only src/index.ts",
"dev": "cross-env APP_MODE=dev ts-node-dev -r dotenv/config --respawn --transpile-only src/index.ts",
"start": "node dist/index.js",
"start:prod": "node dist/index.js",
"start:dev": "cross-env APP_MODE=dev ts-node-dev -r dotenv/config --respawn --transpile-only src/index.ts",
"dev": "npm run start:dev",
"build": "tsc",
"migrate:deploy": "npx prisma migrate deploy"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@prisma/adapter-better-sqlite3": "^7.1.0",
"@prisma/client": "^7.1.0",
"@prisma/adapter-better-sqlite3": "^7.2.0",
"@prisma/client": "^7.2.0",
"@types/better-sqlite3": "^7.6.13",
"bcryptjs": "3.0.3",
"better-sqlite3": "^11.0.0",
@@ -22,11 +22,11 @@
"dotenv": "17.2.3",
"express": "5.1.0",
"jsonwebtoken": "9.0.2",
"ts-node-dev": "^2.0.0",
"winston": "^3.19.0",
"zod": "^4.1.13"
},
"devDependencies": {
"ts-node-dev": "^2.0.0",
"@types/bcryptjs": "*",
"@types/cors": "*",
"@types/express": "*",
@@ -35,7 +35,7 @@
"cross-env": "^10.1.0",
"dotenv-cli": "^11.0.0",
"nodemon": "*",
"prisma": "^7.1.0",
"prisma": "^7.2.0",
"ts-node": "*",
"typescript": "*"
}

View File

@@ -1,4 +1,6 @@
import 'dotenv/config';
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({ path: path.resolve(process.cwd(), '../.env') });
import { defineConfig, env } from 'prisma/config';
export default defineConfig({

Binary file not shown.

80
server/reset_prod_db.js Normal file
View File

@@ -0,0 +1,80 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const bcrypt = require('bcryptjs');
const { PrismaClient } = require('@prisma/client');
const dotenv = require('dotenv');
// Load .env from root
const envPath = path.resolve(process.cwd(), '../.env');
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
} else {
// Try current dir just in case, but preference is root
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
}
async function resetDb() {
const adminEmail = process.env.ADMIN_EMAIL_PROD || 'admin-prod@gymflow.ai';
const adminPassword = process.env.ADMIN_PASSWORD_PROD || 'secure-prod-password-change-me';
// 1. Determine DB path (relative to server dir where app runs)
const dbPath = path.resolve(process.cwd(), 'prod.db');
const prismaSchemaPath = path.resolve(process.cwd(), 'prisma/schema.prisma');
console.log(`--- Database Reset ---`);
console.log(`Admin Email: ${adminEmail}`);
console.log(`DB Path: ${dbPath}`);
// 2. Delete existing DB
if (fs.existsSync(dbPath)) {
console.log(`Deleting existing database...`);
fs.unlinkSync(dbPath);
}
// 3. Initialize fresh DB schema using Prisma
console.log(`Initializing schema via Prisma...`);
try {
// Set DATABASE_URL for prisma CLI (used by prisma.config.ts)
const absoluteDbPath = `file:${dbPath}`;
console.log(`Setting DATABASE_URL=${absoluteDbPath}`);
execSync(`npx prisma db push`, {
stdio: 'inherit',
env: {
...process.env,
DATABASE_URL: absoluteDbPath
}
});
} catch (error) {
console.error(`Failed to initialize schema:`, error.message);
process.exit(1);
}
// 4. Create the Admin user
console.log(`Creating fresh admin user...`);
// In Prisma 7, we must use the adapter for better-sqlite3
const { PrismaBetterSqlite3 } = require('@prisma/adapter-better-sqlite3');
const adapter = new PrismaBetterSqlite3({ url: dbPath });
const prisma = new PrismaClient({ adapter });
try {
const hashedPassword = await bcrypt.hash(adminPassword, 10);
await prisma.user.create({
data: {
email: adminEmail,
password: hashedPassword,
role: 'ADMIN',
profile: { create: { weight: 70 } }
}
});
console.log(`✅ Success! Admin user created.`);
} catch (error) {
console.error(`Failed to create admin:`, error.message);
} finally {
await prisma.$disconnect();
}
}
resetDb();

View File

@@ -21,9 +21,15 @@ export class AuthController {
static async login(req: any, res: Response) {
try {
const { email, password } = req.body;
console.log(`[AuthController] Attempting login for: ${email}`);
const result = await AuthService.login(email, password);
console.log(`[AuthController] Login successful for: ${email}`);
return sendSuccess(res, result);
} catch (error: any) {
console.error(`[AuthController] Login failed for: ${req.body?.email}. Error: ${error.message}`);
if (error.message === 'Invalid credentials') {
return sendError(res, error.message, 400);
}

View File

@@ -56,8 +56,16 @@ async function ensureAdminUser() {
where: { role: 'ADMIN' },
});
const { AuthService } = await import('./services/auth.service');
if (existingAdmin) {
console.info(`✅ Admin user already exists (email: ${existingAdmin.email})`);
if (existingAdmin.email === adminEmail) {
console.info(`✅ Admin user already exists (email: ${existingAdmin.email})`);
} else {
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 prisma.$disconnect();
return;
}
@@ -73,7 +81,10 @@ async function ensureAdminUser() {
},
});
console.info(`🛠️ Created default admin user (email: ${admin.email})`);
// Seed exercises for new admin
await AuthService.seedDefaultExercises(admin.id);
console.info(`✅ Admin user created and exercises seeded (email: ${adminEmail})`);
await prisma.$disconnect();
}
@@ -97,7 +108,7 @@ if (process.env.NODE_ENV === 'production') {
const distPath = path.join(__dirname, '../../dist');
app.use(express.static(distPath));
app.get('*', (req, res) => {
app.get('*all', (req, res) => {
if (!req.path.startsWith('/api')) {
res.sendFile(path.join(distPath, 'index.html'));
} else {

View File

@@ -68,20 +68,30 @@ export class AuthService {
});
// 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) {
try {
// Ensure env is loaded (in case server didn't restart)
// Ensure env is loaded from root (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 rootEnv = path.resolve(process.cwd(), '../.env');
if (fs.existsSync(rootEnv)) {
dotenv.config({ path: rootEnv, override: true });
} else {
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)
@@ -109,7 +119,7 @@ export class AuthService {
if (row.name && row.type) {
exercisesToCreate.push({
userId: user.id,
userId,
name: row.name,
type: row.type,
bodyWeightPercentage: row.bodyWeightPercentage ? parseFloat(row.bodyWeightPercentage) : 0,
@@ -120,9 +130,16 @@ export class AuthService {
}
if (exercisesToCreate.length > 0) {
await prisma.exercise.createMany({
data: exercisesToCreate
});
// Check if exercises already exist for this user to avoid duplicates
const existingCount = await prisma.exercise.count({ where: { userId } });
if (existingCount === 0) {
await prisma.exercise.createMany({
data: exercisesToCreate
});
console.log(`[AuthService] Seeded ${exercisesToCreate.length} exercises for user: ${userId}`);
} else {
console.log(`[AuthService] User ${userId} already has ${existingCount} exercises. Skipping seed.`);
}
}
}
} else {
@@ -134,11 +151,6 @@ export class AuthService {
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 { password: _, ...userSafe } = user;
return { user: userSafe, token };
}
static async changePassword(userId: string, newPassword: string) {