web.js

const config = require('../../config.json');
const express = require('express');
const path = require('path');
const _ = require('lodash');
const Op = require('sequelize').Op
const morgan = require('morgan');
const cluster = require('cluster');

const port = config.webserver.port;

/**
 * Express Request Object
 * 
 * @external Request
 * @see {@link https://expressjs.com/en/api.html#req Request}
 */

/**
 * Express Response Object
 *
 * @external Response
 * @see {@link https://expressjs.com/en/api.html#res Response}
 */

/**
 * Class to control the webserver & routes
 */
class Web {

    constructor() {

        if (!config.webserver.port) {
            throw new Error('Must provide a port in config when the webserver is enabled');
        }

        this.app = express();
        this.app.set('view engine', 'ejs');
        this.app.use(morgan("combined", {
            stream: {
                write: (message) => global.logger.info(message)
            }
        }));

        // Routes
        this.app.get('/', this.indexRoute);
        this.app.get('/player/:id', this.playerIdRoute);
        this.app.get('/api/overview', this.ApiOverviewRoute);
        this.app.get('/api/player/:id', this.ApiPlayerInfoRoute);
        this.app.get('/api/historicalData', this.getHistoricalDataRoute);

        this.app.use('/static', express.static(path.resolve(__dirname, '../../static')));
        this.app.listen(port, () => {
            if (cluster.isMaster) {
                global.logger.info(`Webserver listening on port ${port}!`)
            }
        })
    }

    /**
     * Serves the index page
     * @param {external:Request} req 
     * @param {external:Response} res 
     */
    indexRoute(req, res) {
        res.render('index', {
            name: config.serverName
        });
    }

    /**
     * Serves the player profile page
     * @param {external:Request} req 
     * @param {external:Response} res 
     */
    async playerIdRoute(req, res) {
        const player = await global.models.Player.findByPk(req.params.id);

        if (_.isNull(player)) {
            res.status(404)
                .send('No player with that ID found.');
            return res.end();
        }

        res.render('profile', {
            name: config.serverName
        });
    }

    /**
     * Returns general info about all players
     * @param {external:Request} req
     * @param {external:Response} res
     */
    async ApiOverviewRoute(req, res) {
        const data = await global.models.Player.findAll({
            attributes: ['id', 'name', 'score', 'kills', 'deaths', 'headshots', 'steam'],
            limit: 1000,
            order: [
                ['score', 'DESC']
            ]
        });
        res.json(data);
        return res.end();
    }

    /**
     * Returns current stats for a single player
     * @param {external:Request} req
     * @param {external:Response} res
     */
    async ApiPlayerInfoRoute(req, res) {
        const data = await global.models.Player.findOne({
            attributes: {
                exclude: 'lastip'
            },
            where: {
                id: req.params.id
            }
        });

        if (_.isNull(data)) {
            res.status(404)
                .send('No player with that ID found.');
            return res.end();
        }

        res.json(data);
        return res.end();
    }

    /**
     * 
     * @param {external:Request} req 
     * @param {external:Response} res 
     */
    async getHistoricalDataRoute(req, res) {

        if (_.isUndefined(req.query.endDate)) {
            req.query.endDate = Date.now();
        }

        if (_.isUndefined(req.query.startDate)) {
            // Default to week-old data
            req.query.startDate = Date.now() - 604800000;
        }

        const whereObj = {
            [Op.and]: {
                createdAt: {
                    [Op.gte]: req.query.startDate,
                    [Op.lte]: req.query.endDate
                },
            }
        }

        if (req.query.steam !== undefined) {
            whereObj[Op.and].steam = req.query.steam
        }

        const startDate = new Date(req.query.startDate);
        const endDate = new Date(req.query.endDate);
        const data = await global.models.HistoricalData.findAll({
            where: whereObj,
            raw: true,
        });

        res.json(data);
        return res.end();
    }

}

module.exports = Web;