LCOV - code coverage report
Current view: top level - src - Server.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 52 63 82.5 %
Date: 2024-07-30 12:54:47 Functions: 6 10 60.0 %

          Line data    Source code
       1             : // @ts-check
       2             : const
       3           1 :   express = require('express'),
       4           1 :   helmet = require('helmet'),
       5           1 :   serveStatic = require('express-static-gzip'),
       6           1 :   path = require('path'),
       7           1 :   { attempt, noop } = require('lodash'),
       8           1 :   { makeDeferred } = require('@cern/prom'),
       9           1 :   ews = require('express-ws'),
      10             : 
      11           1 :   { CamService } = require('@cern/cam-express'),
      12           1 :   logger = require('./httpLogger'),
      13           1 :   auth = require('./auth');
      14             : 
      15             : /**
      16             :  * @typedef {import('express').Request} Request
      17             :  * @typedef {import('express').Response} Response
      18             :  * @typedef {import('express').NextFunction} NextFunction
      19             :  */
      20             : 
      21             : class Server {
      22             :   /**
      23             :    * @param {AppServer.Config} config
      24             :    */
      25             :   constructor(config) {
      26           1 :     this.config = config;
      27           1 :     this.app = express();
      28           1 :     this.camService = null;
      29           1 :     this._prom = this.prepare(config);
      30             :   }
      31             : 
      32             :   /**
      33             :    * @param {AppServer.Config} config
      34             :    */
      35             :   async prepare(config) {
      36           1 :     if (process.env.NODE_ENV === 'production') {
      37           0 :       this.app.use(helmet.contentSecurityPolicy({
      38             :         useDefaults: true,
      39             :         directives: {
      40             :           imgSrc: [ "'self'", "data:", "blob:" ]
      41             :         }
      42             :       }));
      43             :     }
      44           1 :     logger.register(this.app);
      45           1 :     ews(this.app);
      46             : 
      47           1 :     this.router = express.Router();
      48           1 :     const dist = path.join(__dirname, '..', 'www', 'dist');
      49           1 :     this.router.use('/dist', serveStatic(dist, { enableBrotli: true }));
      50             : 
      51             :     /* everything after this point is authenticated */
      52           1 :     if (config.auth) {
      53           1 :       await auth.register(this.router, config);
      54           1 :       this.router.use(this.runAuth.bind(this, 'user'));
      55             :     }
      56             : 
      57           1 :     this.app.set('view engine', 'pug');
      58           1 :     this.app.set('views', path.join(__dirname, 'views'));
      59             : 
      60           1 :     this.router.get('/', (req, res) => res.render('index', config));
      61             : 
      62           1 :     if (config.auth) {
      63             :       // protected admin path for admin role only
      64           1 :       this.router.use('/admin', this.runAuth.bind(this, [ 'admin' ]));
      65             :     }
      66             : 
      67             :     /* NOTE: declare your additional endpoints here */
      68           1 :     const swaggerOpts = {
      69             :       definition: {
      70             :         info: {
      71             :           title: config.title,
      72             :           description: 'Web App API to interact with the back-end',
      73             :           contact: config.contact
      74             :         },
      75             :         basePath: config.basePath
      76             :       }
      77             :     };
      78           1 :     this.camService = new CamService(config.camApp, swaggerOpts);
      79             : 
      80           1 :     if (config.auth) {
      81           1 :       this.router?.put("/*",
      82             :         auth.roleCheck.bind(null, [ "expert", "operator" ]));
      83             :     }
      84             : 
      85           1 :     this.camService.register(this.router);
      86             : 
      87           1 :     this.app.use(config.basePath, this.router);
      88             : 
      89             :     // default route
      90           1 :     this.app.use(function(req, res, next) {
      91           0 :       next({ status: 404, message: 'Not Found' });
      92             :     });
      93             : 
      94             :     // error handler
      95           1 :     this.app.use(function(
      96             :       /** @type {any} */ err,
      97             :       /** @type {Request} */ req,
      98             :       /** @type {Response} */res,
      99             :       /** @type {NextFunction} */ next) { /* eslint-disable-line */ /* jshint ignore:line */
     100           0 :       res.locals.message = err.message;
     101           0 :       res.locals.error = req.app.get('env') === 'development' ? err : {};
     102           0 :       res.locals.status = err.status || 500;
     103           0 :       res.status(err.status || 500);
     104           0 :       res.render('error', config);
     105             :     });
     106             :   }
     107             : 
     108             :   close() {
     109           1 :     if (this.camService) {
     110           1 :       this.camService.release();
     111           1 :       this.camService = null;
     112             :     }
     113           1 :     if (this.server) {
     114           1 :       this.server.close();
     115           1 :       this.server = null;
     116             :     }
     117             :   }
     118             : 
     119             :   /**
     120             :    * @param {string|string[]} role
     121             :    * @param {Request} req
     122             :    * @param {Response} res
     123             :    * @param {NextFunction} next
     124             :    */
     125             :   runAuth(role, req, res, next) {
     126           0 :     if (req.path.startsWith('/dist')) {
     127           0 :       return next();
     128             :     }
     129           0 :     return auth.roleCheck(role, req, res, next);
     130             :   }
     131             : 
     132             :   /**
     133             :    * @param {() => any} cb
     134             :    */
     135             :   async listen(cb) {
     136           1 :     await this._prom;
     137           1 :     const def = makeDeferred();
     138             :     /* we're called as a main, let's listen */
     139           1 :     var server = this.app.listen(this.config.port, () => {
     140           1 :       this.server = server;
     141           1 :       def.resolve(undefined);
     142           1 :       return attempt(cb || noop);
     143             :     });
     144           1 :     return def.promise;
     145             :   }
     146             : 
     147             :   address() {
     148           1 :     if (this.server) {
     149           1 :       return this.server.address();
     150             :     }
     151           0 :     return null;
     152             :   }
     153             : }
     154             : 
     155           1 : module.exports = Server;

Generated by: LCOV version 1.16