@ -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 { InternalService } from "@autoplex/microservice"; | |||
import { join } from "path"; | |||
import routes from "./routes"; | |||
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 | |||
*/ | |||
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); | |||
} | |||
} |