Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import nodeHttp from 'http';
import nodeHttps from 'https';

import config from '../config/index.js';
import logger from './services/logger.js';
import express from './services/express.js';
import mongooseService from './services/mongoose.js';
import migrations from './services/migrations.js';
Expand Down Expand Up @@ -94,26 +95,25 @@ const logConfiguration = async () => {
// Create server URL
const server = `${(config.secure && config.secure.credentials ? 'https://' : 'http://') + config.api.host}:${config.api.port}`;
// Logging initialization
console.log(chalk.green(config.app.title));
console.log();
console.log(chalk.green(`Environment: ${process.env.NODE_ENV ? process.env.NODE_ENV : 'develoment'}`));
console.log(chalk.green(`Server: ${server}`));
console.log(chalk.green(`Database: ${config.db.uri}`));
if (config.cors.origin.length > 0) console.log(chalk.green(`Cors: ${config.cors.origin}`));
logger.info(chalk.green(config.app.title));
logger.info(chalk.green(`Environment: ${process.env.NODE_ENV ? process.env.NODE_ENV : 'development'}`));
logger.info(chalk.green(`Server: ${server}`));
const safeUri = config.db.uri.replace(/\/\/[^@]+@/, '//***:***@');
logger.info(chalk.green(`Database: ${safeUri}`));
if (config.cors.origin.length > 0) logger.info(chalk.green(`Cors: ${config.cors.origin}`));

// SaaS readiness summary (skip in test to keep output clean)
if (process.env.NODE_ENV !== 'test') {
try {
const { default: HomeService } = await import('../modules/home/services/home.service.js');
const checks = HomeService.getReadinessStatus();
console.log();
console.log(chalk.green('SaaS Readiness:'));
logger.info(chalk.green('SaaS Readiness:'));
checks.forEach((c) => {
const icon = c.status === 'ok' ? chalk.green('OK') : chalk.yellow('WARN');
console.log(` ${icon} ${c.category.padEnd(12)} ${c.message}`);
logger.info(` ${icon} ${c.category.padEnd(12)} ${c.message}`);
});
} catch (err) {
console.log(chalk.yellow(` SaaS readiness check failed: ${err.message}`));
logger.warn(chalk.yellow(` SaaS readiness check failed: ${err.message}`), err);
}
}
};
Expand Down Expand Up @@ -160,7 +160,7 @@ const FORCE_SHUTDOWN_TIMEOUT_MS = 5000;
const shutdown = async (server) => {
// Force exit if graceful shutdown hangs
const forceTimeout = setTimeout(() => {
console.error(chalk.red('Forced shutdown (timeout)'));
logger.error(chalk.red('Forced shutdown (timeout)'));
process.exit(1);
}, FORCE_SHUTDOWN_TIMEOUT_MS);
forceTimeout.unref();
Expand All @@ -171,15 +171,16 @@ const shutdown = async (server) => {
await SentryService.shutdown();
await mongooseService.disconnect();
value.http.close((err) => {
console.info(chalk.yellow('Server closed'));
if (err) {
console.info(chalk.red('Error on server close.', err));
logger.error(chalk.red('Error on server close.'), err);
process.exitCode = 1;
} else {
logger.info(chalk.yellow('Server closed'));
}
process.exit();
});
} catch (err) {
console.error(chalk.red('Shutdown error: server never started or shutdown failed'), err);
logger.error(chalk.red('Shutdown error: server never started or shutdown failed'), err);
process.exit(1);
}
};
Expand Down
3 changes: 2 additions & 1 deletion lib/helpers/mailer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path';
import handlebars from 'handlebars';

import config from '../../../config/index.js';
import logger from '../../services/logger.js';
import files from '../files.js';
import NodemailerProvider from './provider.nodemailer.js';
import ResendProvider from './provider.resend.js';
Expand Down Expand Up @@ -93,7 +94,7 @@ const sendMail = async (mail) => {
if (!Array.isArray(result?.accepted)) return { ...result, accepted: [mail.to], rejected: [] };
return result;
} catch (err) {
console.error(`Mail send error: ${err.message}`);
logger.error('Mail send error', err);
return null;
}
};
Expand Down
9 changes: 9 additions & 0 deletions lib/helpers/mailer/tests/mailer.unit.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ jest.unstable_mockModule('../provider.nodemailer.js', () => ({
default: jest.fn().mockImplementation(() => ({ send: jest.fn() })),
}));

jest.unstable_mockModule('../../../services/logger.js', () => ({
default: {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
},
}));

const { default: mailer } = await import('../index.js');

