1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-21 06:09:41 +02:00
codex.docs/src/backend/server.ts

211 lines
5.2 KiB
TypeScript
Raw Normal View History

#!/usr/bin/env node
/**
* Module dependencies.
*/
import http from 'http';
import Debug from 'debug';
import appConfig from './utils/appConfig.js';
import { drawBanner } from './utils/banner.js';
import express, { NextFunction, Request, Response } from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import HawkCatcher from '@hawk.so/nodejs';
import os from 'os';
import { downloadFavicon, FaviconData } from './utils/downloadFavicon.js';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import routes from './routes/index.js';
import HttpException from './exceptions/httpException.js';
const debug = Debug.debug('codex.docs:server');
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(appConfig.port.toString() || '3000');
/**
* Create Express server
*/
function createApp(): express.Express {
/**
* The __dirname CommonJS variables are not available in ES modules.
* https://nodejs.org/api/esm.html#no-__filename-or-__dirname
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const localConfig = appConfig.frontend;
// Initialize the backend error tracking catcher.
if (appConfig.hawk?.backendToken) {
HawkCatcher.init(appConfig.hawk.backendToken);
}
// Get url to upload favicon from config
const favicon = appConfig.favicon;
app.locals.config = localConfig;
// Set client error tracking token as app local.
if (appConfig.hawk?.frontendToken) {
app.locals.config.hawkClientToken = appConfig.hawk.frontendToken;
}
// view engine setup
app.set('views', path.join(__dirname, './', 'views'));
app.set('view engine', 'twig');
import('./utils/twig.js');
const downloadedFaviconFolder = os.tmpdir();
// Check if favicon is not empty
if (favicon) {
// Upload favicon by url, it's path on server is '/temp/favicon.{format}'
downloadFavicon(favicon, downloadedFaviconFolder).then((res) => {
app.locals.favicon = res;
console.log('Favicon successfully uploaded');
})
.catch((err) => {
console.log(err);
console.log('Favicon has not uploaded');
});
} else {
console.log('Favicon is empty, using default path');
app.locals.favicon = {
destination: '/favicon.png',
type: 'image/png',
} as FaviconData;
}
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../../public')));
if (appConfig.uploads.driver === 'local') {
app.use('/uploads', express.static(appConfig.uploads.local.path));
}
app.use('/favicon', express.static(downloadedFaviconFolder));
app.use('/', routes);
// global error handler
app.use(function (err: unknown, req: Request, res: Response, next: NextFunction) {
// send any type of error to hawk server.
if (appConfig.hawk?.backendToken && err instanceof Error) {
HawkCatcher.send(err);
}
// only send Http based exception to client.
if (err instanceof HttpException) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
}
next(err);
});
return app;
}
/**
* Create and run HTTP server.
*/
export default function runHttpServer(): void {
const app = createApp();
app.set('port', port);
/**
* Create HTTP server.
*/
const server = http.createServer(app);
/**
* Event listener for HTTP server 'listening' event.
*/
function onListening(): void {
const addr = server.address();
if (addr === null) {
debug('Address not found');
process.exit(1);
}
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
drawBanner([
`CodeX Docs server is running`,
``,
`Main page: http://localhost:${port}`,
]);
}
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
}
/**
* Normalize a port into a number, string, or false.
*
* @param val
*/
function normalizePort(val: string): number | string | false {
const value = parseInt(val, 10);
if (isNaN(value)) {
// named pipe
return val;
}
if (value >= 0) {
// port number
return value;
}
return false;
}
/**
* Event listener for HTTP server 'error' event.
*
* @param error
*/
function onError(error: NodeJS.ErrnoException): void {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}