v3.0.0/src/api/main.ts

121 lines
3.8 KiB
TypeScript

// import express from 'express';
import fastify from 'fastify';
import helmet from 'fastify-helmet';
import cors from 'fastify-cors';
import formbody from 'fastify-formbody';
import fstatic from 'fastify-static';
import path from 'path';
import rateLimit from 'fastify-rate-limit';
import jetpack from 'fs-jetpack';
// import cron from 'cron';
// @ts-ignore - nuxt types can't be found - https://github.com/nuxt/nuxt.js/issues/7651
import { Nuxt, Builder } from 'nuxt';
import nuxtDefaults from './structures/nuxt';
import Routes from './structures/routes';
// const server = express();
const server = fastify({
trustProxy: true,
logger: {
serializers: {
req(request) {
return {
ip: request.hostname,
method: request.method,
url: request.url
};
},
res(reply) {
return {
statusCode: reply.statusCode
};
}
},
prettyPrint: {
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l',
ignore: 'pid,hostname,reqId,req,res,responseTime',
/*
TODO:
Find a way to merge incoming request and request completed into 1 line using
messageFormat as a function instead of a string.
Maybe even set a new flag under log like log.app instead of log.info
to be able to print logs without req and res information
[2021-06-21 19:22:21.553] INFO: incoming request [localhost:5000 - GET /api/verify - ]
[2021-06-21 19:22:21.556] INFO: request completed [ - - 401]
*/
messageFormat: '{msg} [{req.ip} - {req.method} {req.url} - {res.statusCode}]'
}
},
connectionTimeout: 600000
});
const start = async () => {
// Create the folders needed for uploads
jetpack.dir('uploads/chunks');
jetpack.dir('uploads/thumbs/square');
jetpack.dir('uploads/thumbs/preview');
// Create the server and set it up
void server.register(helmet);
void server.register(cors, {
allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId', 'finishedChunks']
});
void server.register(formbody);
server.addHook('onRequest', (req, reply, next) => {
// This bypasses the headers.accept for album download, since it's accesed directly through the browser.
if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next();
// This bypasses the headers.accept if we are accessing the frontend
if (!req.url.includes('/api/') && req.method === 'GET') return next();
if (req.headers.accept?.includes('application/vnd.chibisafe.json')) return next();
return reply.status(405).send({ message: 'Incorrect `Accept` header provided' });
});
// Apply rate limiting to the api only
// TODO: Find a way to only apply this to /api routes
void server.register(rateLimit, {
global: false,
max: parseInt(process.env.RATE_LIMIT_MAX ?? '5', 10),
timeWindow: parseInt(process.env.RATE_LIMIT_WINDOW ?? '2000', 10)
});
// Scan and load routes into express
await Routes.load(server);
// Listen for incoming connections
void server.listen(process.env.port ?? 5000);
// Serve the uploads
void server.register(fstatic, {
root: path.join(__dirname, '../../uploads')
});
const nuxtConfig = await nuxtDefaults();
nuxtConfig.dev = !(process.env.NODE_ENV === 'production');
const nuxt = new Nuxt(nuxtConfig);
if (nuxtConfig.dev) {
const builder = new Builder(nuxt);
await builder.build();
} else {
await nuxt.ready();
}
void server.register(nuxt.render);
/*
const isProd = process.env.NODE_ENV === 'production';
const nuxt = await loadNuxt(isProd ? 'start' : 'dev');
void server.register(nuxt.render);
if (!isProd) build(nuxt);
*/
// TODO: move into the database config. (we can just show the crontab line for start, later on we can add dropdowns and stuff)
// new cron.CronJob('0 0 * * * *', Util.saveStatsToDb, null, true);
};
export const log = server.log;
void start();