diff --git a/app/controllers/admin.js b/app/controllers/admin.js index 51c2fbe..f64be5d 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -66,7 +66,11 @@ class AdminController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new AdminController(dtp); - return controller; -}; +module.exports = { + slug: 'admin', + name: 'admin', + create: async (dtp) => { + let controller = new AdminController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 81d7732..c580ffb 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -189,7 +189,11 @@ class AuthController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new AuthController(dtp); - return controller; +module.exports = { + slug: 'auth', + name: 'auth', + create: async (dtp) => { + let controller = new AuthController(dtp); + return controller; + }, }; \ No newline at end of file diff --git a/app/controllers/dashboard.js b/app/controllers/dashboard.js index 8d52a31..08b02db 100644 --- a/app/controllers/dashboard.js +++ b/app/controllers/dashboard.js @@ -93,7 +93,11 @@ class DashboardController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new DashboardController(dtp); - return controller; +module.exports = { + slug: 'dashboard', + name: 'dashboard', + create: async (dtp) => { + let controller = new DashboardController(dtp); + return controller; + }, }; \ No newline at end of file diff --git a/app/controllers/home.js b/app/controllers/home.js index 0edf272..f2ca8ed 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -8,7 +8,7 @@ const DTP_COMPONENT_NAME = 'home'; const express = require('express'); -const { SiteController } = require('../../lib/site-lib'); +const { SiteController, SiteError } = require('../../lib/site-lib'); class HomeController extends SiteController { @@ -20,7 +20,7 @@ class HomeController extends SiteController { const { dtp } = this; const { limiter: limiterService, sidebar: sidebarService } = dtp.services; - const router = express.Router(); + const router = this.router = express.Router(); dtp.app.use('/', router); router.param('username', this.populateUsername.bind(this)); @@ -58,7 +58,7 @@ class HomeController extends SiteController { try { this.log.debug('profile request', { url: req.url }); if (!res.locals.userProfile) { - return next(); + return next(new SiteError(404, 'Member profile not found')); } res.locals.currentView = 'public-profile'; @@ -94,7 +94,12 @@ class HomeController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new HomeController(dtp); - return controller; -}; +module.exports = { + slug: 'home', + name: 'home', + isHome: true, + create: async (dtp) => { + let controller = new HomeController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/image.js b/app/controllers/image.js index d3acd73..ea738ba 100644 --- a/app/controllers/image.js +++ b/app/controllers/image.js @@ -135,7 +135,11 @@ class ImageController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new ImageController(dtp); - return controller; +module.exports = { + slug: 'image', + name: 'image', + create: async (dtp) => { + let controller = new ImageController(dtp); + return controller; + }, }; \ No newline at end of file diff --git a/app/controllers/link.js b/app/controllers/link.js index e856a45..a8cbf8b 100644 --- a/app/controllers/link.js +++ b/app/controllers/link.js @@ -187,7 +187,11 @@ class LinkController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new LinkController(dtp); - return controller; +module.exports = { + slug: 'link', + name: 'link', + create: async (dtp) => { + let controller = new LinkController(dtp); + return controller; + }, }; \ No newline at end of file diff --git a/app/controllers/manifest.js b/app/controllers/manifest.js index a10d4d0..8704591 100644 --- a/app/controllers/manifest.js +++ b/app/controllers/manifest.js @@ -65,7 +65,11 @@ class ManifestController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new ManifestController(dtp); - return controller; -}; +module.exports = { + slug: 'manifest', + name: 'manifest', + create: async (dtp) => { + let controller = new ManifestController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/newsletter.js b/app/controllers/newsletter.js index a3a6ce2..a15df85 100644 --- a/app/controllers/newsletter.js +++ b/app/controllers/newsletter.js @@ -94,7 +94,11 @@ class NewsletterController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new NewsletterController(dtp); - return controller; -}; +module.exports = { + slug: 'newsletter', + name: 'newsletter', + create: async (dtp) => { + let controller = new NewsletterController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/user.js b/app/controllers/user.js index 51a77a5..2f2b4d0 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -179,7 +179,11 @@ class UserController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new UserController(dtp); - return controller; -}; +module.exports = { + slug: 'user', + name: 'user', + create: async (dtp) => { + let controller = new UserController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/welcome.js b/app/controllers/welcome.js index 5a9964e..f9216fe 100644 --- a/app/controllers/welcome.js +++ b/app/controllers/welcome.js @@ -68,7 +68,11 @@ class WelcomeController extends SiteController { } } -module.exports = async (dtp) => { - let controller = new WelcomeController(dtp); - return controller; +module.exports = { + slug: 'welcome', + name: 'welcome', + create: async (dtp) => { + let controller = new WelcomeController(dtp); + return controller; + }, }; \ No newline at end of file diff --git a/app/services/user.js b/app/services/user.js index 3cef3b8..bf92c65 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -47,7 +47,7 @@ class UserService { // strip characters we don't want to allow in username userDefinition.username = userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, ''); const username_lc = userDefinition.username.toLowerCase(); - this.checkUsername(username_lc); + await this.checkUsername(username_lc); // test the email address for validity, blacklisting, etc. await mailService.checkEmailAddress(userDefinition.email); @@ -337,10 +337,7 @@ class UserService { if (settings.username && (settings.username !== user.username)) { update.username = this.filterUsername(settings.username); - const isReserved = await this.isUsernameReserved(update.username); - if (!isReserved) { - throw new SiteError(403, 'The username you entered is taken'); - } + await this.checkUsername(update.username); } if (settings.email && (settings.email !== user.email)) { @@ -395,42 +392,43 @@ class UserService { return striptags(username.trim().toLowerCase()).replace(/\W/g, ''); } - async isUsernameReserved (username) { - const reservedNames = ['digitaltelepresence', 'dtp', 'rob', 'amy', 'zack']; - if (reservedNames.includes(username)) { - this.log.alert('prohibiting use of reserved username', { username }); - return true; - } - - const user = await User.findOne({ username: username}).select('username').lean(); - if (user) { - this.log.alert('username is already registered', { username }); - return true; - } - - return false; - } - - checkUsername (username) { + async checkUsername (username) { if (!username || (typeof username !== 'string') || (username.length === 0)) { throw new SiteError(406, 'Invalid username'); } const reservedNames = [ + 'about', 'admin', + 'amy', 'auth', + 'digitaltelepresence', 'dist', + 'dtp', + 'fontawesome', 'fonts', 'img', 'image', 'less', 'manifest.json', + 'moment', 'newsletter', + 'numeral', + 'rob', + 'socket.io', + 'uikit', 'user', 'welcome', + 'zack' ]; if (reservedNames.includes(username.trim().toLowerCase())) { throw new SiteError(403, 'That username is reserved for system use'); } + + const user = await User.findOne({ username: username}).select('username').lean(); + if (user) { + this.log.alert('username is already registered', { username }); + throw new SiteError(403, 'Username is already registered'); + } } async recordProfileView (user, req) { diff --git a/dtp-libertylinks.js b/dtp-libertylinks.js index 2deef8e..bd3e061 100644 --- a/dtp-libertylinks.js +++ b/dtp-libertylinks.js @@ -12,7 +12,7 @@ const { SitePlatform, SiteLog } = require(path.join(__dirname, 'lib', 'site-lib' module.pkg = require(path.join(__dirname, 'package.json')); module.config = { - componentName: 'sites', + componentName: 'links-web', root: __dirname, site: { name: process.env.DTP_SITE_NAME, diff --git a/gulpfile.js b/gulpfile.js index 83e325e..dab5705 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -270,7 +270,7 @@ function dtp_develop (done) { }) .on('readable', function ( ) { this.stdout.on('data', (chunk) => { - if (/sites platform online/.test(chunk)) { + if (/platform online/.test(chunk)) { if (isFirst) { util_start_browsersync(); isFirst = false; diff --git a/lib/site-controller.js b/lib/site-controller.js index 96c7534..2b550ce 100644 --- a/lib/site-controller.js +++ b/lib/site-controller.js @@ -18,7 +18,6 @@ class SiteController extends SiteCommon { } async loadChild (filename) { - this.log.info('loading child controller', { script: filename }); let child = await require(filename)(this.dtp); return await child.start(); } diff --git a/lib/site-log.js b/lib/site-log.js index 8f6789d..9389e61 100644 --- a/lib/site-log.js +++ b/lib/site-log.js @@ -8,6 +8,8 @@ const util = require('util'); const moment = require('moment'); const rfs = require('rotating-file-stream'); +const color = require('ansicolor'); + var LogModel, LogStream; if (process.env.DTP_LOG_FILE === 'enabled') { @@ -74,16 +76,47 @@ class SiteLog { async writeLog (level, message, metadata) { const NOW = new Date(); const { componentName } = this; + if (process.env.DTP_LOG_CONSOLE === 'enabled') { + let clevel = level.padEnd(5); + switch (level) { + case 'debug': + clevel = color.black(clevel); + break; + case 'info': + clevel = color.green(clevel); + break; + case 'warn': + clevel = color.yellow(clevel); + break; + case 'alert': + clevel = color.red(clevel); + break; + case 'error': + clevel = color.bgRed.white(clevel); + break; + case 'crit': + clevel = color.bgRed.yellow(clevel); + break; + case 'fatal': + clevel = color.bgRed.black(clevel); + break; + } + + const ctimestamp = color.black(moment(NOW).format('YYYY-MM-DD HH:mm:ss.SSS')); + const ccomponentName = color.cyan(componentName); + const cmessage = color.darkGray(message); if (metadata) { - console.log(`${moment(NOW).format('YYYY-MM-DD HH:mm:ss.SSS')} ${componentName} ${level} ${message}`, util.inspect(metadata, false, Infinity, true)); + console.log(`${ctimestamp} ${clevel} ${ccomponentName} ${cmessage}`, util.inspect(metadata, false, Infinity, true)); } else { - console.log(`${moment(NOW).format('YYYY-MM-DD HH:mm:ss.SSS')} ${componentName} ${level} ${message}`); + console.log(`${ctimestamp} ${clevel} ${ccomponentName} ${cmessage}`); } } + if (LogModel && (process.env.DTP_LOG_MONGODB === 'enabled')) { await LogModel.create({ created: NOW, level, componentName, message, metadata }); } + if (LogStream && (process.env.DTP_LOG_FILE === 'enabled')) { const logEntry = { t: NOW, c: componentName, l: level, m: message, d: metadata, diff --git a/lib/site-platform.js b/lib/site-platform.js index 1afe1e6..8fe4a1f 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -57,10 +57,6 @@ module.loadModels = async (dtp) => { module.log.error('model name collision', { name: model.modelName }); process.exit(-1); } - module.log.info('data model loaded', { - name: model.modelName, - collection: model.collection.collectionName, - }); module.models[model.modelName] = model; }); }; @@ -109,12 +105,10 @@ module.loadServices = async (dtp) => { const scripts = glob.sync(path.join(dtp.config.root, 'app', 'services', '*.js')); const inits = [ ]; await SiteAsync.each(scripts, async (script) => { - module.log.info('service', { script }); const service = await require(script); module.services[service.name] = service.create(dtp); module.services[service.name].__dtp_service_name = service.name; inits.push(module.services[service.name]); - module.log.info('service loaded', { name: service.name }); }); await SiteAsync.each(inits, async (service) => { await service.start(); @@ -124,13 +118,43 @@ module.loadServices = async (dtp) => { module.loadControllers = async (dtp) => { const scripts = glob.sync(path.join(dtp.config.root, 'app', 'controllers', '*.js')); const inits = [ ]; + + dtp.controllers = { }; + await SiteAsync.each(scripts, async (script) => { - module.log.info('controller', { script }); - const controller = await require(script)(dtp); + const controller = await require(script); + controller.instance = await controller.create(dtp); + + dtp.controllers[controller.name] = controller; + inits.push(controller); }); + await SiteAsync.each(inits, async (controller) => { - await controller.start(); + if (controller.isHome) { + return; // must run last + } + await controller.instance.start(); + }); + + /* + * Start the Home controller + */ + await dtp.controllers.home.instance.start(); + + /* + * Default error handler + */ + module.log.info('registering ExpressJS error handler'); + dtp.app.use((error, req, res, next) => { // jshint ignore:line + res.locals.errorCode = error.statusCode || error.status || error.code || 500; + module.log.error('ExpressJS error', { url: req.url, error }); + res.status(res.locals.errorCode).render('error', { + message: error.message, + error, + errorCode: res.locals.errorCode, + }); + // return next(error); }); }; @@ -315,16 +339,6 @@ module.exports.startWebServer = async (dtp) => { return; } - module.app.use(async (err, req, res, next) => { // jshint ignore:line - var errorCode = err.status || err.statusCode || err.code || 500; - module.log.error('HTTP error', { error: err }); - res.status(errorCode).render('error', { - message: err.message, - error: err, - title: 'error' - }); - }); - module.log.info('creating HTTP server'); module.http = require('http').createServer(module.app); diff --git a/lib/site-service.js b/lib/site-service.js index 024fd4b..a610921 100644 --- a/lib/site-service.js +++ b/lib/site-service.js @@ -19,11 +19,11 @@ class SiteService extends SiteCommon { } async start ( ) { - this.log.info(`starting ${this.name} service`); + this.log.debug(`starting ${this.name} service`); } async stop ( ) { - this.log.info(`stopping ${this.name} service`); + this.log.debug(`stopping ${this.name} service`); } } diff --git a/package.json b/package.json index 9a12ec3..f748b35 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@joeattardi/emoji-button": "^4.6.2", "@socket.io/redis-adapter": "^7.1.0", "anchorme": "^2.1.2", + "ansicolor": "^1.1.95", "argv": "^0.0.2", "bull": "^4.1.1", "chart.js": "^3.6.2", diff --git a/yarn.lock b/yarn.lock index e5b8401..eb949ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1424,6 +1424,11 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= +ansicolor@^1.1.95: + version "1.1.95" + resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-1.1.95.tgz#978c494f04793d6c58115ba13a50f56593f736c6" + integrity sha512-R4yTmrfQZ2H9Wr5TZoM2iOz0+T6TNHqztpld7ZToaN8EaUj/06NG4r5UHQfegA9/+K/OY3E+WumprcglbcTMRA== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"