first commit
This commit is contained in:
commit
57562c5ba6
23 changed files with 1979 additions and 0 deletions
176
src/fastify/manager.ts
Normal file
176
src/fastify/manager.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
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 };
|
35
src/fastify/plugins/favicon.ts
Normal file
35
src/fastify/plugins/favicon.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import type { FastifyInstance, FastifyPluginAsync } from "fastify";
|
||||
import type { FastifyReply } from "fastify/types/reply";
|
||||
import type { FastifyRequest } from "fastify/types/request";
|
||||
import { readFile } from "fs/promises";
|
||||
|
||||
interface FaviconOptions {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const faviconPlugin: FastifyPluginAsync<FaviconOptions> = async (fastify: FastifyInstance, options: FaviconOptions): Promise<void> => {
|
||||
let faviconData: Buffer | null = null;
|
||||
|
||||
try {
|
||||
faviconData = await readFile(options.path);
|
||||
} catch (err) {
|
||||
console.error("Error reading favicon:", err);
|
||||
}
|
||||
|
||||
fastify.get("/favicon.ico", async (_request: FastifyRequest, reply: FastifyReply): Promise<void> => {
|
||||
if (faviconData) {
|
||||
const contentType = options.path.endsWith(".gif") ? "image/gif" : "image/x-icon";
|
||||
reply.header("Content-Type", contentType)
|
||||
.header("Cache-Control", "public, max-age=86400") // 1 day
|
||||
.send(faviconData);
|
||||
} else {
|
||||
reply.status(404).send({
|
||||
code: 404,
|
||||
error: "FILE_NOT_FOUND",
|
||||
message: "Favicon not found"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default faviconPlugin as FastifyPluginAsync<FaviconOptions>;
|
Loading…
Add table
Add a link
Reference in a new issue