176 lines
6.3 KiB
TypeScript
176 lines
6.3 KiB
TypeScript
import ejs from "ejs";
|
|
import Fastify from "fastify";
|
|
|
|
import { readdir, stat } from "fs/promises";
|
|
import { IncomingMessage, Server, ServerResponse } from "http";
|
|
import { join } from "path";
|
|
|
|
import { environment } from "../../config/environment";
|
|
|
|
// types
|
|
import type { FastifyInstance, FastifyRequest } from "fastify";
|
|
import type { Stats } from "fs";
|
|
import type { AddressInfo } from "net";
|
|
|
|
// official plugins
|
|
import { fastifyStatic } from "@fastify/static";
|
|
import { fastifyView } from "@fastify/view";
|
|
import fastifyWebSocket from '@fastify/websocket';
|
|
|
|
//custom plugins
|
|
import faviconPlugin from "./plugins/favicon";
|
|
|
|
class FastifyManager {
|
|
private readonly port: number = environment.fastify.port;
|
|
private readonly server: FastifyInstance<Server, IncomingMessage, ServerResponse>;
|
|
|
|
constructor() {
|
|
this.server = Fastify({
|
|
logger: false,
|
|
trustProxy: !environment.development,
|
|
ignoreTrailingSlash: true
|
|
});
|
|
}
|
|
private async registerPlugins(): Promise<void> {
|
|
// official plugins
|
|
this.server.register(fastifyView, {
|
|
engine: {
|
|
ejs
|
|
},
|
|
root: environment.paths.www.views,
|
|
includeViewExtension: true
|
|
});
|
|
|
|
this.server.register(fastifyStatic, {
|
|
root: environment.paths.www.public,
|
|
prefix: "/public/",
|
|
prefixAvoidTrailingSlash: true
|
|
});
|
|
|
|
await this.server.register(fastifyWebSocket, {
|
|
options: {
|
|
maxPayload: 1048576
|
|
}
|
|
});
|
|
|
|
// custom plugins
|
|
this.server.register(faviconPlugin, { path: join(environment.paths.www.public, "assets", "favicon.gif") });
|
|
}
|
|
|
|
// dynamic route loading
|
|
private async loadRoutes(): Promise<void> {
|
|
const routePaths: [string, string, boolean][] = [
|
|
["routes", "/", false],
|
|
["../api", "/api", true]
|
|
];
|
|
|
|
for (const [routePath, prefix, recursive] of routePaths) {
|
|
const modifiedRoutePath: string = join(environment.paths.www.root, routePath);
|
|
|
|
let files: string[];
|
|
try {
|
|
files = await this.readDirRecursive(modifiedRoutePath, recursive);
|
|
} catch (err) {
|
|
console.error("Failed to read route directory", modifiedRoutePath, "error:", err);
|
|
return;
|
|
}
|
|
|
|
for (const file of files) {
|
|
try {
|
|
const routeModule = await import(file);
|
|
const { default: routeData } = routeModule;
|
|
|
|
if (!routeData || !routeData.routeInfo || !routeData.route) {
|
|
console.error(`Failed to load route from ${file}:`, "Route data is missing");
|
|
continue;
|
|
}
|
|
|
|
if (routeData.routeInfo.enabled && routeData.route) {
|
|
const { routeInfo, route } = routeData;
|
|
|
|
let routePath = routeInfo.path || "/";
|
|
|
|
if (prefix) {
|
|
routePath = routePath === "/" ? prefix : join(prefix, routePath);
|
|
}
|
|
|
|
routePath = routePath.replace(/\\/g, '/');
|
|
|
|
if (!routePath.startsWith('/')) {
|
|
routePath = '/' + routePath;
|
|
}
|
|
|
|
routePath = routePath.replace(/\/+/g, "/");
|
|
const methods = Array.isArray(routeInfo.method) ? routeInfo.method : [routeInfo.method];
|
|
|
|
for (const method of methods) {
|
|
if (routeInfo.websocket) {
|
|
this.server.get(routePath, { websocket: true }, function wsHandler(socket: any, req: FastifyRequest) {
|
|
route(socket, req);
|
|
});
|
|
continue;
|
|
} else {
|
|
this.server.route({
|
|
method,
|
|
url: routePath,
|
|
handler: route
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(`Failed to load route from ${file}:`, err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async readDirRecursive(dir: string, recursive: boolean): Promise<string[]> {
|
|
let results: string[] = [];
|
|
const list: string[] = await readdir(dir);
|
|
|
|
for (const file of list) {
|
|
const filePath: string = join(dir, file);
|
|
const statObj: Stats = await stat(filePath);
|
|
if (statObj && statObj.isDirectory()) {
|
|
if (recursive) {
|
|
results = results.concat(await this.readDirRecursive(filePath, recursive));
|
|
}
|
|
} else {
|
|
results.push(filePath);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public async start(): Promise<void> {
|
|
try {
|
|
await this.registerPlugins();
|
|
await this.loadRoutes();
|
|
|
|
await this.server.listen({
|
|
port: environment.fastify.port,
|
|
host: environment.fastify.host
|
|
});
|
|
|
|
const [_address, _port, scheme]: [string, number, string] = ((): [string, number, string] => {
|
|
const address: string | AddressInfo | null = this.server.server.address();
|
|
const resolvedAddress: [string, number] = (address && typeof address === 'object') ? [(address.address.startsWith("::") || address.address === "0.0.0.0") ? "localhost" : address.address, address.port] : ["localhost", this.port];
|
|
const resolvedScheme: string = resolvedAddress[0] === "localhost" ? "http" : "https";
|
|
|
|
return [...resolvedAddress, resolvedScheme];
|
|
})();
|
|
|
|
|
|
console.info("Started Listening on", `${scheme}://${_address}:${_port}`);
|
|
console.info("Registered routes: ", "\n", this.server.printRoutes());
|
|
} catch (error) {
|
|
console.error("Failed to start Fastify server", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
const fastifyManager = new FastifyManager();
|
|
export default fastifyManager;
|
|
export { fastifyManager };
|