Files
lynq-chat/packages/backend/src/index.ts
T

158 lines
4.2 KiB
TypeScript
Executable File

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);