# DB Config Module ## Overview The DB Config module manages database connection settings, environment validation, and database utilities. It provides environment-aware configuration for PostgreSQL connections via Sequelize ORM. **Location:** `backend/src/db/` **Key Files:** - `db-config.ts` - Typed ESM database connection settings per environment - `umzug.ts` - Typed Umzug runner for migrations, seeders, create/drop - `utils.ts` - Database utility functions - `sync.ts` - Development database sync script - `reset.ts` - Database reset script **Related Files:** - `backend/src/config.ts` - Application configuration - `backend/src/utils/env-validation.ts` - Environment variable validation --- ## Architecture ``` backend/ ├── src/ │ ├── config.ts # Application config │ ├── utils/ │ │ └── env-validation.ts # Joi schema validation │ └── db/ │ ├── db-config.ts # Typed database connection config │ ├── umzug.ts # Typed migration/seed runner │ ├── utils.ts # DB utilities │ ├── sync.ts # Dev sync script (21 LOC) │ ├── reset.ts # Reset script (18 LOC) │ ├── models/ │ │ ├── loader.ts # Typed model loader + Sequelize init │ │ └── index.ts # ESM model registry entrypoint │ ├── migrations/ # Schema migrations │ └── seeders/ # Data seeders ``` --- ## Database Configuration ### db-config.ts `db-config.ts` exports environment-specific database settings for Sequelize. It uses the official Sequelize `Options` type plus project-specific `DatabaseConfigMap`/`DatabaseEnvironmentConfig` reusable contracts from `backend/src/types/db-config.ts`. Database scripts use `backend/src/db/umzug.ts` directly. `DB_PORT` is parsed to a number for the Sequelize `Options` contract. If the env var is absent or invalid, the `port` property is omitted. ### Environment Comparison | Setting | Production | Development | Dev Stage | |---------|------------|-------------|-----------| | **Dialect** | postgres | postgres | postgres | | **Credentials** | Env vars | Hardcoded | Env vars | | **Logging** | Disabled | Pino debug | Pino debug | | **Host** | Env var | localhost | Env var | | **Migration Storage** | SequelizeMeta | SequelizeMeta | SequelizeMeta | | **Seeder Storage** | SequelizeData | SequelizeData | SequelizeData | --- ## Environment Variables ### Database Variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `NODE_ENV` | No | development | Environment selection | | `DB_HOST` | Prod/Stage | localhost | Database host | | `DB_PORT` | Prod/Stage | 5432 | Database port | | `DB_NAME` | Prod/Stage | db_tour_builder_platform | Database name | | `DB_USER` | Prod/Stage | postgres | Database username | | `DB_PASS` | Prod/Stage | (empty) | Database password | ### Environment Validation The `env-validation.ts` file uses Joi schema to validate all environment variables: ```javascript const Joi = require('joi'); const envSchema = Joi.object({ NODE_ENV: Joi.string() .valid('development', 'test', 'production', 'dev_stage') .default('development'), PORT: Joi.number().default(8080), DB_HOST: Joi.string().default('localhost'), DB_PORT: Joi.number().default(5432), DB_NAME: Joi.string().default('db_tour_builder_platform'), DB_USER: Joi.string().default('postgres'), DB_PASS: Joi.string().allow('').default(''), SECRET_KEY: Joi.string() .min(16) .default('88dbeaf8-e906-405e-9e41-c3baadeda5c6'), // ... more variables }).unknown(true); ``` ### Complete Environment Variable Schema | Category | Variable | Validation | Default | |----------|----------|------------|---------| | **Server** | NODE_ENV | enum: development, test, production, dev_stage | development | | | PORT | number | 8080 | | **Database** | DB_HOST | string | localhost | | | DB_PORT | number | 5432 | | | DB_NAME | string | db_tour_builder_platform | | | DB_USER | string | postgres | | | DB_PASS | string (allow empty) | (empty) | | **Auth** | SECRET_KEY | string, min 16 chars | (default UUID) | | | ADMIN_PASS | string | 88dbeaf8 | | | USER_PASS | string | c3baadeda5c6 | | | ADMIN_EMAIL | email | admin@flatlogic.com | | **OAuth** | GOOGLE_CLIENT_ID | string (allow empty) | (empty) | | | GOOGLE_CLIENT_SECRET | string (allow empty) | (empty) | | | MS_CLIENT_ID | string (allow empty) | (empty) | | | MS_CLIENT_SECRET | string (allow empty) | (empty) | | **AWS S3** | AWS_ACCESS_KEY_ID | string (allow empty) | (empty) | | | AWS_SECRET_ACCESS_KEY | string (allow empty) | (empty) | | | AWS_S3_BUCKET | string (allow empty) | (empty) | | | AWS_S3_REGION | string | us-east-1 | | | AWS_S3_PREFIX | string | (default hash) | | | AWS_S3_CONNECTION_TIMEOUT | number (ms) | 5000 | | | AWS_S3_REQUEST_TIMEOUT | number (ms) | 30000 | | | AWS_S3_MAX_ATTEMPTS | number | 3 | | | AWS_S3_MAX_SOCKETS | number | 50 | | | AWS_S3_KEEP_ALIVE | boolean string | true | | | AWS_S3_PRESIGN_EXPIRY | number (seconds) | 3600 | | **Email** | EMAIL_USER | string (allow empty) | (empty) | | | EMAIL_PASS | string (allow empty) | (empty) | | | EMAIL_TLS_REJECT_UNAUTHORIZED | enum: true, false | true | | **External APIs** | PEXELS_KEY | string (allow empty) | (empty) | | **Logging** | LOG_LEVEL | enum: fatal, error, warn, info, debug, trace | info | ### Validation Behavior ```javascript function validateEnv() { const { error, value } = envSchema.validate(process.env, { abortEarly: false, // Report all errors, not just first stripUnknown: false, // Keep unknown env vars }); if (error) { const messages = error.details.map((d) => ` - ${d.message}`); logger.error({ errors: messages }, 'Environment validation failed'); if (process.env.NODE_ENV === 'production') { process.exit(1); // Fatal in production } else { logger.warn('Continuing with default values in non-production mode'); } } return value; } ``` --- ## Umzug Configuration The database command entrypoint is `backend/src/db/umzug.ts`. ### Runtime Paths | Setting | Path | |---------|------| | Config | `src/db/db-config.ts` | | Runner | `src/db/umzug.ts` | | Models | `src/db/models/` | | Seeders | `src/db/seeders/` | | Migrations | `src/db/migrations/` | --- ## Database Initialization ### Model Loader `src/db/models/loader.ts` explicitly imports model factories and builds the Sequelize registry. `src/db/models/index.ts` is the ESM entrypoint. The typed service-specific overload facade lives in `src/types/db-models.ts`. ### Initialization Flow ``` Server Start │ ▼ config.ts loads │ ├─ dotenv.config() ─── Load .env file │ ├─ validateEnv() ─── Validate with Joi schema │ │ │ ├─ Production: Exit on error │ └─ Development: Warn, use defaults │ └─ Export config object │ ▼ db/models/index.ts loads │ ├─ Read NODE_ENV │ ├─ Load db-config.ts[env] │ ├─ Create Sequelize instance │ ├─ Initialize explicitly imported model factories │ ├─ Call model.associate() for relationships │ └─ Export db object { sequelize, Sequelize, models... } ``` --- ## Database Utilities ### utils.ts Database utility helpers are TypeScript/ESM source. Runtime DB access goes through `src/db/models/index.ts`, whose typed facade is provided by `src/types/db-models.ts`. ### Usage Examples ```typescript import Utils from '../db/utils.ts'; // UUID validation Utils.isValidUuid('550e8400-e29b-41d4-a716-446655440000'); // true Utils.isValidUuid('not-a-uuid'); // false // Generate new UUID const id = Utils.generateUuid(); // Returns new UUID v4 // Filter array to valid UUIDs only const validIds = Utils.filterValidUuids(['uuid1', 'invalid', 'uuid2']); // Case-insensitive search const where = { [Op.or]: [ Utils.ilike('users', 'firstName', searchTerm), Utils.ilike('users', 'lastName', searchTerm), Utils.ilike('users', 'email', searchTerm), ] }; ``` --- ## Database Scripts ### sync.ts (Development Only) Synchronizes models to database schema using Sequelize's `alter` mode. ```javascript async function syncDatabase() { // Safety check - never run in production if (process.env.NODE_ENV === 'production') { console.error('ERROR: sync.ts should not be run in production. Use migrations instead.'); process.exit(1); } try { console.log('Syncing database...'); await db.sequelize.sync({ alter: true }); console.log('Database synced successfully!'); process.exit(0); } catch (error) { console.error('Error syncing database:', error); process.exit(1); } } ``` **Usage:** ```bash node src/db/sync.ts ``` **Sync Modes:** | Mode | Description | Use Case | |------|-------------|----------| | `{ force: true }` | Drop and recreate all tables | Fresh start | | `{ alter: true }` | Modify tables to match models | Development | | (none) | Create only missing tables | Safe default | ### reset.ts Forcefully resets database and runs seeders. ```javascript db.sequelize .sync({ force: true }) .then(() => { execSync('sequelize db:seed:all'); console.log('OK'); process.exit(); }) .catch((error) => { console.error(error); process.exit(1); }); ``` **Usage:** ```bash node src/db/reset.ts ``` **Warning:** This drops ALL tables and recreates them. Data loss! --- ## Application Configuration ### config.ts Centralized application configuration with environment-aware settings. ```javascript const env = validateEnv(); const config = { gcloud: { bucket: 'fldemo-files', hash: 'afeefb9d49f5b7977577876b99532ac7', projectId: env.GC_PROJECT_ID, clientEmail: env.GC_CLIENT_EMAIL, privateKey: env.GC_PRIVATE_KEY, }, fileStorage: { provider: env.FILE_STORAGE_PROVIDER, }, s3: { bucket: env.AWS_S3_BUCKET, region: env.AWS_S3_REGION, accessKeyId: env.AWS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY, prefix: env.AWS_S3_PREFIX, connectionTimeout: env.AWS_S3_CONNECTION_TIMEOUT, requestTimeout: env.AWS_S3_REQUEST_TIMEOUT, maxAttempts: env.AWS_S3_MAX_ATTEMPTS, maxSockets: env.AWS_S3_MAX_SOCKETS, keepAlive: env.AWS_S3_KEEP_ALIVE !== 'false', presignExpirySeconds: env.AWS_S3_PRESIGN_EXPIRY, }, resilience: { ffmpeg: { reverseTimeoutMs, ffprobeTimeoutMs, breaker }, fileStorage: { breaker }, }, server: { env: env.NODE_ENV, port: serverPort, swaggerServerUrl, }, secret_key: env.SECRET_KEY, admin_pass: env.ADMIN_PASS, user_pass: env.USER_PASS, admin_email: env.ADMIN_EMAIL, // Roles roles: { admin: 'Administrator', user: 'Analytics Viewer', }, apiUrl: `${host}${port ? `:${port}` : ''}/api`, swaggerUrl: `${swaggerUI}${swaggerPort}`, uiUrl: `${hostUI}${portUI ? `:${portUI}` : ''}/#`, backUrl: `${hostUI}${portUI ? `:${portUI}` : ''}`, }; ``` --- ## Configuration Categories ### Storage Configuration | Provider | Variables | Purpose | |----------|-----------|---------| | **AWS S3** | AWS_S3_BUCKET, AWS_S3_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | File storage | | **GCloud** | (hardcoded bucket) | Legacy support | | **Local** | uploadDir (os.tmpdir()) | Development fallback | ### S3 Performance Tuning | Variable | Default | Description | |----------|---------|-------------| | AWS_S3_CONNECTION_TIMEOUT | 5000ms | TCP connection timeout | | AWS_S3_REQUEST_TIMEOUT | 30000ms | Total request timeout | | AWS_S3_MAX_ATTEMPTS | 3 | Retry attempts on failure | | AWS_S3_MAX_SOCKETS | 50 | Connection pool size | | AWS_S3_KEEP_ALIVE | true | Reuse TCP connections | | AWS_S3_PRESIGN_EXPIRY | 3600s | Presigned URL validity (1 hour) | ### Security Configuration | Setting | Value | Purpose | |---------|-------|---------| | bcrypt.saltRounds | 12 | Password hashing strength | | SECRET_KEY | 16+ char string | JWT signing key | | EMAIL_TLS_REJECT_UNAUTHORIZED | true/false | TLS certificate validation | ### URL Configuration | URL | Development | Production | |-----|-------------|------------| | apiUrl | http://localhost:3000/api | (remote)/api | | swaggerUrl | http://localhost:3000 | (remote) | | uiUrl | http://localhost:3001/# | (remote)/# | | backUrl | http://localhost:3001 | (remote) | --- ## Running Commands ### Development ```bash cd backend npm run start-dev ``` `start-dev` runs the standard `start` pipeline (`db:migrate`, `db:seed`, `watch`) with `LOG_PRETTY=true`. `src/load-env.ts` loads `backend/.env` before DB config selection; when `NODE_ENV` is absent it defaults to `dev_stage`, which matches the standard VM backend flow and listens on port `3000`. ### VM / Dev Stage ```bash cd backend npm run start ``` In the standard VM PM2 setup, `NODE_ENV=dev_stage` makes the backend listen on port `3000`; the frontend runs separately on `3001` and Apache proxies public traffic from port `80`. See [`deployment-vm.md`](../../../documentation/deployment-vm.md). Do not add `NODE_ENV=production` only to make local startup work. The current flow loads `.env` through `src/load-env.ts` and defaults missing `NODE_ENV` to `dev_stage`, matching the VM backend process. --- ## Best Practices ### 1. Never Commit Secrets ```bash # .env file should be in .gitignore # Use environment variables in deployment ``` ### 2. Use Migrations in Production ```javascript // Never use sync.ts or reset.ts in production if (process.env.NODE_ENV === 'production') { process.exit(1); } ``` ### 3. Validate Environment Early ```javascript // config.ts loads validation at import time import { validateEnv } from './utils/env-validation.ts'; validateEnv(); // Called before app starts ``` ### 4. Environment-Specific Logging ```javascript // Production: logging disabled (performance) // Development/dev_stage: SQL logs use structured Pino debug entries logging: process.env.NODE_ENV === 'production' ? false : (sql) => logger.debug({ sql }, 'Sequelize query'), ``` --- ## Related Documentation - [DB Models](./db-models.md) - Sequelize model definitions - [DB API](./db-api.md) - Database access layer - [DB Migrations](./db-migrations.md) - Schema evolution - [DB Seeders](./db-seeders.md) - Initial data population - [Core Module](./core.md) - Application entry point