import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import { Console } from "hsconsole"; import Session from "../objects/Session"; import SessionUser from "../objects/SessionUser"; import RequestCtx from "../objects/RequestCtx"; import { UserLevel } from "../enums/UserLevel"; // prepare for ts-ignore :3 // TODO: figure out some runtime field / type checking so // can auto badRequest on missing stuff. export default abstract class Controller { public static FastifyInstance:FastifyInstance; public constructor() { const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)); const rawControllerParts = this.constructor.name.split("_"); const controllerName = rawControllerParts.splice(0, 1)[0].replace("Controller", "").toLowerCase(); let controllerAuthLevel: UserLevel | undefined; for (const prop of rawControllerParts) { if (prop.startsWith("Auth")) { const userLevel = prop.split("$")[1]; // @ts-ignore controllerAuthLevel = UserLevel[userLevel]; Console.printInfo(`Set Auth level requirement for ${this.constructor.name} to ${userLevel}`); } } for (const method of methods) { if (method === "constructor" || method[0] !== method[0].toUpperCase()) { // * Anything that starts with lowercase we'll consider "private" continue; } const params = method.split("_"); const methodNameRaw = params.splice(0, 1)[0] const methodName = methodNameRaw.toLowerCase(); const doAuth = !params.includes("AllowAnonymous"); // @ts-ignore const controllerRequestHandler = this[method]; const requestHandler = (req:FastifyRequest, res:FastifyReply) => { let session = Session.CheckValiditiy(req.cookies); if (doAuth && session === undefined) { return res.redirect(`/account/login?returnTo=${encodeURIComponent(req.url)}`); } if (session !== undefined && controllerAuthLevel !== undefined && controllerAuthLevel !== session.userLevel) { return res.status(403).send("Forbidden"); } const requestCtx = new RequestCtx(req, res, controllerName, methodName, session); controllerRequestHandler.bind(requestCtx)(req.method === "GET" ? req.query : req.body); } let funcMethods:Array = []; for (const param of params) { if (param === "Get" || param === "Post" || param === "Put") { funcMethods.push(param); // @ts-ignore Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName === "index" ? "" : methodName}`, requestHandler); Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName === "index" ? "" : methodName}" as ${param}`); if (methodName === "index") { // @ts-ignore Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName}`, requestHandler); Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName}" as ${param}`); // @ts-ignore Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}`, requestHandler); Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}" as ${param}`); } } } if (controllerName === "home" && methodName === "index") { for (const httpMethod of funcMethods) { Console.printInfo(`Registered ${this.constructor.name}.${method} to "/" as ${httpMethod}`); // @ts-ignore Controller.FastifyInstance[httpMethod.toLowerCase()](`/`, requestHandler); } } } } // not real, these RequestCtx so they autocomplete :) // yeah, i know. this is terrible. // Fields // @ts-ignore public session:SessionUser; // @ts-ignore public req: FastifyRequest; // @ts-ignore public res: FastifyReply; // Methods view(view?:string | Object, model?: Object) {} redirectToAction(action:string, controller?:string) {} ok(message?:string) {} badRequest(message?:string) {} unauthorised(message?:string) {} forbidden(message?:string) {} }