diff --git a/src/api/UserApiRoute.ts b/src/api/UserApiRoute.ts new file mode 100644 index 0000000..8e4c83f --- /dev/null +++ b/src/api/UserApiRoute.ts @@ -0,0 +1,25 @@ +import type { RouteHandler } from "../net/http/HttpRouteManger"; + + +export class UserApiRoute { + private readonly _getRoutes: Record = {}; + private readonly _postRoutes: Record = {}; + private readonly _putRoutes: Record = {}; + private readonly _deleteRoutes: Record = {}; + + public getGETRoutes(): Record { + return this._getRoutes; + } + + public getPOSTRoutes(): Record { + return this._postRoutes; + } + + public getPUTRoutes(): Record { + return this._putRoutes; + } + + public getDELETERoutes(): Record { + return this._deleteRoutes; + } +} \ No newline at end of file diff --git a/src/net/http/HttpRouteManger.ts b/src/net/http/HttpRouteManger.ts new file mode 100644 index 0000000..f2281bf --- /dev/null +++ b/src/net/http/HttpRouteManger.ts @@ -0,0 +1,95 @@ +import assertUnreachable from '../../lang/assertUnreachable'; +import type express from 'express'; + +export type HttpRoute = string; +export type RoutePath = string; +export type RouteHandler = (request: express.Request, response: express.Response) => void | Promise; + +export class HttpRouteManager { + private readonly _staticGETRoutes: Record; + private readonly _getRoutes: Record; + private readonly _postRoutes: Record; + private readonly _putRoutes: Record; + private readonly _deleteRoutes: Record; + + constructor() { + this._staticGETRoutes = {}; + this._getRoutes = {}; + this._postRoutes = {}; + this._putRoutes = {}; + this._deleteRoutes = {}; + } + + public registerStaticRoute(routePath: RoutePath, destination: string) { + if (this._staticGETRoutes[routePath]) { + throw new Error(`Error: Static route [${routePath}] is already defined`); + } + + this._staticGETRoutes[routePath] = destination; + } + + public register(verb: 'GET' | 'POST' | 'PUT' | 'DELETE', route: string, handler: RouteHandler) { + const isRouteDefined = (routes: Record, routePath: string) => !!routes[routePath]; + + switch (verb) { + case 'GET': + if (isRouteDefined(this._getRoutes, route)) { + throw new Error(`Error: Route [${route}] is already defined`); + } + + this._getRoutes[route] = handler; + + break; + + case 'POST': + if (isRouteDefined(this._postRoutes, route)) { + throw new Error(`Error: Route [${route}] is already defined`); + } + + this._postRoutes[route] = handler; + + break; + + case 'PUT': + if (isRouteDefined(this._putRoutes, route)) { + throw new Error(`Error: Route [${route}] is already defined`); + } + + this._putRoutes[route] = handler; + + break; + + case 'DELETE': + if (isRouteDefined(this._deleteRoutes, route)) { + throw new Error(`Error: Route [${route}] is already defined`); + } + + this._deleteRoutes[route] = handler; + + break; + + default: + assertUnreachable(verb); + } + } + + public getStaticGETRoutes(): Record { + return this._staticGETRoutes; + } + + public getGETRoutes(): Record { + return this._getRoutes; + } + + public getPOSTRoutes(): Record { + return this._postRoutes; + } + + public getPUTRoutes(): Record { + return this._putRoutes; + } + + public getDELETERoutes(): Record { + return this._deleteRoutes; + } +} diff --git a/src/net/http/HttpServer.ts b/src/net/http/HttpServer.ts new file mode 100644 index 0000000..44e0b09 --- /dev/null +++ b/src/net/http/HttpServer.ts @@ -0,0 +1,56 @@ +import express, {type Express} from 'express'; +import type {HttpRouteManager} from './HttpRouteManger'; +import path from 'path'; +import morgan from 'morgan'; + +type Routes = Record; +type RouteHandler = (req: express.Request, res: express.Response) => void | Promise; + +export class HttpServer { + private _app: Express; + + constructor(private readonly routeManager: HttpRouteManager) { + this._app = express(); + this._app.use(morgan('dev')); + + this.initialize(); + } + + public start(port: number) { + this._app.listen(port, () => console.log('Server listening on [%o]', port)); + } + + private initialize() { + this.applyMiddleware(); + this.applyStaticRoutes(); + this.applyRoutes('get', this.routeManager.getGETRoutes()); + this.applyRoutes('post', this.routeManager.getPOSTRoutes()); + this.applyRoutes('put', this.routeManager.getPUTRoutes()); + this.applyRoutes('delete', this.routeManager.getDELETERoutes()); + } + + private applyRoutes(httpVerb: 'get' | 'post' | 'put' | 'delete', routes: Routes) { + Object.entries(routes).forEach(([route, handler]) => this._app[httpVerb as keyof typeof this._app](route, handler)); + } + + private applyStaticRoutes() { + Object.entries(this.routeManager.getStaticGETRoutes()).forEach(([route, destination]) => { + this._app.use( + route, + express.static(path.resolve(destination), { + setHeaders: (res, filePath) => { + console.log(filePath); + if (filePath.endsWith('.js')) { + res.setHeader('Content-Type', 'application/javascript'); + } + } + }) + ); + }); + } + + private applyMiddleware() { + this._app.use(express.json()); + this._app.use(express.urlencoded({extended: true})); + } +}