// HSNOTE: It is designed to be used with bun but there is an issue with // bun's implementation of node:http so i've temp allowed this to // run with node again. very disapointed. // if (!process.versions.bun) { // console.log("EUS is only designed to run on Bun, sorry!"); // process.exit(1); // } 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 FastifySend from "@fastify/send"; 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"; import ApiController from "./controllers/ApiController"; import UploadController from "./controllers/UploadController"; import DomainService from "./services/DomainService"; 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.split("?")[0])) { // @ts-ignore req.logType = cyan("CONTROLLER"); HeaderUtility.AddBakedHeaders(res); return done(); } else { const domain = await DomainService.LoadDomainByHost(req.headers.host ?? ""); if (domain) { 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 = "REDIRECT_TO_MEDIA"; res.redirect(`${domain.HasHttps ? "https" : "http"}://${domain.MediaDomain}/${media.MediaTag}`); return;*/ // @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) => { // @ts-ignore if (req.logType !== "REDIRECT_TO_MEDIA") { 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 ApiController(); if (Config.controllers["home-enabled"]) { new HomeController(); } new UploadController(); } 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.printWarn(`[ ${green("MAIN")} ] The EUS rewrite is currently beta software, use at your own risk!`); Console.printInfo(`[ ${green("MAIN")} ] Listening at ${address.replace("0.0.0.0", "localhost").replace("127.0.0.1", "localhost")}`); }); /*const mediaServer = createServer(async (req, res) => { const startTime = Date.now(); 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) { const fileStore = HashFS.GetHashFSInstance("images"); const { statusCode, headers, stream } = await FastifySend(req, fileStore.GetRelativePath(media.Hash), { root: fileStore.path }); headers["content-type"] = media.MediaType; res.writeHead(statusCode, HeaderUtility.CombineHeaders(headers)); stream.pipe(res); Console.printInfo(`[ ${cyan("MEDIA")} ] [ ${req.method?.toUpperCase()} ] [ ${ConsoleUtility.StatusColor(res.statusCode)} ] [ ${blue(`${Date.now() - startTime}ms`)} ] > ${req.url}`); return; } else { } } res.statusCode = 404; res.write("EUS Media Server"); return res.end(); }); mediaServer.listen(Config.hosts.webPort + 1, () => { Console.printInfo(`[ ${cyan("MEDIA")} ] Listening at http://localhost:${(Config.hosts.webPort + 1)}`); });*/