diff --git a/.sequelizerc b/.sequelizerc new file mode 100644 index 00000000..a6f14ffd --- /dev/null +++ b/.sequelizerc @@ -0,0 +1,8 @@ +import path from 'path'; + +module.exports = { + 'config': path.resolve('server/src/db/config', 'database.js'), + 'models-path': path.resolve('server/src/db/models'), + 'seeders-path': path.resolve('server/src/db/seeders'), + 'migrations-path': path.resolve('server/src/db/migrations') +}; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b571cc6f..45f70a33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,7 @@ services: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env environment: + DB_HOST: ${DB_HOST} BACKEND_URL: ${BACKEND_URL} # to ensure Playwright works in Docker PLAYWRIGHT_BROWSERS_PATH: /ms-playwright diff --git a/package.json b/package.json index d9818fcc..3e8c3bc1 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,13 @@ "build:server": "tsc -p server/tsconfig.json", "start:server": "cross-env NODE_OPTIONS='--max-old-space-size=8000' server/dist/server/src/server.js", "preview": "vite preview", - "lint": "./node_modules/.bin/eslint ." + "lint": "./node_modules/.bin/eslint .", + "migrate": "sequelize-cli db:migrate", + "migrate:undo": "sequelize-cli db:migrate:undo", + "migrate:undo:all": "sequelize-cli db:migrate:undo:all", + "seed": "sequelize-cli db:seed:all", + "seed:undo:all": "sequelize-cli db:seed:undo:all", + "migration:generate": "sequelize-cli migration:generate --name" }, "eslintConfig": { "extends": [ diff --git a/server/Dockerfile b/server/Dockerfile index 65e472eb..8770b78e 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -11,6 +11,7 @@ COPY public ./public COPY server ./server COPY tsconfig.json ./ COPY server/tsconfig.json ./server/ +COPY .sequelizerc ./ # COPY server/start.sh ./ # Install dependencies @@ -41,11 +42,18 @@ RUN apt-get update && apt-get install -y \ libxext6 \ libxi6 \ libxtst6 \ + postgresql-client \ + netcat-openbsd \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix +COPY server/docker-entrypoint.sh /app/ +RUN chmod +x /app/docker-entrypoint.sh + # Expose the backend port EXPOSE ${BACKEND_PORT:-8080} +ENTRYPOINT ["/app/docker-entrypoint.sh"] + # Start the backend using the start script CMD ["npm", "run", "server"] diff --git a/server/docker-entrypoint.sh b/server/docker-entrypoint.sh new file mode 100644 index 00000000..ad670faf --- /dev/null +++ b/server/docker-entrypoint.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +# Function to wait for PostgreSQL +wait_for_postgres() { + echo "Waiting for PostgreSQL at $DB_HOST:$DB_PORT..." + + max_retries=30 + retries=0 + + while ! nc -z $DB_HOST $DB_PORT; do + retries=$((retries+1)) + if [ $retries -eq $max_retries ]; then + echo "Error: PostgreSQL not available after $max_retries attempts. Continuing anyway..." + break + fi + echo "PostgreSQL not available yet (attempt $retries/$max_retries), retrying..." + sleep 2 + done + + if [ $retries -lt $max_retries ]; then + echo "PostgreSQL is ready!" + fi +} + +# Wait for PostgreSQL to be ready +wait_for_postgres + +# Run the application with migrations before startup +NODE_OPTIONS="--max-old-space-size=4096" node -e "require('./server/src/db/migrate')().then(() => { console.log('Migration process completed.'); })" + +# Run the server normally +exec "$@" \ No newline at end of file diff --git a/server/src/db/config/database.js b/server/src/db/config/database.js new file mode 100644 index 00000000..ae6972d1 --- /dev/null +++ b/server/src/db/config/database.js @@ -0,0 +1,42 @@ +import dotenv from 'dotenv'; +dotenv.config({ path: './.env' }); + +// Validate required environment variables +const requiredEnvVars = ['DB_USER', 'DB_PASSWORD', 'DB_NAME', 'DB_HOST', 'DB_PORT']; +requiredEnvVars.forEach(envVar => { + if (!process.env[envVar]) { + console.error(`Error: Environment variable ${envVar} is not set.`); + process.exit(1); + } +}); + + +module.exports = { + development: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + dialect: 'postgres', + logging: console.log, + }, + test: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + dialect: 'postgres', + logging: false, + }, + production: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + dialect: 'postgres', + logging: false, + } +}; \ No newline at end of file diff --git a/server/src/db/migrate.js b/server/src/db/migrate.js new file mode 100644 index 00000000..a76c04eb --- /dev/null +++ b/server/src/db/migrate.js @@ -0,0 +1,30 @@ +'use strict'; + +import { execSync } from 'child_process'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import db from './models/index.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +async function runMigrations() { + try { + console.log('Testing database connection...'); + await db.sequelize.authenticate(); + console.log('Database connection established successfully.'); + + console.log('Running database migrations...'); + execSync('npx sequelize-cli db:migrate', { + stdio: 'inherit', + cwd: path.resolve(__dirname, '../../..') + }); + console.log('Migrations completed successfully'); + return true; + } catch (error) { + console.error('Migration error:', error); + return false; + } +} + +module.exports = runMigrations; \ No newline at end of file diff --git a/server/src/db/models/index.js b/server/src/db/models/index.js new file mode 100644 index 00000000..1f26e70b --- /dev/null +++ b/server/src/db/models/index.js @@ -0,0 +1,59 @@ +'use strict'; + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import Sequelize from 'sequelize'; +import databaseConfig from '../config/database.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; +const config = databaseConfig[env]; +const db = {}; + +let sequelize; +if (config.use_env_variable) { + try { + sequelize = new Sequelize(process.env[config.use_env_variable], config); + console.log(`Connected to database using ${config.use_env_variable}`); + } catch (error) { + console.error('Unable to connect to the database using environment variable:', error); + process.exit(1); + } +} else { + try { + sequelize = new Sequelize(config.database, config.username, config.password, config); + console.log(`Connected to database: ${config.database}`); + } catch (error) { + console.error('Unable to connect to the database:', error); + process.exit(1); + } +} + +fs + .readdirSync(__dirname) + .filter(file => { + return ( + file.indexOf('.') !== 0 && + file !== basename && + file.slice(-3) === '.js' && + file.indexOf('.test.js') === -1 + ); + }) + .forEach(file => { + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); + db[model.name] = model; + }); + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; \ No newline at end of file