@ -1,121 +1,34 @@ | |||||
import fastify from "fastify"; | |||||
import fastifyCookie from "fastify-cookie"; | |||||
import fastifyFormBody from "fastify-formbody"; | |||||
import fastifyHttpProxy from "fastify-http-proxy"; | |||||
import fastifyMultipart from "fastify-multipart"; | |||||
import fastifyStatic from "fastify-static"; | |||||
import Application from "@server/Application"; | import Application from "@server/Application"; | ||||
import { InternalService } from "@autoplex/microservice"; | |||||
import { join } from "path"; | |||||
import routes from "./routes"; | import routes from "./routes"; | ||||
import "./validators"; | import "./validators"; | ||||
import RouteRegisterFactory, { RouteFactory } from "./routes/RouteRegisterFactory"; | |||||
import { WebServerService } from "@autoplex/webserver"; | |||||
import { env } from "@autoplex/utils"; | |||||
export default class WebServer extends InternalService<Application> | |||||
export default class WebServer extends WebServerService<Application> | |||||
{ | { | ||||
/** | |||||
* The port to host the webserver on | |||||
*/ | |||||
protected readonly port: number; | |||||
/** | |||||
* The internal webserver instance | |||||
*/ | |||||
protected fastify: ReturnType<typeof fastify>; | |||||
/** | |||||
* Create a new webserver instance | |||||
*/ | |||||
public constructor(app: Application) { | |||||
super(app); | |||||
this.port = parseInt(<string>process.env["WEBSERVER_PORT"]); | |||||
this.fastify = fastify(); | |||||
} | |||||
/** | /** | ||||
* The service name | * The service name | ||||
*/ | */ | ||||
public readonly NAME = "Web Server"; | public readonly NAME = "Web Server"; | ||||
/** | /** | ||||
* Register required Fastify plugins | |||||
*/ | |||||
protected registerPlugins() { | |||||
return Promise.all([ | |||||
this.fastify.register(fastifyCookie, { | |||||
secret: this.app.APP_KEY | |||||
}), | |||||
this.fastify.register(fastifyFormBody), | |||||
this.fastify.register(fastifyMultipart, { | |||||
limits: { | |||||
fileSize: 16*1024*1024, | |||||
files: 50 | |||||
} | |||||
}) | |||||
]); | |||||
} | |||||
/** | |||||
* Boot the webserver | |||||
*/ | |||||
public async boot() { | |||||
// Install plugins | |||||
await this.registerPlugins(); | |||||
// Register the routes | |||||
this.registerRoutes(); | |||||
} | |||||
/** | |||||
* Start the webserver | |||||
* The port to host the webserver on | |||||
*/ | */ | ||||
public start() { | |||||
// Start listening | |||||
this.fastify.listen(this.port, "0.0.0.0"); | |||||
this.log("Webserver listening on port:", this.port); | |||||
} | |||||
protected readonly PORT: number = parseInt(env("WEBSERVER_PORT")); | |||||
/** | /** | ||||
* Shutdown the webserver | |||||
* The application key | |||||
*/ | */ | ||||
public async shutdown() { | |||||
this.log("Webserver shutting down"); | |||||
await this.fastify.close(); | |||||
} | |||||
// --------------------------------------------------------------------------------------------- | |||||
protected readonly APP_KEY = this.app.APP_KEY; | |||||
/** | /** | ||||
* Register all route groups | |||||
* @TODO | |||||
* Register SPA routes for now | |||||
*/ | */ | ||||
protected registerRoutes() { | |||||
this.registerSpaRoutes(); | |||||
let factory = new RouteRegisterFactory(this, this.fastify, this.app); | |||||
for (let group in routes) { | |||||
<RouteFactory>(<any>routes)[group](factory, this.app); | |||||
} | |||||
} | |||||
protected REGISTER_SPA_ROUTES = true; | |||||
/** | /** | ||||
* Register the routes required for the single-page application | |||||
* The routes to register | |||||
*/ | */ | ||||
protected registerSpaRoutes() { | |||||
/** | |||||
* If the app is in production mode, serve static assets. | |||||
* If the app is in development mode, forward 404's to Vite. | |||||
*/ | |||||
if (process.env["NODE_ENV"] == "production") { | |||||
this.fastify.register(fastifyStatic, { | |||||
root: join(__dirname, "../../../public") | |||||
}); | |||||
this.fastify.setNotFoundHandler((request, reply) => { | |||||
return reply.sendFile("index.html"); | |||||
}); | |||||
} else { | |||||
this.log("Using Vite proxy"); | |||||
this.fastify.register(fastifyHttpProxy, { | |||||
upstream: "http://localhost:3001" | |||||
}); | |||||
} | |||||
} | |||||
protected ROUTES = Object.values(routes); | |||||
} | } |
@ -1,57 +0,0 @@ | |||||
import { FastifyRequest, FastifyReply } from "fastify"; | |||||
import { MiddlewareRequest } from "../middleware"; | |||||
export default class Request<T = unknown> | |||||
{ | |||||
/** | |||||
* Handle the incoming request | |||||
*/ | |||||
public async handle(request: MiddlewareRequest<T>, reply: FastifyReply) { | |||||
if (!this.checkFormat(request)) { | |||||
reply.status(400); | |||||
return { | |||||
status: "Bad request" | |||||
}; | |||||
} | |||||
if (!await this.isAuthorized(request)) { | |||||
reply.status(403); | |||||
return { | |||||
status: "Forbidden" | |||||
}; | |||||
} | |||||
try { | |||||
await this.validate(request); | |||||
} catch(errors) { | |||||
reply.status(422); | |||||
return { | |||||
status: "Unprocessable entities", | |||||
errors | |||||
}; | |||||
} | |||||
} | |||||
// Overridable --------------------------------------------------------------------------------- | |||||
/** | |||||
* Check the format of the given request | |||||
*/ | |||||
public checkFormat(request: MiddlewareRequest<T>) { | |||||
return true; | |||||
} | |||||
/** | |||||
* Check if the user is authorized to make this request | |||||
*/ | |||||
public async isAuthorized(request: MiddlewareRequest<T>) { | |||||
return true; | |||||
} | |||||
/** | |||||
* Validate the request and return any errors | |||||
*/ | |||||
public async validate(request: MiddlewareRequest<T>): Promise<any|undefined> { | |||||
return undefined; | |||||
} | |||||
} | |||||
Request.apply(undefined); |
@ -1,23 +1,7 @@ | |||||
import Request from "./Request"; | |||||
import { MiddlewareRequest } from "../middleware"; | |||||
import { FastifyReply } from "fastify"; | |||||
type RequestConstructor<T> = new () => Request<T>; | |||||
export type RouteMiddlewareHandler<T> = (request: MiddlewareRequest<T>, reply: FastifyReply) => Promise<void> | void; | |||||
export default function handle<T>(requests: RequestConstructor<T>[], handle: RouteMiddlewareHandler<T>): RouteMiddlewareHandler<T> | |||||
{ | |||||
return async (fastifyRequest, fastifyReply) => { | |||||
// Request parsing | |||||
for (let requestClass of requests) { | |||||
let request = new requestClass(); | |||||
let response = await request.handle(fastifyRequest, fastifyReply); | |||||
if (response) { | |||||
fastifyReply.send(response); | |||||
return; | |||||
} | |||||
} | |||||
// Requests have been parsed/handled successfully, proceed to process the request | |||||
await (<any>handle)(fastifyRequest, fastifyReply); | |||||
} | |||||
} | |||||
export * from "./LinkDiscordRequest"; | |||||
export * from "./LoginRequest"; | |||||
export * from "./MovieSearchRequest"; | |||||
export * from "./RegisterRequest"; | |||||
export * from "./RequestImdbMovieRequest"; | |||||
export * from "./RequestMovieRequest"; | |||||
export * from "./RequestTmdbMovieRequest"; |
@ -1,108 +0,0 @@ | |||||
import { FastifyInstance } from "fastify"; | |||||
import Application from "@server/Application"; | |||||
import { handleMiddleware, HandlerMethodWithMiddleware, MiddlewareMethod } from "../middleware"; | |||||
import fastifyHttpProxy from "fastify-http-proxy"; | |||||
import WebServer from ".."; | |||||
export type RouteFactory<M extends MiddlewareMethod<any> = never> = ((factory: RouteRegisterFactory<M>) => void) | |||||
| ((factory: RouteRegisterFactory<M>, app: Application) => void); | |||||
export default class RouteRegisterFactory<M extends MiddlewareMethod<any> = MiddlewareMethod<void>> | |||||
{ | |||||
/** | |||||
* The application instance | |||||
*/ | |||||
protected readonly app: Application; | |||||
/** | |||||
* The Fastify server instance | |||||
*/ | |||||
protected readonly fastify: FastifyInstance; | |||||
/** | |||||
* The webserver instance | |||||
*/ | |||||
protected readonly webserver: WebServer; | |||||
/** | |||||
* The list of middleware | |||||
*/ | |||||
protected middleware: M[] = []; | |||||
/** | |||||
* The current route prefix | |||||
*/ | |||||
protected pathPrefix: string = ""; | |||||
/** | |||||
* Create a new route factory | |||||
*/ | |||||
public constructor(webserver: WebServer, fastify: FastifyInstance, app: Application) { | |||||
this.app = app; | |||||
this.fastify = fastify; | |||||
this.webserver = webserver; | |||||
} | |||||
/** | |||||
* Register a group of routes under a common prefix and middleware | |||||
*/ | |||||
public prefix<T extends MiddlewareMethod<any>>(prefix: string, middleware: T[], factory: RouteFactory<(M|T)>) { | |||||
let prefixBackup = this.pathPrefix; | |||||
this.pathPrefix += prefix; | |||||
this.group(middleware, factory); | |||||
this.pathPrefix = prefixBackup; | |||||
} | |||||
/** | |||||
* Register a group of routes under common middleware | |||||
*/ | |||||
public group<T extends MiddlewareMethod<any>>(middleware: T[], factory: RouteFactory<(M|T)>) { | |||||
let middlewareBackup = this.middleware; | |||||
this.middleware = this.middleware.concat(<any>middleware); | |||||
factory(<RouteRegisterFactory<(M|T)>>this, this.app); | |||||
this.middleware = middlewareBackup; | |||||
} | |||||
/** | |||||
* Register a GET request | |||||
*/ | |||||
public get<T extends MiddlewareMethod<any>>(path: string, handler: HandlerMethodWithMiddleware<M[]>): void; | |||||
public get<T extends MiddlewareMethod<any>>(path: string, middleware: T[], handler: HandlerMethodWithMiddleware<(T|M)[]>): void; | |||||
public get<T extends MiddlewareMethod<any>>(path: string, middleware: HandlerMethodWithMiddleware<(T|M)[]>|T[], handler?: HandlerMethodWithMiddleware<(T|M)[]>) { | |||||
type Handler = HandlerMethodWithMiddleware<(T|M)[]>; | |||||
handler = (handler ?? <Handler>middleware); | |||||
middleware = (middleware instanceof Array) ? <any>this.middleware.concat(<any>middleware) : this.middleware; | |||||
this.fastify.get(`${this.pathPrefix}${path}`, handleMiddleware(<(T|M)[]>middleware, <Handler>handler)); | |||||
} | |||||
/** | |||||
* Register a POST request | |||||
*/ | |||||
public post<T extends MiddlewareMethod<any>>(path: string, handler: HandlerMethodWithMiddleware<M[]>): void; | |||||
public post<T extends MiddlewareMethod<any>>(path: string, middleware: T[], handler: HandlerMethodWithMiddleware<(T|M)[]>): void; | |||||
public post<T extends MiddlewareMethod<any>>(path: string, middleware: HandlerMethodWithMiddleware<(T|M)[]>|T[], handler?: HandlerMethodWithMiddleware<(T|M)[]>) { | |||||
type Handler = HandlerMethodWithMiddleware<(T|M)[]>; | |||||
handler = (handler ?? <Handler>middleware); | |||||
middleware = (middleware instanceof Array) ? <any>this.middleware.concat(<any>middleware) : this.middleware; | |||||
this.fastify.post(`${this.pathPrefix}${path}`, handleMiddleware(<(T|M)[]>middleware, <Handler>handler)); | |||||
} | |||||
/** | |||||
* Register a proxy route | |||||
*/ | |||||
public proxy<T extends MiddlewareMethod<any>>(path: string, upstream: string, middleware?: T[]) { | |||||
this.log(`Registering proxy: ${this.pathPrefix}${path} -> ${upstream}`); | |||||
this.fastify.register(fastifyHttpProxy, { | |||||
prefix: `${this.pathPrefix}${path}`, | |||||
beforeHandler: middleware ? handleMiddleware(this.middleware.concat(<any>middleware)) : undefined, | |||||
upstream | |||||
}); | |||||
} | |||||
/** | |||||
* Log under the WebServer service | |||||
*/ | |||||
public log(...args: any[]) { | |||||
this.webserver.log(...args); | |||||
} | |||||
} |