import Fastify, { type FastifyReply, type FastifyRequest } from "fastify"; import FastifyFormBody from "@fastify/formbody"; import FastifyMultipart from "@fastify/multipart"; import FastifyCookie from "@fastify/cookie"; import FastifyView from "@fastify/view"; import FastifyStatic from "@fastify/static"; import Config from "./objects/Config"; import EJS from "ejs"; import { Console } from "hsconsole"; import Controller from "./controllers/Controller"; import HomeController from "./controllers/HomeController"; import Database from "./objects/Database"; import { join } from "path"; import AccountController from "./controllers/AccountController"; import { magenta, blue, cyan, green, red } from "dyetty"; import ConsoleUtility from "./utilities/ConsoleUtility"; import HashFS from "./objects/HashFS"; import FunkyArray from "funky-array"; import MediaService from "./services/MediaService"; import Media from "./entities/Media"; import HeaderUtility from "./utilities/HeaderUtility"; import { createReadStream } from "fs"; Console.customHeader(`EUS server started at ${new Date()}`); const fastify = Fastify({ logger: false }); fastify.register(FastifyView, { engine: { ejs: EJS } }); fastify.register(FastifyFormBody); fastify.register(FastifyMultipart, { limits: { files: 1, fileSize: 104857600 } }); fastify.register(FastifyCookie, { secret: Config.session.secret, parseOptions: { path: "/", secure: true } }); fastify.register(FastifyStatic, { root: join(__dirname, "wwwroot"), preCompressed: true, decorateReply: false, redirect: false }); function printReqInfo(req: FastifyRequest, res: FastifyReply) { // @ts-ignore Console.printInfo(`[ ${req.logType} ] [ ${req.method.toUpperCase()} ] [ ${ConsoleUtility.StatusColor(res.statusCode)} ] [ ${blue(`${Date.now() - req.startTime}ms`)} ] > ${req.url}`); } const hashLookupCache = new FunkyArray(); fastify.addHook("preHandler", (req, res, done) => { (async () => { // @ts-ignore req.startTime = Date.now(); // * Take usual controller path if this path is registered. if (Controller.RegisteredPaths.includes(req.url)) { // @ts-ignore req.logType = cyan("CONTROLLER"); HeaderUtility.AddBakedHeaders(res); return done(); } else { const urlParts = req.url.split("/"); if (urlParts.length === 2 && urlParts[1].length <= 16) { let media = hashLookupCache.get(urlParts[1]) ?? null; if (!media) { media = await MediaService.GetByTag(urlParts[1]); if (media) { hashLookupCache.set(urlParts[1], media); } } if (media) { // @ts-ignore req.logType = cyan("MEDIA"); const fileStore = HashFS.GetHashFSInstance("images"); const readStream = createReadStream(join(fileStore.path, fileStore.GetRelativePath(media.Hash))); res.raw.writeHead(200, HeaderUtility.CombineHeaders({ "content-type": media.MediaType, "content-length": media.FileSize, })); readStream.pipe(res.raw); printReqInfo(req, res); return; } } else { HeaderUtility.AddBakedHeaders(res); } // @ts-ignore req.logType = magenta("STATIC"); } return done(); })(); }); fastify.addHook("onSend", (req, res, _payload, done) => { printReqInfo(req, res); done(); }); fastify.setNotFoundHandler(async (_req, res) => { return res.status(404).view("views/404.ejs"); }); HashFS.STARTUP_DIR = __dirname; new HashFS("images"); if (Config.database.enabled) { new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name); } else { Console.printInfo(`[ ${red("DATABASE")} ] Database is disabled.`); } if (Config.controllers.enabled && Config.database.enabled) { Controller.FastifyInstance = fastify; new AccountController(); new HomeController(); } else { Console.printInfo(`[ ${red("CONTROLLER")} ] Controllers are disabled${Config.controllers.enabled && !Config.database.enabled ? " because the database is disabled but required by the controllers." : "."} Server will operate in static mode only.`); } fastify.listen({ port: Config.hosts.webPort, host: Config.hosts.webHost }, (err, address) => { if (err) { Console.printError(`Error occured while spinning up fastify:\n${err}`); process.exit(1); } Console.printInfo(`[ ${green("MAIN")} ] Listening at ${address.replace("0.0.0.0", "localhost").replace("127.0.0.1", "localhost")}`); });