describe('mailer index with resend provider unit tests:', () => {
Expand Down
6 changes: 3 additions & 3 deletions lib/services/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ const initPreParserRoutes = async (app) => {
try {
const route = await import(path.resolve(routePath));
if (typeof route.default !== 'function') {
console.warn(`Pre-parser route ${routePath} does not export a default function`);
logger.warn(`Pre-parser route ${routePath} does not export a default function`);
continue;
}
route.default(app);
} catch (err) {
console.error(`Failed to load pre-parser route: ${routePath}`, err);
logger.error(`Failed to load pre-parser route: ${routePath}`, err);
throw err;
}
}
Expand Down Expand Up @@ -237,7 +237,7 @@ const initModulesServerRoutes = async (app) => {
const initErrorRoutes = (app) => {
app.use((err, req, res, next) => {
if (!err) return next();
console.error(err.stack);
logger.error(err.stack);
res.status(err.status || 500).send({
message: err.message,
code: err.code,
Expand Down
16 changes: 8 additions & 8 deletions lib/services/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import chalk from 'chalk';
import mongoose from 'mongoose';
import path from 'path';
import { glob } from 'glob';
import logger from './logger.js';

/**
* Scan all modules for migration files matching `modules/*/migrations/*.js`.
Expand Down Expand Up @@ -89,7 +90,7 @@ const runMigration = async (filePath, executed) => {
// Atomically claim the migration to prevent concurrent execution
const claimed = await claimMigration(name);
if (!claimed) {
console.log(chalk.yellow(` Migration already claimed by another runner: ${name}`));
logger.warn(chalk.yellow(` Migration already claimed by another runner: ${name}`));
return false;
}

Expand All @@ -115,7 +116,7 @@ const runMigration = async (filePath, executed) => {
throw err;
}

console.log(chalk.green(` Migration executed: ${name}`));
logger.info(chalk.green(` Migration executed: ${name}`));
return true;
};

Expand All @@ -133,30 +134,29 @@ const run = async () => {
const files = await discoverMigrationFiles();

if (files.length === 0) {
console.log(chalk.yellow('No migration files found.'));
logger.warn(chalk.yellow('No migration files found.'));
return { total: 0, executed: 0 };
}

const executed = await getExecutedMigrations();
let executedCount = 0;

console.log(chalk.yellow(`Running migrations (${files.length} found, ${executed.size} already executed)...`));
logger.info(chalk.yellow(`Running migrations (${files.length} found, ${executed.size} already executed)...`));

for (const filePath of files) {
try {
const wasRun = await runMigration(filePath, executed);
if (wasRun) executedCount++;
} catch (err) {
console.error(chalk.red(`Migration failed: ${path.basename(filePath)}`));
console.error(chalk.red(err.message));
logger.error(chalk.red(`Migration failed: ${path.basename(filePath)}`), err);
throw err;
}
}

if (executedCount > 0) {
console.log(chalk.green(`Migrations complete: ${executedCount} executed.`));
logger.info(chalk.green(`Migrations complete: ${executedCount} executed.`));
} else {
console.log(chalk.yellow('All migrations already up to date.'));
logger.info(chalk.yellow('All migrations already up to date.'));
}

return { total: files.length, executed: executedCount };
Expand Down
9 changes: 5 additions & 4 deletions lib/services/mongoose.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import chalk from 'chalk';
import mongoose from 'mongoose';
import path from 'path';
import config from '../../config/index.js';
import logger from './logger.js';

/**
* Load all mongoose related models
Expand Down Expand Up @@ -38,13 +39,13 @@ const connect = async () => {

await mongoose.connect(config.db.uri, mongoOptions);
mongoose.set('debug', config.db.debug);
console.info(chalk.yellow('Connected to MongoDB.'));
logger.info(chalk.yellow('Connected to MongoDB.'));

return mongoose;
} catch (err) {
// Log Error
console.error(chalk.red('Could not connect to MongoDB!'));
console.log(err);
logger.error(chalk.red('Could not connect to MongoDB!'));
logger.error(err);
throw err;
}
};
Expand All @@ -54,7 +55,7 @@ const connect = async () => {
*/
const disconnect = async () => {
await mongoose.disconnect();
console.info(chalk.yellow('Disconnected from MongoDB.'));
logger.info(chalk.yellow('Disconnected from MongoDB.'));
};

export default {
Expand Down
13 changes: 7 additions & 6 deletions lib/services/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from 'lodash';
import chalk from 'chalk';

import config from '../../config/index.js';
import logger from './logger.js';

import AppError from '../helpers/AppError.js';

Expand All @@ -19,10 +20,10 @@ const seedTheUser = (UserService, user) => async (password) => {
if (process.env.NODE_ENV === 'test' && (await UserService.get(user))) UserService.remove(user);
try {
const result = await UserService.create(user);
if (seedOptions.logResults) console.log(chalk.bold.blue(`Database Seeding: Local ${user.email} added with password set to ${password}`));
if (seedOptions.logResults) logger.info(chalk.bold.blue(`Database Seeding: Local ${user.email} added with password set to ***`));
return result;
} catch (err) {
console.log(err);
logger.error(err);
throw new AppError('Failed to seedTheUser.', { code: 'LIB_ERROR' });
}
};
Expand All @@ -31,10 +32,10 @@ const seedTheUser = (UserService, user) => async (password) => {
const seedTasks = async (TaskService, task, user) => {
try {
const result = await TaskService.create(task, user);
if (seedOptions.logResults) console.log(chalk.bold.blue(`Database Seeding: Local ${task.title} added`));
if (seedOptions.logResults) logger.info(chalk.bold.blue(`Database Seeding: Local ${task.title} added`));
return result;
} catch (err) {
console.log(err);
logger.error(err);
throw new AppError('Failed to seedTasks.', { code: 'LIB_ERROR' });
}
};
Expand Down Expand Up @@ -66,7 +67,7 @@ const start = async (options, UserService, AuthService, TaskService) => {
}
}
} catch (err) {
console.log(err);
logger.error(err);
return new AppError('Error on seed start.');
}

Expand All @@ -84,7 +85,7 @@ const user = async (user, UserService, AuthService) => {
pwd = await AuthService.generateRandomPassphrase();
result.push(await seedTheUser(UserService, user)(pwd));
} catch (err) {
console.log(err);
logger.error(err);
return new AppError('Error on seed start.');
}

Expand Down
3 changes: 2 additions & 1 deletion lib/services/sentry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Module dependencies
*/
import config from '../../config/index.js';
import logger from './logger.js';

/**
* Sentry client reference (null when not configured).
Expand Down Expand Up @@ -32,7 +33,7 @@ const init = async () => {
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});
} catch (err) {
console.error('Sentry init failed:', err.message);
logger.error('Sentry init failed:', err);
Sentry = null;
}
};
Expand Down
9 changes: 9 additions & 0 deletions lib/services/tests/sentry.unit.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ jest.unstable_mockModule('../../../config/index.js', () => ({
},
}));

jest.unstable_mockModule('../logger.js', () => ({
default: {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
},
}));

describe('SentryService unit tests:', () => {
let SentryService;

Expand Down
3 changes: 2 additions & 1 deletion modules/audit/audit.init.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Module dependencies
*/
import config from '../../config/index.js';
import logger from '../../lib/services/logger.js';
import auditMiddleware from './middlewares/audit.middleware.js';

/**
Expand All @@ -22,6 +23,6 @@ export default async (app) => {

if (process.env.NODE_ENV !== 'test') {
const enabled = config.audit?.enabled ?? false;
console.log(`Audit module: ${enabled ? 'enabled' : 'disabled'}`);
logger.info(`Audit module: ${enabled ? 'enabled' : 'disabled'}`);
}
};
8 changes: 8 additions & 0 deletions modules/auth/tests/auth.abilities.integration.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ describe('Auth abilities integration tests:', () => {
agent = request.agent(init.app);
adminAgent = request.agent(init.app);

// clean up stale users from previous runs on shared databases
for (const email of [userCredentials.email, adminCredentials.email]) {
try {
const existing = await UserService.getBrut({ email });
if (existing) await UserService.remove(existing);
} catch (_) { /* cleanup – ignore errors */ }
}

// Create a regular user
const userRes = await agent
.post('/api/auth/signup')
Expand Down
8 changes: 8 additions & 0 deletions modules/auth/tests/auth.authorization.integration.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ describe('Authorization integration tests:', () => {
adminAgent = request.agent(init.app);
otherAgent = request.agent(init.app);

// clean up stale users from previous runs on shared databases
for (const email of ['auth-test-user@test.com', 'auth-test-admin@test.com', 'auth-test-other@test.com']) {
try {
const existing = await UserService.getBrut({ email });
if (existing) await UserService.remove(existing);
} catch (_) { /* cleanup – ignore errors */ }
}

// Create a normal user via agent
const userRes = await agent
.post('/api/auth/signup')
Expand Down
Loading
Loading