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-06-19 11:54:38 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/nodash'),
       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             :           license: config.license
      75             :         },
      76             :         basePath: config.basePath
      77             :       }
      78             :     };
      79           1 :     this.camService = new CamService(config.camApp, swaggerOpts);
      80             : 
      81           1 :     if (config.auth) {
      82           1 :       this.router?.put("/*",
      83             :         auth.roleCheck.bind(null, [ "expert", "operator" ]));
      84             :     }
      85             : 
      86           1 :     this.camService.register(this.router);
      87             : 
      88           1 :     this.app.use(config.basePath, this.router);
      89             : 
      90             :     // default route
      91           1 :     this.app.use(function(req, res, next) {
      92           0 :       next({ status: 404, message: 'Not Found' });
      93             :     });
      94             : 
      95             :     // error handler
      96           1 :     this.app.use(function(
      97             :       /** @type {any} */ err,
      98             :       /** @type {Request} */ req,
      99             :       /** @type {Response} */res,
     100             :       /** @type {NextFunction} */ next) { /* eslint-disable-line */ /* jshint ignore:line */
     101           0 :       res.locals.message = err.message;
     102           0 :       res.locals.error = req.app.get('env') === 'development' ? err : {};
     103           0 :       res.locals.status = err.status || 500;
     104           0 :       res.status(err.status || 500);
     105           0 :       res.render('error', config);
     106             :     });
     107             :   }
     108             : 
     109             :   close() {
     110           1 :     if (this.camService) {
     111           1 :       this.camService.release();
     112           1 :       this.camService = null;
     113             :     }
     114           1 :     if (this.server) {
     115           1 :       this.server.close();
     116           1 :       this.server = null;
     117             :     }
     118             :   }
     119             : 
     120             :   /**
     121             :    * @param {string|string[]} role
     122             :    * @param {Request} req
     123             :    * @param {Response} res
     124             :    * @param {NextFunction} next
     125             :    */
     126             :   runAuth(role, req, res, next) {
     127           0 :     if (req.path.startsWith('/dist')) {
     128           0 :       return next();
     129             :     }
     130           0 :     return auth.roleCheck(role, req, res, next);
     131             :   }
     132             : 
     133             :   /**
     134             :    * @param {() => any} cb
     135             :    */
     136             :   async listen(cb) {
     137           1 :     await this._prom;
     138           1 :     const def = makeDeferred();
     139             :     /* we're called as a main, let's listen */
     140           1 :     var server = this.app.listen(this.config.port, () => {
     141           1 :       this.server = server;
     142           1 :       def.resolve(undefined);
     143           1 :       return attempt(cb || noop);
     144             :     });
     145           1 :     return def.promise;
     146             :   }
     147             : 
     148             :   address() {
     149           1 :     if (this.server) {
     150           1 :       return this.server.address();
     151             :     }
     152           0 :     return null;
     153             :   }
     154             : }
     155             : 
     156           1 : module.exports = Server;

Generated by: LCOV version 1.16