sat startup script for local development

This commit is contained in:
Dmitri 2026-06-26 08:23:11 +02:00
parent 9802a1d131
commit b43c1cd5b4
7 changed files with 70 additions and 34 deletions

View File

@ -47,7 +47,7 @@ PGPASSWORD='postgres' psql -U postgres -c "CREATE DATABASE app_39215 OWNER app_3
```bash ```bash
cd backend cd backend
yarn install yarn install
export $(cat .env | xargs) && NODE_ENV=production yarn start npm run start-dev
``` ```
Backend runs on **http://localhost:8080** Backend runs on **http://localhost:8080**

View File

@ -28,7 +28,7 @@ yarn install
yarn db:create yarn db:create
# Start server (runs migrations, seeds, and watches for changes) # Start server (runs migrations, seeds, and watches for changes)
export $(cat .env | xargs) && NODE_ENV=production yarn start npm run start-dev
``` ```
The server runs on **port 8080** by default. The server runs on **port 8080** by default.

View File

@ -3,6 +3,7 @@
"description": "Tour Builder Platform - template backend", "description": "Tour Builder Platform - template backend",
"scripts": { "scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch", "start": "npm run db:migrate && npm run db:seed && npm run watch",
"start-dev": "cross-env NODE_ENV=production LOG_PRETTY=true DOTENV_CONFIG_PATH=.env NODE_OPTIONS=\"-r dotenv/config\" npm run start",
"lint": "eslint . --ext .js", "lint": "eslint . --ext .js",
"db:migrate": "sequelize-cli db:migrate", "db:migrate": "sequelize-cli db:migrate",
"db:migrate:undo": "sequelize-cli db:migrate:undo", "db:migrate:undo": "sequelize-cli db:migrate:undo",

View File

@ -9,7 +9,6 @@ const config = require('../db.config')[env];
const db = {}; const db = {};
let sequelize; let sequelize;
console.log(env);
if (config.use_env_variable) { if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config); sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else { } else {

View File

@ -1,5 +1,6 @@
const ValidationError = require('../services/notifications/errors/validation'); const ValidationError = require('../services/notifications/errors/validation');
const RolesDBApi = require('../db/api/roles'); const RolesDBApi = require('../db/api/roles');
const { logger } = require('../utils/logger');
// Cache for the 'Public' role object // Cache for the 'Public' role object
let publicRoleCache = null; let publicRoleCache = null;
@ -11,17 +12,21 @@ async function fetchAndCachePublicRole() {
publicRoleCache = await RolesDBApi.findBy({ name: 'Public' }); publicRoleCache = await RolesDBApi.findBy({ name: 'Public' });
if (!publicRoleCache) { if (!publicRoleCache) {
console.error( logger.warn(
"WARNING: Role 'Public' not found in database during middleware startup. Check your migrations.", { role: 'Public' },
'Role not found during permissions middleware startup',
); );
// The system might not function correctly without this role. May need to throw an error or use a fallback stub. // The system might not function correctly without this role. May need to throw an error or use a fallback stub.
} else { } else {
console.log("'Public' role successfully loaded and cached."); logger.info(
{ role: 'Public', roleId: publicRoleCache.id },
'Role loaded and cached',
);
} }
} catch (error) { } catch (error) {
console.error( logger.error(
"Error fetching 'Public' role during middleware startup:", { err: error, role: 'Public' },
error, 'Error fetching role during permissions middleware startup',
); );
// Handle the error during startup fetch // Handle the error during startup fetch
throw error; // Important to know if the app can proceed without the Public role throw error; // Important to know if the app can proceed without the Public role
@ -32,9 +37,9 @@ async function fetchAndCachePublicRole() {
// This should happen during application startup when routes are being configured. // This should happen during application startup when routes are being configured.
fetchAndCachePublicRole().catch((error) => { fetchAndCachePublicRole().catch((error) => {
// Handle the case where the fetchAndCachePublicRole promise is rejected // Handle the case where the fetchAndCachePublicRole promise is rejected
console.error( logger.error(
'Critical error during permissions middleware initialization:', { err: error },
error, 'Critical error during permissions middleware initialization',
); );
}); });
@ -81,8 +86,10 @@ function checkPermissions(permission) {
if (!publicRoleCache) { if (!publicRoleCache) {
// If the cache is unexpectedly empty (e.g., startup error caught), // If the cache is unexpectedly empty (e.g., startup error caught),
// we can try fetching the role again synchronously (less ideal) or just deny access. // we can try fetching the role again synchronously (less ideal) or just deny access.
console.error( const log = req.log || logger;
'Public role cache is empty. Attempting synchronous fetch...', log.warn(
{ role: 'Public' },
'Role cache is empty, attempting synchronous fetch',
); );
// Less efficient fallback option: // Less efficient fallback option:
effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow
@ -117,9 +124,10 @@ function checkPermissions(permission) {
} else if (Array.isArray(effectiveRole.permissions)) { } else if (Array.isArray(effectiveRole.permissions)) {
rolePermissions = effectiveRole.permissions; // Or take from property if permissions are pre-loaded rolePermissions = effectiveRole.permissions; // Or take from property if permissions are pre-loaded
} else { } else {
console.error( const log = req.log || logger;
'Role object lacks getPermissions() method or permissions property:', log.error(
effectiveRole, { roleId: effectiveRole.id, roleName: effectiveRole.name },
'Role object lacks getPermissions method or permissions property',
); );
return next( return next(
new Error('Internal Server Error: Invalid role object format.'), new Error('Internal Server Error: Invalid role object format.'),
@ -140,7 +148,8 @@ function checkPermissions(permission) {
} }
} catch (e) { } catch (e) {
// Handle errors during role or permission fetching // Handle errors during role or permission fetching
console.error('Error during permission check:', e); const log = req.log || logger;
log.error({ err: e, permission }, 'Error during permission check');
next(e); // Pass the error to the next middleware next(e); // Pass the error to the next middleware
} }
}; };

View File

@ -1,12 +1,21 @@
const pino = require('pino'); const pino = require('pino');
const crypto = require('crypto'); const crypto = require('crypto');
const isDevelopment = process.env.NODE_ENV === 'development'; const shouldPrettyPrint =
process.env.LOG_PRETTY === 'true' || process.env.NODE_ENV === 'development';
const logger = pino({ const logger = pino({
level: process.env.LOG_LEVEL || 'info', level: process.env.LOG_LEVEL || 'info',
transport: isDevelopment transport: shouldPrettyPrint
? { target: 'pino-pretty', options: { colorize: true } } ? {
target: 'pino-pretty',
options: {
colorize: true,
ignore: 'pid,hostname',
messageFormat: '{service} | {msg}',
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l',
},
}
: undefined, : undefined,
base: { base: {
service: 'tour-builder-api', service: 'tour-builder-api',
@ -27,7 +36,7 @@ function requestLogger(req, res, next) {
method: req.method, method: req.method,
url: req.originalUrl || req.url, url: req.originalUrl || req.url,
status: res.statusCode, status: res.statusCode,
duration, durationMs: duration,
userAgent: req.headers['user-agent'], userAgent: req.headers['user-agent'],
}; };

View File

@ -1,21 +1,38 @@
const chokidar = require('chokidar'); const chokidar = require('chokidar');
const { exec } = require('child_process'); const { exec } = require('child_process');
const nodemon = require('nodemon'); const nodemon = require('nodemon');
const { logger } = require('./src/utils/logger');
const nodeEnv = process.env.NODE_ENV || 'dev_stage'; const nodeEnv = process.env.NODE_ENV || 'dev_stage';
const childEnv = { ...process.env, NODE_ENV: nodeEnv }; const childEnv = { ...process.env, NODE_ENV: nodeEnv };
const log = logger.child({ module: 'watcher' });
function logCommandResult(error, stdout, stderr, successMessage) {
const output = stdout && stdout.trim();
const errorOutput = stderr && stderr.trim();
if (output) {
log.info({ output }, successMessage);
}
if (error) {
log.error(
{ err: error, stderr: errorOutput },
'Watched database command failed',
);
} else if (errorOutput) {
log.warn({ stderr: errorOutput }, 'Watched database command wrote stderr');
}
}
const migrationsWatcher = chokidar.watch('./src/db/migrations', { const migrationsWatcher = chokidar.watch('./src/db/migrations', {
persistent: true, persistent: true,
ignoreInitial: true ignoreInitial: true
}); });
migrationsWatcher.on('add', (filePath) => { migrationsWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New migration file: ${filePath}`); log.info({ filePath }, 'New migration file detected');
exec('npm run db:migrate', { env: childEnv }, (error, stdout, stderr) => { exec('npm run db:migrate', { env: childEnv }, (error, stdout, stderr) => {
console.log(stdout); logCommandResult(error, stdout, stderr, 'Migration command completed');
if (error) {
console.error(stderr);
}
}); });
}); });
@ -24,12 +41,9 @@ const seedersWatcher = chokidar.watch('./src/db/seeders', {
ignoreInitial: true ignoreInitial: true
}); });
seedersWatcher.on('add', (filePath) => { seedersWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New seed file: ${filePath}`); log.info({ filePath }, 'New seed file detected');
exec('npm run db:seed', { env: childEnv }, (error, stdout, stderr) => { exec('npm run db:seed', { env: childEnv }, (error, stdout, stderr) => {
console.log(stdout); logCommandResult(error, stdout, stderr, 'Seeder command completed');
if (error) {
console.error(stderr);
}
}); });
}); });
@ -41,9 +55,13 @@ nodemon({
}); });
nodemon.on('start', () => { nodemon.on('start', () => {
console.log('Nodemon started'); log.info({ nodeEnv }, 'Nodemon started');
}); });
nodemon.on('restart', (files) => { nodemon.on('restart', (files) => {
console.log('Nodemon restarted due changes in:', files); log.info({ files }, 'Nodemon restarted due to file changes');
});
nodemon.on('crash', () => {
log.error('Nodemon app crashed');
}); });