1 import express, { Express } from 'express';
2 import cors from 'cors';
3 import { v4 as uuid } from 'uuid';
4 import { logger } from './logger';
5 import * as time from './time';
7 import {BadInputError, ForbiddenError} from './http-errors';
8 import addFormats from 'ajv-formats';
9 import {prisma} from './db';
10 import path from 'path';
12 export enum HTTP_METHOD {
19 export type ApiEndpointHandler<Input, Output> = (input: Input, server: ApiServer) => Promise<Output>;
21 export interface WrappedApiEndpointHandler {
24 handler: (req: express.Request, res: express.Response) => void;
27 const ajv = addFormats(new Ajv({}), [
40 'relative-json-pointer',
42 ]).addKeyword('kind').addKeyword('modifier')
44 export interface EndpointOptions {
46 authenicated?: boolean
50 export class ApiServer {
55 this.express = express();
57 this.express.use(express.json());
58 this.express.use(cors());
59 this.express.use(express.static(path.join(__dirname, '..', 'public')));
64 APIEndpointWrapper<Input, Output>(type: HTTP_METHOD, path: string, handler: ApiEndpointHandler<Input, Output>, options: EndpointOptions = {}): WrappedApiEndpointHandler {
65 logger.info(`Register ${type} ${path}`);
66 const hasSchema = options.schema;
71 handler: async (req: express.Request, res: express.Response) => {
72 const start = Date.now();
79 logger.info(`Req: ${req.method} ${req.path}`);
86 if(hasSchema && !this.ajv.validate(options.schema, params)) {
88 throw new BadInputError(this.ajv.errors.map(e => e.message).join('.'));
91 throw new BadInputError();
95 if(options.authenicated) {
96 // lets use the token header and validate this request
97 const token = await prisma.authToken.findUnique({
99 token: req.header('x-auth-token')
104 throw new ForbiddenError('Invalid x-auth-token');
108 const output: Output = await handler(params as Input, self);
109 meta.processingTime = Date.now() - start;
119 meta.processingTime = Date.now() - start;
123 statusCode: e.statusCode ? e.statusCode : 500,
131 get<I, O>(path: string, schema: EndpointOptions, handler: ApiEndpointHandler<I, O>) {
132 return this.APIEndpointWrapper<I, O>(HTTP_METHOD.GET, path, handler, schema);
135 post<I, O>(path: string, schema: EndpointOptions, handler: ApiEndpointHandler<I, O>) {
136 return this.APIEndpointWrapper<I, O>(HTTP_METHOD.POST, path, handler, schema);
139 patch<I, O>(path: string, schema: EndpointOptions, handler: ApiEndpointHandler<I, O>) {
140 return this.APIEndpointWrapper<I, O>(HTTP_METHOD.PATCH, path, handler, schema);
143 start(port: string) {
144 return new Promise(res => {
145 this.express.listen(port, () => {
146 logger.info(`Listening on port ${port}`);
153 export const server = new ApiServer();