import Fastify from "fastify"; import config from "@/lib/config"; import { accessSync, constants as fsConst } from "node:fs"; import staticStream from "@fastify/static"; import { styleText } from "node:util"; import Routes from "@/routes"; import Database from "@/lib/db"; import Logger from "@/lib/logger"; import AccessLog from "@/lib/access"; import Authorization from "@/lib/auth"; import { RequestContext } from "@mikro-orm/core"; import { DatabaseError, ErrorBase } from "@/errors"; import { ConfigEntity } from "@/modules/entities/Config"; process.title = "LynqChat"; const logger = new Logger("Core"); logger.info("Process started..."); if (process.env.NODE_ENV !== "production") { process.title = "LynqChat backend"; logger.warn(styleText(["red", "bold", "bgYellow"], "Development environment avaiable!!")); } const fastify = Fastify({ logger: false, trustProxy: config.server.trustProxy, bodyLimit: 1024 * 100, }); try { fastify.setErrorHandler((err, req, res) => { res.header("Content-Type", "application/json"); const customLogger = new Logger("Unknown(Error handler)"); customLogger.error("Unknown error:", err); return res.status(500).send(ErrorBase({ bad: "server", code: "unknown_error", message: "不明なエラーが発生しました。", })); }); if (process.env.NODE_ENV === "production") { try { accessSync(`${import.meta.dirname}/../../frontend/dist/index.html`, fsConst.R_OK); await fastify.register(staticStream, { root: `${import.meta.dirname}/../../frontend/dist`, index: "/", }); } catch (err) { logger.error("It's in production but the frontend dist is not found."); process.exit(1); } } fastify.setNotFoundHandler((req, res) => { if (req.url.startsWith("/api")) { res.header("Content-Type", "application/json"); return res.code(404).send(ErrorBase({ bad: "client", code: "endpoint_not_found", message: "エンドポイントが見つかりませんでした。", })); } if (process.env.NODE_ENV === "production") { return res.sendFile("index.html"); } return res.code(500).send(); }); await fastify.register(Database); fastify.addHook("onRequest", (req, res, done) => { RequestContext.create(fastify.orm.em, done); }); fastify.addHook("onRequest", async (req, res) => { if ( req.url.startsWith("/api") && !req.url.startsWith("/api/setup") ) { try { const configCount = await fastify.orm.em.count(ConfigEntity); if (configCount === 0) { return res.code(409).send(ErrorBase({ bad: "client", code: "yet_initialization", message: "初期設定が行われていません。", })); } } catch (err) { const customLogger = new Logger("Check already initialized hook"); customLogger.error("Database error: Could not check if already initialization:", err); return res.code(500).send(DatabaseError()); } } }); await fastify.register(AccessLog); await fastify.register(Authorization); fastify.removeAllContentTypeParsers(); fastify.addContentTypeParser("application/json", { parseAs: "string" }, (req, body, done) => { try { const json = JSON.parse( typeof body === "string" ? body : body.toString("utf-8") ); done(null, json); } catch (err) { done(null, {}); } }); await fastify.register(Routes, { prefix: "/api", }); const addr = await fastify.listen({ port: config.server.port, host: config.server.host, }); if (process.env.NODE_ENV === "production") { logger.info(`Listening at ${addr}`); } else { logger.info(`Backend listening at ${addr}`); } } catch (err) { logger.error(err); process.exit(1); } const shutdown = async () => { try { await fastify.close(); logger.log("Server downed."); process.exit(0); } catch (err) { logger.error("Server down failed", err); process.exit(1); } }; process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown);