Add sources
This commit is contained in:
parent
ac7d14b458
commit
662d12df68
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
node_modules
|
20
dist/Application.d.ts
vendored
Normal file
20
dist/Application.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import WebSocketListener from "./WebSocketListener";
|
||||||
|
import ApplicationComponent from "./ApplicationComponent";
|
||||||
|
import Controller from "./Controller";
|
||||||
|
export default abstract class Application {
|
||||||
|
private readonly version;
|
||||||
|
private readonly controllers;
|
||||||
|
private readonly webSocketListeners;
|
||||||
|
private readonly components;
|
||||||
|
private ready;
|
||||||
|
protected constructor(version: string);
|
||||||
|
protected abstract init(): Promise<void>;
|
||||||
|
protected use(thing: Controller | WebSocketListener | ApplicationComponent<any>): void;
|
||||||
|
start(): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
private routes;
|
||||||
|
getWebSocketListeners(): {
|
||||||
|
[p: string]: WebSocketListener;
|
||||||
|
};
|
||||||
|
isReady(): boolean;
|
||||||
|
}
|
140
dist/Application.js
vendored
Normal file
140
dist/Application.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
dist/ApplicationComponent.d.ts
vendored
Normal file
10
dist/ApplicationComponent.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Express, Router } from "express";
|
||||||
|
export default abstract class ApplicationComponent<T> {
|
||||||
|
private val?;
|
||||||
|
abstract start(app: Express, router: Router): Promise<void>;
|
||||||
|
abstract stop(): Promise<void>;
|
||||||
|
protected export(val: T): void;
|
||||||
|
import(): T;
|
||||||
|
protected prepare(name: string, prepare: () => Promise<void>): Promise<void>;
|
||||||
|
protected close(thingName: string, thing: any, fn: Function): Promise<void>;
|
||||||
|
}
|
55
dist/ApplicationComponent.js
vendored
Normal file
55
dist/ApplicationComponent.js
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import Logger from "./Logger";
|
||||||
|
import { sleep } from "./Utils";
|
||||||
|
export default class ApplicationComponent {
|
||||||
|
export(val) {
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
import() {
|
||||||
|
if (!this.val)
|
||||||
|
throw 'Cannot import if nothing was exported.';
|
||||||
|
return this.val;
|
||||||
|
}
|
||||||
|
prepare(name, prepare) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let err;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
yield prepare();
|
||||||
|
err = null;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
err = e;
|
||||||
|
Logger.error(err, `${name} failed to prepare; retrying in 5s...`);
|
||||||
|
yield sleep(5000);
|
||||||
|
}
|
||||||
|
} while (err);
|
||||||
|
Logger.info(`${name} ready!`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
close(thingName, thing, fn) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
yield new Promise((resolve, reject) => fn.call(thing, (err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
}));
|
||||||
|
Logger.info(`${thingName} closed.`);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Logger.error(e, `An error occurred while closing the ${thingName}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQXBwbGljYXRpb25Db21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbIkFwcGxpY2F0aW9uQ29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUNBLE9BQU8sTUFBTSxNQUFNLFVBQVUsQ0FBQztBQUM5QixPQUFPLEVBQUMsS0FBSyxFQUFDLE1BQU0sU0FBUyxDQUFDO0FBRTlCLE1BQU0sQ0FBQyxPQUFPLE9BQWdCLG9CQUFvQjtJQU9wQyxNQUFNLENBQUMsR0FBTTtRQUNuQixJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQztJQUNuQixDQUFDO0lBRU0sTUFBTTtRQUNULElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRztZQUFFLE1BQU0sd0NBQXdDLENBQUM7UUFDOUQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDO0lBQ3BCLENBQUM7SUFFZSxPQUFPLENBQUMsSUFBWSxFQUFFLE9BQTRCOztZQUM5RCxJQUFJLEdBQUcsQ0FBQztZQUNSLEdBQUc7Z0JBQ0MsSUFBSTtvQkFDQSxNQUFNLE9BQU8sRUFBRSxDQUFDO29CQUNoQixHQUFHLEdBQUcsSUFBSSxDQUFDO2lCQUNkO2dCQUFDLE9BQU8sQ0FBQyxFQUFFO29CQUNSLEdBQUcsR0FBRyxDQUFDLENBQUM7b0JBQ1IsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLHVDQUF1QyxDQUFDLENBQUE7b0JBQ2pFLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO2lCQUNyQjthQUNKLFFBQVEsR0FBRyxFQUFFO1lBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksU0FBUyxDQUFDLENBQUM7UUFDbEMsQ0FBQztLQUFBO0lBRWUsS0FBSyxDQUFDLFNBQWlCLEVBQUUsS0FBVSxFQUFFLEVBQVk7O1lBQzdELElBQUk7Z0JBQ0EsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsR0FBUSxFQUFFLEVBQUU7b0JBQy9ELElBQUksR0FBRzt3QkFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7O3dCQUNoQixPQUFPLEVBQUUsQ0FBQztnQkFDbkIsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFFSixNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsU0FBUyxVQUFVLENBQUMsQ0FBQzthQUN2QztZQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUNSLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHVDQUF1QyxTQUFTLEdBQUcsQ0FBQyxDQUFDO2FBQ3hFO1FBQ0wsQ0FBQztLQUFBO0NBQ0oiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge0V4cHJlc3MsIFJvdXRlcn0gZnJvbSBcImV4cHJlc3NcIjtcbmltcG9ydCBMb2dnZXIgZnJvbSBcIi4vTG9nZ2VyXCI7XG5pbXBvcnQge3NsZWVwfSBmcm9tIFwiLi9VdGlsc1wiO1xuXG5leHBvcnQgZGVmYXVsdCBhYnN0cmFjdCBjbGFzcyBBcHBsaWNhdGlvbkNvbXBvbmVudDxUPiB7XG4gICAgcHJpdmF0ZSB2YWw/OiBUO1xuXG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+O1xuXG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPjtcblxuICAgIHByb3RlY3RlZCBleHBvcnQodmFsOiBUKSB7XG4gICAgICAgIHRoaXMudmFsID0gdmFsO1xuICAgIH1cblxuICAgIHB1YmxpYyBpbXBvcnQoKTogVCB7XG4gICAgICAgIGlmICghdGhpcy52YWwpIHRocm93ICdDYW5ub3QgaW1wb3J0IGlmIG5vdGhpbmcgd2FzIGV4cG9ydGVkLic7XG4gICAgICAgIHJldHVybiB0aGlzLnZhbDtcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgYXN5bmMgcHJlcGFyZShuYW1lOiBzdHJpbmcsIHByZXBhcmU6ICgpID0+IFByb21pc2U8dm9pZD4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgbGV0IGVycjtcbiAgICAgICAgZG8ge1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBhd2FpdCBwcmVwYXJlKCk7XG4gICAgICAgICAgICAgICAgZXJyID0gbnVsbDtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgICAgICBlcnIgPSBlO1xuICAgICAgICAgICAgICAgIExvZ2dlci5lcnJvcihlcnIsIGAke25hbWV9IGZhaWxlZCB0byBwcmVwYXJlOyByZXRyeWluZyBpbiA1cy4uLmApXG4gICAgICAgICAgICAgICAgYXdhaXQgc2xlZXAoNTAwMCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gd2hpbGUgKGVycik7XG4gICAgICAgIExvZ2dlci5pbmZvKGAke25hbWV9IHJlYWR5IWApO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBjbG9zZSh0aGluZ05hbWU6IHN0cmluZywgdGhpbmc6IGFueSwgZm46IEZ1bmN0aW9uKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiBmbi5jYWxsKHRoaW5nLCAoZXJyOiBhbnkpID0+IHtcbiAgICAgICAgICAgICAgICBpZiAoZXJyKSByZWplY3QoZXJyKTtcbiAgICAgICAgICAgICAgICBlbHNlIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH0pKTtcblxuICAgICAgICAgICAgTG9nZ2VyLmluZm8oYCR7dGhpbmdOYW1lfSBjbG9zZWQuYCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIExvZ2dlci5lcnJvcihlLCBgQW4gZXJyb3Igb2NjdXJyZWQgd2hpbGUgY2xvc2luZyB0aGUgJHt0aGluZ05hbWV9LmApO1xuICAgICAgICB9XG4gICAgfVxufSJdfQ==
|
21
dist/Controller.d.ts
vendored
Normal file
21
dist/Controller.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { RequestHandler, Router } from "express";
|
||||||
|
import { PathParams } from "express-serve-static-core";
|
||||||
|
export default abstract class Controller {
|
||||||
|
private static readonly routes;
|
||||||
|
static route(route: string, params?: RouteParams, absolute?: boolean): string;
|
||||||
|
private router?;
|
||||||
|
getGlobalHandlers(): RequestHandler[];
|
||||||
|
hasGlobalHandlers(): boolean;
|
||||||
|
setupGlobalHandlers(router: Router): void;
|
||||||
|
getRoutesPrefix(): string;
|
||||||
|
abstract routes(): void;
|
||||||
|
setupRoutes(router: Router): void;
|
||||||
|
protected use(handler: RequestHandler): void;
|
||||||
|
protected get(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]): void;
|
||||||
|
protected post(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]): void;
|
||||||
|
private wrap;
|
||||||
|
private registerRoutes;
|
||||||
|
}
|
||||||
|
export declare type RouteParams = {
|
||||||
|
[p: string]: string;
|
||||||
|
} | string[] | string | number;
|
104
dist/Controller.js
vendored
Normal file
104
dist/Controller.js
vendored
Normal file
File diff suppressed because one or more lines are too long
28
dist/HttpError.d.ts
vendored
Normal file
28
dist/HttpError.d.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { WrappingError } from "./Utils";
|
||||||
|
export declare abstract class HttpError extends WrappingError {
|
||||||
|
readonly instructions: string;
|
||||||
|
constructor(message: string, instructions: string, cause?: Error);
|
||||||
|
get name(): string;
|
||||||
|
abstract get errorCode(): number;
|
||||||
|
}
|
||||||
|
export declare class BadRequestError extends HttpError {
|
||||||
|
readonly url: string;
|
||||||
|
constructor(message: string, instructions: string, url: string, cause?: Error);
|
||||||
|
get errorCode(): number;
|
||||||
|
}
|
||||||
|
export declare class ForbiddenHttpError extends BadRequestError {
|
||||||
|
constructor(thing: string, url: string, cause?: Error);
|
||||||
|
get errorCode(): number;
|
||||||
|
}
|
||||||
|
export declare class NotFoundHttpError extends BadRequestError {
|
||||||
|
constructor(thing: string, url: string, cause?: Error);
|
||||||
|
get errorCode(): number;
|
||||||
|
}
|
||||||
|
export declare class ServerError extends HttpError {
|
||||||
|
constructor(message: string, cause?: Error);
|
||||||
|
get errorCode(): number;
|
||||||
|
}
|
||||||
|
export declare class ServiceUnavailableHttpError extends ServerError {
|
||||||
|
constructor(message: string, cause?: Error);
|
||||||
|
get errorCode(): number;
|
||||||
|
}
|
52
dist/HttpError.js
vendored
Normal file
52
dist/HttpError.js
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { WrappingError } from "./Utils";
|
||||||
|
export class HttpError extends WrappingError {
|
||||||
|
constructor(message, instructions, cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.instructions = instructions;
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class BadRequestError extends HttpError {
|
||||||
|
constructor(message, instructions, url, cause) {
|
||||||
|
super(message, instructions, cause);
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ForbiddenHttpError extends BadRequestError {
|
||||||
|
constructor(thing, url, cause) {
|
||||||
|
super(`You don't have access to this ${thing}.`, `${url} doesn't belong to *you*.`, url, cause);
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class NotFoundHttpError extends BadRequestError {
|
||||||
|
constructor(thing, url, cause) {
|
||||||
|
super(`${thing.charAt(0).toUpperCase()}${thing.substr(1)} not found.`, `${url} doesn't exist or was deleted.`, url, cause);
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ServerError extends HttpError {
|
||||||
|
constructor(message, cause) {
|
||||||
|
super(message, `Maybe you should contact us; see instructions below.`, cause);
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ServiceUnavailableHttpError extends ServerError {
|
||||||
|
constructor(message, cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 503;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSHR0cEVycm9yLmpzIiwic291cmNlUm9vdCI6Ii4vIiwic291cmNlcyI6WyJIdHRwRXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFDLGFBQWEsRUFBQyxNQUFNLFNBQVMsQ0FBQztBQUV0QyxNQUFNLE9BQWdCLFNBQVUsU0FBUSxhQUFhO0lBR2pELFlBQVksT0FBZSxFQUFFLFlBQW9CLEVBQUUsS0FBYTtRQUM1RCxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3RCLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ3JDLENBQUM7SUFFRCxJQUFJLElBQUk7UUFDSixPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDO0lBQ2pDLENBQUM7Q0FHSjtBQUVELE1BQU0sT0FBTyxlQUFnQixTQUFRLFNBQVM7SUFHMUMsWUFBWSxPQUFlLEVBQUUsWUFBb0IsRUFBRSxHQUFXLEVBQUUsS0FBYTtRQUN6RSxLQUFLLENBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNwQyxJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQztJQUNuQixDQUFDO0lBRUQsSUFBSSxTQUFTO1FBQ1QsT0FBTyxHQUFHLENBQUM7SUFDZixDQUFDO0NBQ0o7QUFFRCxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsZUFBZTtJQUNuRCxZQUFZLEtBQWEsRUFBRSxHQUFXLEVBQUUsS0FBYTtRQUNqRCxLQUFLLENBQ0QsaUNBQWlDLEtBQUssR0FBRyxFQUN6QyxHQUFHLEdBQUcsMkJBQTJCLEVBQ2pDLEdBQUcsRUFDSCxLQUFLLENBQ1IsQ0FBQztJQUNOLENBQUM7SUFFRCxJQUFJLFNBQVM7UUFDVCxPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7Q0FDSjtBQUVELE1BQU0sT0FBTyxpQkFBa0IsU0FBUSxlQUFlO0lBQ2xELFlBQVksS0FBYSxFQUFFLEdBQVcsRUFBRSxLQUFhO1FBQ2pELEtBQUssQ0FDRCxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsYUFBYSxFQUMvRCxHQUFHLEdBQUcsZ0NBQWdDLEVBQ3RDLEdBQUcsRUFDSCxLQUFLLENBQ1IsQ0FBQztJQUNOLENBQUM7SUFFRCxJQUFJLFNBQVM7UUFDVCxPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7Q0FDSjtBQUVELE1BQU0sT0FBTyxXQUFZLFNBQVEsU0FBUztJQUN0QyxZQUFZLE9BQWUsRUFBRSxLQUFhO1FBQ3RDLEtBQUssQ0FBQyxPQUFPLEVBQUUsc0RBQXNELEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDbEYsQ0FBQztJQUVELElBQUksU0FBUztRQUNULE9BQU8sR0FBRyxDQUFDO0lBQ2YsQ0FBQztDQUNKO0FBRUQsTUFBTSxPQUFPLDJCQUE0QixTQUFRLFdBQVc7SUFDeEQsWUFBWSxPQUFlLEVBQUUsS0FBYTtRQUN0QyxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFCLENBQUM7SUFFRCxJQUFJLFNBQVM7UUFDVCxPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7Q0FDSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7V3JhcHBpbmdFcnJvcn0gZnJvbSBcIi4vVXRpbHNcIjtcblxuZXhwb3J0IGFic3RyYWN0IGNsYXNzIEh0dHBFcnJvciBleHRlbmRzIFdyYXBwaW5nRXJyb3Ige1xuICAgIHB1YmxpYyByZWFkb25seSBpbnN0cnVjdGlvbnM6IHN0cmluZztcblxuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZywgaW5zdHJ1Y3Rpb25zOiBzdHJpbmcsIGNhdXNlPzogRXJyb3IpIHtcbiAgICAgICAgc3VwZXIobWVzc2FnZSwgY2F1c2UpO1xuICAgICAgICB0aGlzLmluc3RydWN0aW9ucyA9IGluc3RydWN0aW9ucztcbiAgICB9XG5cbiAgICBnZXQgbmFtZSgpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gdGhpcy5jb25zdHJ1Y3Rvci5uYW1lO1xuICAgIH1cblxuICAgIGFic3RyYWN0IGdldCBlcnJvckNvZGUoKTogbnVtYmVyO1xufVxuXG5leHBvcnQgY2xhc3MgQmFkUmVxdWVzdEVycm9yIGV4dGVuZHMgSHR0cEVycm9yIHtcbiAgICBwdWJsaWMgcmVhZG9ubHkgdXJsOiBzdHJpbmc7XG5cbiAgICBjb25zdHJ1Y3RvcihtZXNzYWdlOiBzdHJpbmcsIGluc3RydWN0aW9uczogc3RyaW5nLCB1cmw6IHN0cmluZywgY2F1c2U/OiBFcnJvcikge1xuICAgICAgICBzdXBlcihtZXNzYWdlLCBpbnN0cnVjdGlvbnMsIGNhdXNlKTtcbiAgICAgICAgdGhpcy51cmwgPSB1cmw7XG4gICAgfVxuXG4gICAgZ2V0IGVycm9yQ29kZSgpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gNDAwO1xuICAgIH1cbn1cblxuZXhwb3J0IGNsYXNzIEZvcmJpZGRlbkh0dHBFcnJvciBleHRlbmRzIEJhZFJlcXVlc3RFcnJvciB7XG4gICAgY29uc3RydWN0b3IodGhpbmc6IHN0cmluZywgdXJsOiBzdHJpbmcsIGNhdXNlPzogRXJyb3IpIHtcbiAgICAgICAgc3VwZXIoXG4gICAgICAgICAgICBgWW91IGRvbid0IGhhdmUgYWNjZXNzIHRvIHRoaXMgJHt0aGluZ30uYCxcbiAgICAgICAgICAgIGAke3VybH0gZG9lc24ndCBiZWxvbmcgdG8gKnlvdSouYCxcbiAgICAgICAgICAgIHVybCxcbiAgICAgICAgICAgIGNhdXNlXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgZ2V0IGVycm9yQ29kZSgpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gNDAzO1xuICAgIH1cbn1cblxuZXhwb3J0IGNsYXNzIE5vdEZvdW5kSHR0cEVycm9yIGV4dGVuZHMgQmFkUmVxdWVzdEVycm9yIHtcbiAgICBjb25zdHJ1Y3Rvcih0aGluZzogc3RyaW5nLCB1cmw6IHN0cmluZywgY2F1c2U/OiBFcnJvcikge1xuICAgICAgICBzdXBlcihcbiAgICAgICAgICAgIGAke3RoaW5nLmNoYXJBdCgwKS50b1VwcGVyQ2FzZSgpfSR7dGhpbmcuc3Vic3RyKDEpfSBub3QgZm91bmQuYCxcbiAgICAgICAgICAgIGAke3VybH0gZG9lc24ndCBleGlzdCBvciB3YXMgZGVsZXRlZC5gLFxuICAgICAgICAgICAgdXJsLFxuICAgICAgICAgICAgY2F1c2VcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBnZXQgZXJyb3JDb2RlKCk6IG51bWJlciB7XG4gICAgICAgIHJldHVybiA0MDQ7XG4gICAgfVxufVxuXG5leHBvcnQgY2xhc3MgU2VydmVyRXJyb3IgZXh0ZW5kcyBIdHRwRXJyb3Ige1xuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZywgY2F1c2U/OiBFcnJvcikge1xuICAgICAgICBzdXBlcihtZXNzYWdlLCBgTWF5YmUgeW91IHNob3VsZCBjb250YWN0IHVzOyBzZWUgaW5zdHJ1Y3Rpb25zIGJlbG93LmAsIGNhdXNlKTtcbiAgICB9XG5cbiAgICBnZXQgZXJyb3JDb2RlKCk6IG51bWJlciB7XG4gICAgICAgIHJldHVybiA1MDA7XG4gICAgfVxufVxuXG5leHBvcnQgY2xhc3MgU2VydmljZVVuYXZhaWxhYmxlSHR0cEVycm9yIGV4dGVuZHMgU2VydmVyRXJyb3Ige1xuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZywgY2F1c2U/OiBFcnJvcikge1xuICAgICAgICBzdXBlcihtZXNzYWdlLCBjYXVzZSk7XG4gICAgfVxuXG4gICAgZ2V0IGVycm9yQ29kZSgpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gNTAzO1xuICAgIH1cbn0iXX0=
|
18
dist/Logger.d.ts
vendored
Normal file
18
dist/Logger.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export default class Logger {
|
||||||
|
static silentError(error: Error, ...message: any[]): string;
|
||||||
|
static error(error: Error, ...message: any[]): string;
|
||||||
|
static warn(...message: any[]): void;
|
||||||
|
static info(...message: any[]): void;
|
||||||
|
static debug(...message: any[]): void;
|
||||||
|
static dev(...message: any[]): void;
|
||||||
|
private static log;
|
||||||
|
private constructor();
|
||||||
|
}
|
||||||
|
export declare enum LogLevel {
|
||||||
|
ERROR = 0,
|
||||||
|
WARN = 1,
|
||||||
|
INFO = 2,
|
||||||
|
DEBUG = 3,
|
||||||
|
DEV = 4
|
||||||
|
}
|
||||||
|
export declare type LogLevelKeys = keyof typeof LogLevel;
|
111
dist/Logger.js
vendored
Normal file
111
dist/Logger.js
vendored
Normal file
File diff suppressed because one or more lines are too long
24
dist/Mail.d.ts
vendored
Normal file
24
dist/Mail.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { SentMessageInfo } from "nodemailer";
|
||||||
|
export declare function mailRoute(template: string): string;
|
||||||
|
export default class Mail {
|
||||||
|
private static transporter;
|
||||||
|
private static getTransporter;
|
||||||
|
static prepare(): Promise<void>;
|
||||||
|
static end(): void;
|
||||||
|
static parse(template: string, data: any, textOnly: boolean): string;
|
||||||
|
private readonly template;
|
||||||
|
private readonly options;
|
||||||
|
private readonly data;
|
||||||
|
constructor(template: MailTemplate, data?: {
|
||||||
|
[p: string]: any;
|
||||||
|
});
|
||||||
|
private verifyData;
|
||||||
|
send(...to: string[]): Promise<SentMessageInfo[]>;
|
||||||
|
}
|
||||||
|
export declare class MailTemplate {
|
||||||
|
private readonly _template;
|
||||||
|
private readonly subject;
|
||||||
|
constructor(template: string, subject: (data: any) => string);
|
||||||
|
get template(): string;
|
||||||
|
getSubject(data: any): string;
|
||||||
|
}
|
122
dist/Mail.js
vendored
Normal file
122
dist/Mail.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
dist/Pagination.d.ts
vendored
Normal file
10
dist/Pagination.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import Model from "./db/Model";
|
||||||
|
export default class Pagination<T extends Model> {
|
||||||
|
private readonly models;
|
||||||
|
readonly page: number;
|
||||||
|
readonly perPage: number;
|
||||||
|
readonly totalCount: number;
|
||||||
|
constructor(models: T[], page: number, perPage: number, totalCount: number);
|
||||||
|
hasPrevious(): boolean;
|
||||||
|
hasNext(): boolean;
|
||||||
|
}
|
15
dist/Pagination.js
vendored
Normal file
15
dist/Pagination.js
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export default class Pagination {
|
||||||
|
constructor(models, page, perPage, totalCount) {
|
||||||
|
this.models = models;
|
||||||
|
this.page = page;
|
||||||
|
this.perPage = perPage;
|
||||||
|
this.totalCount = totalCount;
|
||||||
|
}
|
||||||
|
hasPrevious() {
|
||||||
|
return this.page > 1;
|
||||||
|
}
|
||||||
|
hasNext() {
|
||||||
|
return this.models.length >= this.perPage && this.page * this.perPage < this.totalCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGFnaW5hdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIuLyIsInNvdXJjZXMiOlsiUGFnaW5hdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxNQUFNLENBQUMsT0FBTyxPQUFPLFVBQVU7SUFNM0IsWUFBWSxNQUFXLEVBQUUsSUFBWSxFQUFFLE9BQWUsRUFBRSxVQUFrQjtRQUN0RSxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQztJQUNqQyxDQUFDO0lBRU0sV0FBVztRQUNkLE9BQU8sSUFBSSxDQUFDLElBQUksR0FBRyxDQUFDLENBQUM7SUFDekIsQ0FBQztJQUVNLE9BQU87UUFDVixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7SUFDNUYsQ0FBQztDQUVKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IE1vZGVsIGZyb20gXCIuL2RiL01vZGVsXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFBhZ2luYXRpb248VCBleHRlbmRzIE1vZGVsPiB7XG4gICAgcHJpdmF0ZSByZWFkb25seSBtb2RlbHM6IFRbXTtcbiAgICBwdWJsaWMgcmVhZG9ubHkgcGFnZTogbnVtYmVyO1xuICAgIHB1YmxpYyByZWFkb25seSBwZXJQYWdlOiBudW1iZXI7XG4gICAgcHVibGljIHJlYWRvbmx5IHRvdGFsQ291bnQ6IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKG1vZGVsczogVFtdLCBwYWdlOiBudW1iZXIsIHBlclBhZ2U6IG51bWJlciwgdG90YWxDb3VudDogbnVtYmVyKSB7XG4gICAgICAgIHRoaXMubW9kZWxzID0gbW9kZWxzO1xuICAgICAgICB0aGlzLnBhZ2UgPSBwYWdlO1xuICAgICAgICB0aGlzLnBlclBhZ2UgPSBwZXJQYWdlO1xuICAgICAgICB0aGlzLnRvdGFsQ291bnQgPSB0b3RhbENvdW50O1xuICAgIH1cblxuICAgIHB1YmxpYyBoYXNQcmV2aW91cygpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucGFnZSA+IDE7XG4gICAgfVxuXG4gICAgcHVibGljIGhhc05leHQoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiB0aGlzLm1vZGVscy5sZW5ndGggPj0gdGhpcy5wZXJQYWdlICYmIHRoaXMucGFnZSAqIHRoaXMucGVyUGFnZSA8IHRoaXMudG90YWxDb3VudDtcbiAgICB9XG5cbn0iXX0=
|
7
dist/Utils.d.ts
vendored
Normal file
7
dist/Utils.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export declare function sleep(ms: number): Promise<void>;
|
||||||
|
export declare abstract class WrappingError extends Error {
|
||||||
|
readonly cause?: Error;
|
||||||
|
protected constructor(message: string, cause?: Error);
|
||||||
|
get name(): string;
|
||||||
|
}
|
||||||
|
export declare function cryptoRandomDictionary(size: number, dictionary: string): string;
|
38
dist/Utils.js
vendored
Normal file
38
dist/Utils.js
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
export function sleep(ms) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return yield new Promise(resolve => {
|
||||||
|
setTimeout(() => resolve(), ms);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export class WrappingError extends Error {
|
||||||
|
constructor(message, cause) {
|
||||||
|
super(message);
|
||||||
|
this.cause = cause;
|
||||||
|
if (cause !== undefined) {
|
||||||
|
this.stack = (this.stack || '') + `\n> Caused by: ${cause.stack}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function cryptoRandomDictionary(size, dictionary) {
|
||||||
|
const randomBytes = crypto.randomBytes(size);
|
||||||
|
const output = new Array(size);
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
output[i] = dictionary[Math.floor((randomBytes[i] / 255) * dictionary.length)];
|
||||||
|
}
|
||||||
|
return output.join('');
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXRpbHMuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbIlV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRWpDLE1BQU0sVUFBZ0IsS0FBSyxDQUFDLEVBQVU7O1FBQ2xDLE9BQU8sTUFBTSxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRTtZQUMvQixVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDcEMsQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDO0NBQUE7QUFFRCxNQUFNLE9BQWdCLGFBQWMsU0FBUSxLQUFLO0lBRzdDLFlBQXNCLE9BQWUsRUFBRSxLQUFhO1FBQ2hELEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBRW5CLElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRTtZQUNyQixJQUFJLENBQUMsS0FBSyxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsR0FBRyxrQkFBa0IsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1NBQ3JFO0lBQ0wsQ0FBQztJQUVELElBQUksSUFBSTtRQUNKLE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUM7SUFDakMsQ0FBQztDQUNKO0FBRUQsTUFBTSxVQUFVLHNCQUFzQixDQUFDLElBQVksRUFBRSxVQUFrQjtJQUNuRSxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdDLE1BQU0sTUFBTSxHQUFHLElBQUksS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRS9CLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxJQUFJLEVBQUUsQ0FBQyxFQUFFLEVBQUU7UUFDM0IsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0tBQ2xGO0lBRUQsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQzNCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjcnlwdG8gZnJvbSBcImNyeXB0b1wiO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2xlZXAobXM6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHtcbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiByZXNvbHZlKCksIG1zKTtcbiAgICB9KTtcbn1cblxuZXhwb3J0IGFic3RyYWN0IGNsYXNzIFdyYXBwaW5nRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gICAgcHVibGljIHJlYWRvbmx5IGNhdXNlPzogRXJyb3I7XG5cbiAgICBwcm90ZWN0ZWQgY29uc3RydWN0b3IobWVzc2FnZTogc3RyaW5nLCBjYXVzZT86IEVycm9yKSB7XG4gICAgICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgICAgICB0aGlzLmNhdXNlID0gY2F1c2U7XG5cbiAgICAgICAgaWYgKGNhdXNlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHRoaXMuc3RhY2sgPSAodGhpcy5zdGFjayB8fCAnJykgKyBgXFxuPiBDYXVzZWQgYnk6ICR7Y2F1c2Uuc3RhY2t9YDtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGdldCBuYW1lKCk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiB0aGlzLmNvbnN0cnVjdG9yLm5hbWU7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY3J5cHRvUmFuZG9tRGljdGlvbmFyeShzaXplOiBudW1iZXIsIGRpY3Rpb25hcnk6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgcmFuZG9tQnl0ZXMgPSBjcnlwdG8ucmFuZG9tQnl0ZXMoc2l6ZSk7XG4gICAgY29uc3Qgb3V0cHV0ID0gbmV3IEFycmF5KHNpemUpO1xuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzaXplOyBpKyspIHtcbiAgICAgICAgb3V0cHV0W2ldID0gZGljdGlvbmFyeVtNYXRoLmZsb29yKChyYW5kb21CeXRlc1tpXSAvIDI1NSkgKiBkaWN0aW9uYXJ5Lmxlbmd0aCldO1xuICAgIH1cblxuICAgIHJldHVybiBvdXRwdXQuam9pbignJyk7XG59Il19
|
8
dist/WebSocketListener.d.ts
vendored
Normal file
8
dist/WebSocketListener.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
/// <reference types="express-session" />
|
||||||
|
import WebSocket from "ws";
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
export default abstract class WebSocketListener {
|
||||||
|
abstract path(): string;
|
||||||
|
abstract handle(socket: WebSocket, request: IncomingMessage, session: Express.SessionData): Promise<void>;
|
||||||
|
}
|
3
dist/WebSocketListener.js
vendored
Normal file
3
dist/WebSocketListener.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default class WebSocketListener {
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2ViU29ja2V0TGlzdGVuZXIuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbIldlYlNvY2tldExpc3RlbmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE1BQU0sQ0FBQyxPQUFPLE9BQWdCLGlCQUFpQjtDQUk5QyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBXZWJTb2NrZXQgZnJvbSBcIndzXCI7XG5pbXBvcnQge0luY29taW5nTWVzc2FnZX0gZnJvbSBcImh0dHBcIjtcblxuZXhwb3J0IGRlZmF1bHQgYWJzdHJhY3QgY2xhc3MgV2ViU29ja2V0TGlzdGVuZXIge1xuICAgIHB1YmxpYyBhYnN0cmFjdCBwYXRoKCk6IHN0cmluZztcblxuICAgIHB1YmxpYyBhYnN0cmFjdCBhc3luYyBoYW5kbGUoc29ja2V0OiBXZWJTb2NrZXQsIHJlcXVlc3Q6IEluY29taW5nTWVzc2FnZSwgc2Vzc2lvbjogRXhwcmVzcy5TZXNzaW9uRGF0YSk6IFByb21pc2U8dm9pZD47XG59Il19
|
6
dist/components/CsrfProtectionComponent.d.ts
vendored
Normal file
6
dist/components/CsrfProtectionComponent.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class CsrfProtectionComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
57
dist/components/CsrfProtectionComponent.js
vendored
Normal file
57
dist/components/CsrfProtectionComponent.js
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { BadRequestError } from "../HttpError";
|
||||||
|
export default class CsrfProtectionComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
res.locals.getCSRFToken = () => {
|
||||||
|
if (typeof req.session.csrf !== 'string') {
|
||||||
|
req.session.csrf = crypto.randomBytes(64).toString('base64');
|
||||||
|
}
|
||||||
|
return req.session.csrf;
|
||||||
|
};
|
||||||
|
if (!['GET', 'HEAD', 'OPTIONS'].find(s => s === req.method)) {
|
||||||
|
if (req.session.csrf === undefined) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `You weren't assigned any CSRF token.`);
|
||||||
|
}
|
||||||
|
else if (req.body.csrf === undefined) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `You didn't provide any CSRF token.`);
|
||||||
|
}
|
||||||
|
else if (req.session.csrf !== req.body.csrf) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `Tokens don't match.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class InvalidCsrfTokenError extends BadRequestError {
|
||||||
|
constructor(url, details, cause) {
|
||||||
|
super(`Invalid CSRF token`, `${details} We can't process this request. Please try again.`, url, cause);
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return 'Invalid CSRF Token';
|
||||||
|
}
|
||||||
|
get errorCode() {
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ3NyZlByb3RlY3Rpb25Db21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvQ3NyZlByb3RlY3Rpb25Db21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRCxPQUFPLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFDNUIsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLGNBQWMsQ0FBQztBQUU3QyxNQUFNLENBQUMsT0FBTyxPQUFPLHVCQUF3QixTQUFRLG9CQUEwQjtJQUM5RCxLQUFLLENBQUMsR0FBWSxFQUFFLE1BQWM7O1lBQzNDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFO2dCQUMxQixJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRTtvQkFDZCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7aUJBQzlDO2dCQUVELEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxHQUFHLEdBQUcsRUFBRTtvQkFDM0IsSUFBSSxPQUFPLEdBQUcsQ0FBQyxPQUFRLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRTt3QkFDdkMsR0FBRyxDQUFDLE9BQVEsQ0FBQyxJQUFJLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7cUJBQ2pFO29CQUNELE9BQU8sR0FBRyxDQUFDLE9BQVEsQ0FBQyxJQUFJLENBQUM7Z0JBQzdCLENBQUMsQ0FBQztnQkFFRixJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUU7b0JBQ3pELElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEtBQUssU0FBUyxFQUFFO3dCQUNoQyxNQUFNLElBQUkscUJBQXFCLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxzQ0FBc0MsQ0FBQyxDQUFDO3FCQUN4Rjt5QkFBTSxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLFNBQVMsRUFBRTt3QkFDcEMsTUFBTSxJQUFJLHFCQUFxQixDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsb0NBQW9DLENBQUMsQ0FBQztxQkFDdEY7eUJBQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRTt3QkFDM0MsTUFBTSxJQUFJLHFCQUFxQixDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUscUJBQXFCLENBQUMsQ0FBQztxQkFDdkU7aUJBQ0o7Z0JBQ0QsSUFBSSxFQUFFLENBQUM7WUFDWCxDQUFDLENBQUMsQ0FBQztRQUNQLENBQUM7S0FBQTtJQUVZLElBQUk7O1FBQ2pCLENBQUM7S0FBQTtDQUNKO0FBRUQsTUFBTSxxQkFBc0IsU0FBUSxlQUFlO0lBQy9DLFlBQVksR0FBVyxFQUFFLE9BQWUsRUFBRSxLQUFhO1FBQ25ELEtBQUssQ0FDRCxvQkFBb0IsRUFDcEIsR0FBRyxPQUFPLG1EQUFtRCxFQUM3RCxHQUFHLEVBQ0gsS0FBSyxDQUNSLENBQUM7SUFDTixDQUFDO0lBRUQsSUFBSSxJQUFJO1FBQ0osT0FBTyxvQkFBb0IsQ0FBQztJQUNoQyxDQUFDO0lBRUQsSUFBSSxTQUFTO1FBQ1QsT0FBTyxHQUFHLENBQUM7SUFDZixDQUFDO0NBQ0oiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQXBwbGljYXRpb25Db21wb25lbnQgZnJvbSBcIi4uL0FwcGxpY2F0aW9uQ29tcG9uZW50XCI7XG5pbXBvcnQge0V4cHJlc3MsIFJvdXRlcn0gZnJvbSBcImV4cHJlc3NcIjtcbmltcG9ydCBjcnlwdG8gZnJvbSBcImNyeXB0b1wiO1xuaW1wb3J0IHtCYWRSZXF1ZXN0RXJyb3J9IGZyb20gXCIuLi9IdHRwRXJyb3JcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQ3NyZlByb3RlY3Rpb25Db21wb25lbnQgZXh0ZW5kcyBBcHBsaWNhdGlvbkNvbXBvbmVudDx2b2lkPiB7XG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcm91dGVyLnVzZSgocmVxLCByZXMsIG5leHQpID0+IHtcbiAgICAgICAgICAgIGlmICghcmVxLnNlc3Npb24pIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Nlc3Npb24gaXMgdW5hdmFpbGFibGUuJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJlcy5sb2NhbHMuZ2V0Q1NSRlRva2VuID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgcmVxLnNlc3Npb24hLmNzcmYgIT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlcS5zZXNzaW9uIS5jc3JmID0gY3J5cHRvLnJhbmRvbUJ5dGVzKDY0KS50b1N0cmluZygnYmFzZTY0Jyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZXEuc2Vzc2lvbiEuY3NyZjtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIGlmICghWydHRVQnLCAnSEVBRCcsICdPUFRJT05TJ10uZmluZChzID0+IHMgPT09IHJlcS5tZXRob2QpKSB7XG4gICAgICAgICAgICAgICAgaWYgKHJlcS5zZXNzaW9uLmNzcmYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZENzcmZUb2tlbkVycm9yKHJlcS5iYXNlVXJsLCBgWW91IHdlcmVuJ3QgYXNzaWduZWQgYW55IENTUkYgdG9rZW4uYCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIGlmIChyZXEuYm9keS5jc3JmID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEludmFsaWRDc3JmVG9rZW5FcnJvcihyZXEuYmFzZVVybCwgYFlvdSBkaWRuJ3QgcHJvdmlkZSBhbnkgQ1NSRiB0b2tlbi5gKTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKHJlcS5zZXNzaW9uLmNzcmYgIT09IHJlcS5ib2R5LmNzcmYpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEludmFsaWRDc3JmVG9rZW5FcnJvcihyZXEuYmFzZVVybCwgYFRva2VucyBkb24ndCBtYXRjaC5gKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBuZXh0KCk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBzdG9wKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIH1cbn1cblxuY2xhc3MgSW52YWxpZENzcmZUb2tlbkVycm9yIGV4dGVuZHMgQmFkUmVxdWVzdEVycm9yIHtcbiAgICBjb25zdHJ1Y3Rvcih1cmw6IHN0cmluZywgZGV0YWlsczogc3RyaW5nLCBjYXVzZT86IEVycm9yKSB7XG4gICAgICAgIHN1cGVyKFxuICAgICAgICAgICAgYEludmFsaWQgQ1NSRiB0b2tlbmAsXG4gICAgICAgICAgICBgJHtkZXRhaWxzfSBXZSBjYW4ndCBwcm9jZXNzIHRoaXMgcmVxdWVzdC4gUGxlYXNlIHRyeSBhZ2Fpbi5gLFxuICAgICAgICAgICAgdXJsLFxuICAgICAgICAgICAgY2F1c2VcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBnZXQgbmFtZSgpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gJ0ludmFsaWQgQ1NSRiBUb2tlbic7XG4gICAgfVxuXG4gICAgZ2V0IGVycm9yQ29kZSgpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gNDAxO1xuICAgIH1cbn1cbiJdfQ==
|
12
dist/components/ExpressAppComponent.d.ts
vendored
Normal file
12
dist/components/ExpressAppComponent.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
import { Server } from "http";
|
||||||
|
export default class ExpressAppComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly port;
|
||||||
|
private server?;
|
||||||
|
constructor(port: number);
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
getServer(): Server;
|
||||||
|
}
|
45
dist/components/ExpressAppComponent.js
vendored
Normal file
45
dist/components/ExpressAppComponent.js
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import express from "express";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
export default class ExpressAppComponent extends ApplicationComponent {
|
||||||
|
constructor(port) {
|
||||||
|
super();
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.server = app.listen(this.port, 'localhost', () => {
|
||||||
|
Logger.info(`Web server running on localhost:${this.port}.`);
|
||||||
|
});
|
||||||
|
router.use(express.json());
|
||||||
|
router.use(express.urlencoded());
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
req.models = {};
|
||||||
|
req.modelCollections = {};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.server) {
|
||||||
|
yield this.close('Webserver', this.server, this.server.close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getServer() {
|
||||||
|
if (!this.server)
|
||||||
|
throw 'Server was not initialized.';
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRXhwcmVzc0FwcENvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIuLyIsInNvdXJjZXMiOlsiY29tcG9uZW50cy9FeHByZXNzQXBwQ29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sb0JBQW9CLE1BQU0seUJBQXlCLENBQUM7QUFDM0QsT0FBTyxPQUEwQixNQUFNLFNBQVMsQ0FBQztBQUNqRCxPQUFPLE1BQU0sTUFBTSxXQUFXLENBQUM7QUFHL0IsTUFBTSxDQUFDLE9BQU8sT0FBTyxtQkFBb0IsU0FBUSxvQkFBMEI7SUFJdkUsWUFBWSxJQUFZO1FBQ3BCLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7SUFDckIsQ0FBQztJQUVZLEtBQUssQ0FBQyxHQUFZLEVBQUUsTUFBYzs7WUFDM0MsSUFBSSxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFLEdBQUcsRUFBRTtnQkFDbEQsTUFBTSxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsSUFBSSxDQUFDLElBQUksR0FBRyxDQUFDLENBQUM7WUFDakUsQ0FBQyxDQUFDLENBQUM7WUFFSCxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQzNCLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7WUFFakMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7Z0JBQzFCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDO2dCQUNoQixHQUFHLENBQUMsZ0JBQWdCLEdBQUcsRUFBRSxDQUFDO2dCQUMxQixJQUFJLEVBQUUsQ0FBQztZQUNYLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztLQUFBO0lBRVksSUFBSTs7WUFDYixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ2IsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLFdBQVcsRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7YUFDakU7UUFDTCxDQUFDO0tBQUE7SUFFTSxTQUFTO1FBQ1osSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNO1lBQUUsTUFBTSw2QkFBNkIsQ0FBQztRQUN0RCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDdkIsQ0FBQztDQUNKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEFwcGxpY2F0aW9uQ29tcG9uZW50IGZyb20gXCIuLi9BcHBsaWNhdGlvbkNvbXBvbmVudFwiO1xuaW1wb3J0IGV4cHJlc3MsIHtFeHByZXNzLCBSb3V0ZXJ9IGZyb20gXCJleHByZXNzXCI7XG5pbXBvcnQgTG9nZ2VyIGZyb20gXCIuLi9Mb2dnZXJcIjtcbmltcG9ydCB7U2VydmVyfSBmcm9tIFwiaHR0cFwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBFeHByZXNzQXBwQ29tcG9uZW50IGV4dGVuZHMgQXBwbGljYXRpb25Db21wb25lbnQ8dm9pZD4ge1xuICAgIHByaXZhdGUgcmVhZG9ubHkgcG9ydDogbnVtYmVyO1xuICAgIHByaXZhdGUgc2VydmVyPzogU2VydmVyO1xuXG4gICAgY29uc3RydWN0b3IocG9ydDogbnVtYmVyKSB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgICAgIHRoaXMucG9ydCA9IHBvcnQ7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgdGhpcy5zZXJ2ZXIgPSBhcHAubGlzdGVuKHRoaXMucG9ydCwgJ2xvY2FsaG9zdCcsICgpID0+IHtcbiAgICAgICAgICAgIExvZ2dlci5pbmZvKGBXZWIgc2VydmVyIHJ1bm5pbmcgb24gbG9jYWxob3N0OiR7dGhpcy5wb3J0fS5gKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgcm91dGVyLnVzZShleHByZXNzLmpzb24oKSk7XG4gICAgICAgIHJvdXRlci51c2UoZXhwcmVzcy51cmxlbmNvZGVkKCkpO1xuXG4gICAgICAgIHJvdXRlci51c2UoKHJlcSwgcmVzLCBuZXh0KSA9PiB7XG4gICAgICAgICAgICByZXEubW9kZWxzID0ge307XG4gICAgICAgICAgICByZXEubW9kZWxDb2xsZWN0aW9ucyA9IHt9O1xuICAgICAgICAgICAgbmV4dCgpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKHRoaXMuc2VydmVyKSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmNsb3NlKCdXZWJzZXJ2ZXInLCB0aGlzLnNlcnZlciwgdGhpcy5zZXJ2ZXIuY2xvc2UpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGdldFNlcnZlcigpOiBTZXJ2ZXIge1xuICAgICAgICBpZiAoIXRoaXMuc2VydmVyKSB0aHJvdyAnU2VydmVyIHdhcyBub3QgaW5pdGlhbGl6ZWQuJztcbiAgICAgICAgcmV0dXJuIHRoaXMuc2VydmVyO1xuICAgIH1cbn0iXX0=
|
6
dist/components/FormHelperComponent.d.ts
vendored
Normal file
6
dist/components/FormHelperComponent.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class FormHelperComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
52
dist/components/FormHelperComponent.js
vendored
Normal file
52
dist/components/FormHelperComponent.js
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
export default class FormHelperComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
res.locals.query = req.query;
|
||||||
|
let _validation = null;
|
||||||
|
res.locals.validation = () => {
|
||||||
|
if (!_validation) {
|
||||||
|
const v = req.flash('validation');
|
||||||
|
_validation = v.length > 0 ? v[0] : null;
|
||||||
|
}
|
||||||
|
return _validation;
|
||||||
|
};
|
||||||
|
let _previousFormData = null;
|
||||||
|
res.locals.previousFormData = () => {
|
||||||
|
if (!_previousFormData) {
|
||||||
|
const v = req.flash('previousFormData');
|
||||||
|
_previousFormData = v.length > 0 ? v[0] : null;
|
||||||
|
}
|
||||||
|
return _previousFormData;
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (['GET', 'POST'].find(m => m === req.method)) {
|
||||||
|
if (typeof req.body === 'object') {
|
||||||
|
req.flash('previousFormData', req.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRm9ybUhlbHBlckNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIuLyIsInNvdXJjZXMiOlsiY29tcG9uZW50cy9Gb3JtSGVscGVyQ29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sb0JBQW9CLE1BQU0seUJBQXlCLENBQUM7QUFHM0QsTUFBTSxDQUFDLE9BQU8sT0FBTyxtQkFBb0IsU0FBUSxvQkFBMEI7SUFDMUQsS0FBSyxDQUFDLEdBQVksRUFBRSxNQUFjOztZQUMzQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRTtnQkFDMUIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUU7b0JBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO2lCQUM5QztnQkFFRCxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDO2dCQUU3QixJQUFJLFdBQVcsR0FBUSxJQUFJLENBQUM7Z0JBQzVCLEdBQUcsQ0FBQyxNQUFNLENBQUMsVUFBVSxHQUFHLEdBQUcsRUFBRTtvQkFDekIsSUFBSSxDQUFDLFdBQVcsRUFBRTt3QkFDZCxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO3dCQUNsQyxXQUFXLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO3FCQUM1QztvQkFFRCxPQUFPLFdBQVcsQ0FBQztnQkFDdkIsQ0FBQyxDQUFBO2dCQUVELElBQUksaUJBQWlCLEdBQVEsSUFBSSxDQUFDO2dCQUNsQyxHQUFHLENBQUMsTUFBTSxDQUFDLGdCQUFnQixHQUFHLEdBQUcsRUFBRTtvQkFDL0IsSUFBSSxDQUFDLGlCQUFpQixFQUFFO3dCQUNwQixNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLENBQUM7d0JBQ3hDLGlCQUFpQixHQUFHLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztxQkFDbkQ7b0JBRUQsT0FBTyxpQkFBaUIsQ0FBQztnQkFDN0IsQ0FBQyxDQUFDO2dCQUNGLElBQUksRUFBRSxDQUFDO1lBQ1gsQ0FBQyxDQUFDLENBQUM7WUFFSCxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRTtnQkFDMUIsSUFBSSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFO29CQUM3QyxJQUFJLE9BQU8sR0FBRyxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUU7d0JBQzlCLEdBQUcsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO3FCQUMzQztpQkFDSjtnQkFDRCxJQUFJLEVBQUUsQ0FBQztZQUNYLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztLQUFBO0lBRVksSUFBSTs7UUFDakIsQ0FBQztLQUFBO0NBRUoiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQXBwbGljYXRpb25Db21wb25lbnQgZnJvbSBcIi4uL0FwcGxpY2F0aW9uQ29tcG9uZW50XCI7XG5pbXBvcnQge0V4cHJlc3MsIFJvdXRlcn0gZnJvbSBcImV4cHJlc3NcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRm9ybUhlbHBlckNvbXBvbmVudCBleHRlbmRzIEFwcGxpY2F0aW9uQ29tcG9uZW50PHZvaWQ+IHtcbiAgICBwdWJsaWMgYXN5bmMgc3RhcnQoYXBwOiBFeHByZXNzLCByb3V0ZXI6IFJvdXRlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByb3V0ZXIudXNlKChyZXEsIHJlcywgbmV4dCkgPT4ge1xuICAgICAgICAgICAgaWYgKCFyZXEuc2Vzc2lvbikge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignU2Vzc2lvbiBpcyB1bmF2YWlsYWJsZS4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzLmxvY2Fscy5xdWVyeSA9IHJlcS5xdWVyeTtcblxuICAgICAgICAgICAgbGV0IF92YWxpZGF0aW9uOiBhbnkgPSBudWxsO1xuICAgICAgICAgICAgcmVzLmxvY2Fscy52YWxpZGF0aW9uID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICghX3ZhbGlkYXRpb24pIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdiA9IHJlcS5mbGFzaCgndmFsaWRhdGlvbicpO1xuICAgICAgICAgICAgICAgICAgICBfdmFsaWRhdGlvbiA9IHYubGVuZ3RoID4gMCA/IHZbMF0gOiBudWxsO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiBfdmFsaWRhdGlvbjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgbGV0IF9wcmV2aW91c0Zvcm1EYXRhOiBhbnkgPSBudWxsO1xuICAgICAgICAgICAgcmVzLmxvY2Fscy5wcmV2aW91c0Zvcm1EYXRhID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICghX3ByZXZpb3VzRm9ybURhdGEpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdiA9IHJlcS5mbGFzaCgncHJldmlvdXNGb3JtRGF0YScpO1xuICAgICAgICAgICAgICAgICAgICBfcHJldmlvdXNGb3JtRGF0YSA9IHYubGVuZ3RoID4gMCA/IHYgWzBdIDogbnVsbDtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gX3ByZXZpb3VzRm9ybURhdGE7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgbmV4dCgpO1xuICAgICAgICB9KTtcblxuICAgICAgICByb3V0ZXIudXNlKChyZXEsIHJlcywgbmV4dCkgPT4ge1xuICAgICAgICAgICAgaWYgKFsnR0VUJywgJ1BPU1QnXS5maW5kKG0gPT4gbSA9PT0gcmVxLm1ldGhvZCkpIHtcbiAgICAgICAgICAgICAgICBpZiAodHlwZW9mIHJlcS5ib2R5ID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgICAgICAgICAgICByZXEuZmxhc2goJ3ByZXZpb3VzRm9ybURhdGEnLCByZXEuYm9keSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbmV4dCgpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB9XG5cbn0iXX0=
|
6
dist/components/LogRequestsComponent.d.ts
vendored
Normal file
6
dist/components/LogRequestsComponent.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class LogRequestsComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
31
dist/components/LogRequestsComponent.js
vendored
Normal file
31
dist/components/LogRequestsComponent.js
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import onFinished from "on-finished";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
export default class LogRequestsComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
onFinished(res, (err) => {
|
||||||
|
if (!err) {
|
||||||
|
Logger.info(`${req.method} ${req.originalUrl} - ${res.statusCode}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTG9nUmVxdWVzdHNDb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvTG9nUmVxdWVzdHNDb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUMzRCxPQUFPLFVBQVUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxNQUFNLE1BQU0sV0FBVyxDQUFDO0FBRy9CLE1BQU0sQ0FBQyxPQUFPLE9BQU8sb0JBQXFCLFNBQVEsb0JBQTBCO0lBQzNELEtBQUssQ0FBQyxHQUFZLEVBQUUsTUFBYzs7WUFDM0MsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7Z0JBQzFCLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtvQkFDcEIsSUFBSSxDQUFDLEdBQUcsRUFBRTt3QkFDTixNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsR0FBRyxDQUFDLE1BQU0sSUFBSSxHQUFHLENBQUMsV0FBVyxNQUFNLEdBQUcsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO3FCQUN2RTtnQkFDTCxDQUFDLENBQUMsQ0FBQztnQkFDSCxJQUFJLEVBQUUsQ0FBQztZQUNYLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztLQUFBO0lBRVksSUFBSTs7UUFDakIsQ0FBQztLQUFBO0NBRUoiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQXBwbGljYXRpb25Db21wb25lbnQgZnJvbSBcIi4uL0FwcGxpY2F0aW9uQ29tcG9uZW50XCI7XG5pbXBvcnQgb25GaW5pc2hlZCBmcm9tIFwib24tZmluaXNoZWRcIjtcbmltcG9ydCBMb2dnZXIgZnJvbSBcIi4uL0xvZ2dlclwiO1xuaW1wb3J0IHtFeHByZXNzLCBSb3V0ZXJ9IGZyb20gXCJleHByZXNzXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIExvZ1JlcXVlc3RzQ29tcG9uZW50IGV4dGVuZHMgQXBwbGljYXRpb25Db21wb25lbnQ8dm9pZD4ge1xuICAgIHB1YmxpYyBhc3luYyBzdGFydChhcHA6IEV4cHJlc3MsIHJvdXRlcjogUm91dGVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJvdXRlci51c2UoKHJlcSwgcmVzLCBuZXh0KSA9PiB7XG4gICAgICAgICAgICBvbkZpbmlzaGVkKHJlcywgKGVycikgPT4ge1xuICAgICAgICAgICAgICAgIGlmICghZXJyKSB7XG4gICAgICAgICAgICAgICAgICAgIExvZ2dlci5pbmZvKGAke3JlcS5tZXRob2R9ICR7cmVxLm9yaWdpbmFsVXJsfSAtICR7cmVzLnN0YXR1c0NvZGV9YCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBuZXh0KCk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBzdG9wKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIH1cblxufSJdfQ==
|
6
dist/components/MailComponent.d.ts
vendored
Normal file
6
dist/components/MailComponent.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class MailComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
24
dist/components/MailComponent.js
vendored
Normal file
24
dist/components/MailComponent.js
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import Mail from "../Mail";
|
||||||
|
export default class MailComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield this.prepare('Mail connection', () => Mail.prepare());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
Mail.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWFpbENvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIuLyIsInNvdXJjZXMiOlsiY29tcG9uZW50cy9NYWlsQ29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sb0JBQW9CLE1BQU0seUJBQXlCLENBQUM7QUFFM0QsT0FBTyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBRTNCLE1BQU0sQ0FBQyxPQUFPLE9BQU8sYUFBYyxTQUFRLG9CQUEwQjtJQUNwRCxLQUFLLENBQUMsR0FBWSxFQUFFLE1BQWM7O1lBQzNDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztRQUNoRSxDQUFDO0tBQUE7SUFFWSxJQUFJOztZQUNiLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNmLENBQUM7S0FBQTtDQUVKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEFwcGxpY2F0aW9uQ29tcG9uZW50IGZyb20gXCIuLi9BcHBsaWNhdGlvbkNvbXBvbmVudFwiO1xuaW1wb3J0IHtFeHByZXNzLCBSb3V0ZXJ9IGZyb20gXCJleHByZXNzXCI7XG5pbXBvcnQgTWFpbCBmcm9tIFwiLi4vTWFpbFwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNYWlsQ29tcG9uZW50IGV4dGVuZHMgQXBwbGljYXRpb25Db21wb25lbnQ8dm9pZD4ge1xuICAgIHB1YmxpYyBhc3luYyBzdGFydChhcHA6IEV4cHJlc3MsIHJvdXRlcjogUm91dGVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGF3YWl0IHRoaXMucHJlcGFyZSgnTWFpbCBjb25uZWN0aW9uJywgKCkgPT4gTWFpbC5wcmVwYXJlKCkpO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBzdG9wKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBNYWlsLmVuZCgpO1xuICAgIH1cblxufSJdfQ==
|
10
dist/components/MaintenanceComponent.d.ts
vendored
Normal file
10
dist/components/MaintenanceComponent.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
import Application from "../Application";
|
||||||
|
export default class MaintenanceComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly application;
|
||||||
|
private readonly canServe;
|
||||||
|
constructor(application: Application, canServe: () => boolean);
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
42
dist/components/MaintenanceComponent.js
vendored
Normal file
42
dist/components/MaintenanceComponent.js
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { ServiceUnavailableHttpError } from "../HttpError";
|
||||||
|
export default class MaintenanceComponent extends ApplicationComponent {
|
||||||
|
constructor(application, canServe) {
|
||||||
|
super();
|
||||||
|
this.application = application;
|
||||||
|
this.canServe = canServe;
|
||||||
|
}
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (res.headersSent) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
if (!this.application.isReady()) {
|
||||||
|
res.header({ 'Retry-After': 60 });
|
||||||
|
res.locals.refresh_after = 5;
|
||||||
|
throw new ServiceUnavailableHttpError('Watch My Stream is readying up. Please wait a few seconds...');
|
||||||
|
}
|
||||||
|
if (!this.canServe()) {
|
||||||
|
res.locals.refresh_after = 30;
|
||||||
|
throw new ServiceUnavailableHttpError('Watch My Stream is unavailable due to failure of dependent services.');
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWFpbnRlbmFuY2VDb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvTWFpbnRlbmFuY2VDb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRCxPQUFPLEVBQUMsMkJBQTJCLEVBQUMsTUFBTSxjQUFjLENBQUM7QUFHekQsTUFBTSxDQUFDLE9BQU8sT0FBTyxvQkFBcUIsU0FBUSxvQkFBMEI7SUFJeEUsWUFBWSxXQUF3QixFQUFFLFFBQXVCO1FBQ3pELEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7UUFDL0IsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDN0IsQ0FBQztJQUVZLEtBQUssQ0FBQyxHQUFZLEVBQUUsTUFBYzs7WUFDM0MsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsSUFBa0IsRUFBRSxFQUFFO2dCQUMzRCxJQUFJLEdBQUcsQ0FBQyxXQUFXLEVBQUU7b0JBQ2pCLE9BQU8sSUFBSSxFQUFFLENBQUM7aUJBQ2pCO2dCQUVELElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLE9BQU8sRUFBRSxFQUFFO29CQUM3QixHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUMsYUFBYSxFQUFFLEVBQUUsRUFBQyxDQUFDLENBQUM7b0JBQ2hDLEdBQUcsQ0FBQyxNQUFNLENBQUMsYUFBYSxHQUFHLENBQUMsQ0FBQztvQkFDN0IsTUFBTSxJQUFJLDJCQUEyQixDQUFDLDhEQUE4RCxDQUFDLENBQUM7aUJBQ3pHO2dCQUVELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLEVBQUU7b0JBQ2xCLEdBQUcsQ0FBQyxNQUFNLENBQUMsYUFBYSxHQUFHLEVBQUUsQ0FBQztvQkFDOUIsTUFBTSxJQUFJLDJCQUEyQixDQUFDLHNFQUFzRSxDQUFDLENBQUM7aUJBQ2pIO2dCQUVELElBQUksRUFBRSxDQUFDO1lBQ1gsQ0FBQyxDQUFDLENBQUM7UUFDUCxDQUFDO0tBQUE7SUFFWSxJQUFJOztRQUNqQixDQUFDO0tBQUE7Q0FFSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBcHBsaWNhdGlvbkNvbXBvbmVudCBmcm9tIFwiLi4vQXBwbGljYXRpb25Db21wb25lbnRcIjtcbmltcG9ydCB7RXhwcmVzcywgTmV4dEZ1bmN0aW9uLCBSZXF1ZXN0LCBSZXNwb25zZSwgUm91dGVyfSBmcm9tIFwiZXhwcmVzc1wiO1xuaW1wb3J0IHtTZXJ2aWNlVW5hdmFpbGFibGVIdHRwRXJyb3J9IGZyb20gXCIuLi9IdHRwRXJyb3JcIjtcbmltcG9ydCBBcHBsaWNhdGlvbiBmcm9tIFwiLi4vQXBwbGljYXRpb25cIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTWFpbnRlbmFuY2VDb21wb25lbnQgZXh0ZW5kcyBBcHBsaWNhdGlvbkNvbXBvbmVudDx2b2lkPiB7XG4gICAgcHJpdmF0ZSByZWFkb25seSBhcHBsaWNhdGlvbjogQXBwbGljYXRpb247XG4gICAgcHJpdmF0ZSByZWFkb25seSBjYW5TZXJ2ZTogKCkgPT4gYm9vbGVhbjtcblxuICAgIGNvbnN0cnVjdG9yKGFwcGxpY2F0aW9uOiBBcHBsaWNhdGlvbiwgY2FuU2VydmU6ICgpID0+IGJvb2xlYW4pIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5hcHBsaWNhdGlvbiA9IGFwcGxpY2F0aW9uO1xuICAgICAgICB0aGlzLmNhblNlcnZlID0gY2FuU2VydmU7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgcm91dGVyLnVzZSgocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pID0+IHtcbiAgICAgICAgICAgIGlmIChyZXMuaGVhZGVyc1NlbnQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV4dCgpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIXRoaXMuYXBwbGljYXRpb24uaXNSZWFkeSgpKSB7XG4gICAgICAgICAgICAgICAgcmVzLmhlYWRlcih7J1JldHJ5LUFmdGVyJzogNjB9KTtcbiAgICAgICAgICAgICAgICByZXMubG9jYWxzLnJlZnJlc2hfYWZ0ZXIgPSA1O1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBTZXJ2aWNlVW5hdmFpbGFibGVIdHRwRXJyb3IoJ1dhdGNoIE15IFN0cmVhbSBpcyByZWFkeWluZyB1cC4gUGxlYXNlIHdhaXQgYSBmZXcgc2Vjb25kcy4uLicpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIXRoaXMuY2FuU2VydmUoKSkge1xuICAgICAgICAgICAgICAgIHJlcy5sb2NhbHMucmVmcmVzaF9hZnRlciA9IDMwO1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBTZXJ2aWNlVW5hdmFpbGFibGVIdHRwRXJyb3IoJ1dhdGNoIE15IFN0cmVhbSBpcyB1bmF2YWlsYWJsZSBkdWUgdG8gZmFpbHVyZSBvZiBkZXBlbmRlbnQgc2VydmljZXMuJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIG5leHQoKTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgfVxuXG59Il19
|
7
dist/components/MysqlComponent.d.ts
vendored
Normal file
7
dist/components/MysqlComponent.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class MysqlComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
canServe(): boolean;
|
||||||
|
}
|
27
dist/components/MysqlComponent.js
vendored
Normal file
27
dist/components/MysqlComponent.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import MysqlConnectionManager from "../db/MysqlConnectionManager";
|
||||||
|
export default class MysqlComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield this.prepare('Mysql connection', () => MysqlConnectionManager.prepare());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield MysqlConnectionManager.endPool();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
canServe() {
|
||||||
|
return MysqlConnectionManager.pool !== undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTXlzcWxDb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvTXlzcWxDb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRCxPQUFPLHNCQUFzQixNQUFNLDhCQUE4QixDQUFDO0FBRWxFLE1BQU0sQ0FBQyxPQUFPLE9BQU8sY0FBZSxTQUFRLG9CQUEwQjtJQUNyRCxLQUFLLENBQUMsR0FBWSxFQUFFLE1BQWM7O1lBQzNDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRSxHQUFHLEVBQUUsQ0FBQyxzQkFBc0IsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ25GLENBQUM7S0FBQTtJQUVZLElBQUk7O1lBQ2IsTUFBTSxzQkFBc0IsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUMzQyxDQUFDO0tBQUE7SUFFTSxRQUFRO1FBQ1gsT0FBTyxzQkFBc0IsQ0FBQyxJQUFJLEtBQUssU0FBUyxDQUFDO0lBQ3JELENBQUM7Q0FFSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBcHBsaWNhdGlvbkNvbXBvbmVudCBmcm9tIFwiLi4vQXBwbGljYXRpb25Db21wb25lbnRcIjtcbmltcG9ydCB7RXhwcmVzcywgUm91dGVyfSBmcm9tIFwiZXhwcmVzc1wiO1xuaW1wb3J0IE15c3FsQ29ubmVjdGlvbk1hbmFnZXIgZnJvbSBcIi4uL2RiL015c3FsQ29ubmVjdGlvbk1hbmFnZXJcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTXlzcWxDb21wb25lbnQgZXh0ZW5kcyBBcHBsaWNhdGlvbkNvbXBvbmVudDx2b2lkPiB7XG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgdGhpcy5wcmVwYXJlKCdNeXNxbCBjb25uZWN0aW9uJywgKCkgPT4gTXlzcWxDb25uZWN0aW9uTWFuYWdlci5wcmVwYXJlKCkpO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBzdG9wKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBhd2FpdCBNeXNxbENvbm5lY3Rpb25NYW5hZ2VyLmVuZFBvb2woKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY2FuU2VydmUoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiBNeXNxbENvbm5lY3Rpb25NYW5hZ2VyLnBvb2wgIT09IHVuZGVmaW5lZDtcbiAgICB9XG5cbn0iXX0=
|
6
dist/components/RedirectBackComponent.d.ts
vendored
Normal file
6
dist/components/RedirectBackComponent.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class RedirectBackComponent extends ApplicationComponent<void> {
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
52
dist/components/RedirectBackComponent.js
vendored
Normal file
52
dist/components/RedirectBackComponent.js
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import onFinished from "on-finished";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import { ServerError } from "../HttpError";
|
||||||
|
export default class RedirectBackComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
onFinished(res, (err) => {
|
||||||
|
if (!err && res.statusCode === 200) {
|
||||||
|
req.session.previousUrl = req.originalUrl;
|
||||||
|
Logger.debug('Prev url set to', req.session.previousUrl);
|
||||||
|
req.session.save((err) => {
|
||||||
|
if (err) {
|
||||||
|
Logger.error(err, 'Error while saving session');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.redirectBack = (defaultUrl) => {
|
||||||
|
if (req.session && typeof req.session.previousUrl === 'string') {
|
||||||
|
res.redirect(req.session.previousUrl);
|
||||||
|
}
|
||||||
|
else if (typeof defaultUrl === 'string') {
|
||||||
|
res.redirect(defaultUrl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ServerError('There is no previous url and no default redirection url was provided.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVkaXJlY3RCYWNrQ29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6Ii4vIiwic291cmNlcyI6WyJjb21wb25lbnRzL1JlZGlyZWN0QmFja0NvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQSxPQUFPLG9CQUFvQixNQUFNLHlCQUF5QixDQUFDO0FBRTNELE9BQU8sVUFBVSxNQUFNLGFBQWEsQ0FBQztBQUNyQyxPQUFPLE1BQU0sTUFBTSxXQUFXLENBQUM7QUFDL0IsT0FBTyxFQUFDLFdBQVcsRUFBQyxNQUFNLGNBQWMsQ0FBQztBQUV6QyxNQUFNLENBQUMsT0FBTyxPQUFPLHFCQUFzQixTQUFRLG9CQUEwQjtJQUM1RCxLQUFLLENBQUMsR0FBWSxFQUFFLE1BQWM7O1lBQzNDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFO2dCQUMxQixJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRTtvQkFDZCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7aUJBQzlDO2dCQUVELFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtvQkFDcEIsSUFBSSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsVUFBVSxLQUFLLEdBQUcsRUFBRTt3QkFDaEMsR0FBRyxDQUFDLE9BQVEsQ0FBQyxXQUFXLEdBQUcsR0FBRyxDQUFDLFdBQVcsQ0FBQzt3QkFDM0MsTUFBTSxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsRUFBRSxHQUFHLENBQUMsT0FBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO3dCQUMxRCxHQUFHLENBQUMsT0FBUSxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFOzRCQUN0QixJQUFJLEdBQUcsRUFBRTtnQ0FDTCxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSw0QkFBNEIsQ0FBQyxDQUFDOzZCQUNuRDt3QkFDTCxDQUFDLENBQUMsQ0FBQztxQkFDTjtnQkFDTCxDQUFDLENBQUMsQ0FBQztnQkFFSCxHQUFHLENBQUMsWUFBWSxHQUFHLENBQUMsVUFBbUIsRUFBRSxFQUFFO29CQUN2QyxJQUFJLEdBQUcsQ0FBQyxPQUFPLElBQUksT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLFdBQVcsS0FBSyxRQUFRLEVBQUU7d0JBQzVELEdBQUcsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztxQkFDekM7eUJBQU0sSUFBSSxPQUFPLFVBQVUsS0FBSyxRQUFRLEVBQUU7d0JBQ3ZDLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUM7cUJBQzVCO3lCQUFNO3dCQUNILE1BQU0sSUFBSSxXQUFXLENBQUMsdUVBQXVFLENBQUMsQ0FBQztxQkFDbEc7Z0JBQ0wsQ0FBQyxDQUFDO2dCQUVGLElBQUksRUFBRSxDQUFDO1lBQ1gsQ0FBQyxDQUFDLENBQUM7UUFDUCxDQUFDO0tBQUE7SUFFWSxJQUFJOztRQUNqQixDQUFDO0tBQUE7Q0FFSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBcHBsaWNhdGlvbkNvbXBvbmVudCBmcm9tIFwiLi4vQXBwbGljYXRpb25Db21wb25lbnRcIjtcbmltcG9ydCB7RXhwcmVzcywgUm91dGVyfSBmcm9tIFwiZXhwcmVzc1wiO1xuaW1wb3J0IG9uRmluaXNoZWQgZnJvbSBcIm9uLWZpbmlzaGVkXCI7XG5pbXBvcnQgTG9nZ2VyIGZyb20gXCIuLi9Mb2dnZXJcIjtcbmltcG9ydCB7U2VydmVyRXJyb3J9IGZyb20gXCIuLi9IdHRwRXJyb3JcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgUmVkaXJlY3RCYWNrQ29tcG9uZW50IGV4dGVuZHMgQXBwbGljYXRpb25Db21wb25lbnQ8dm9pZD4ge1xuICAgIHB1YmxpYyBhc3luYyBzdGFydChhcHA6IEV4cHJlc3MsIHJvdXRlcjogUm91dGVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHJvdXRlci51c2UoKHJlcSwgcmVzLCBuZXh0KSA9PiB7XG4gICAgICAgICAgICBpZiAoIXJlcS5zZXNzaW9uKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTZXNzaW9uIGlzIHVuYXZhaWxhYmxlLicpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBvbkZpbmlzaGVkKHJlcywgKGVycikgPT4ge1xuICAgICAgICAgICAgICAgIGlmICghZXJyICYmIHJlcy5zdGF0dXNDb2RlID09PSAyMDApIHtcbiAgICAgICAgICAgICAgICAgICAgcmVxLnNlc3Npb24hLnByZXZpb3VzVXJsID0gcmVxLm9yaWdpbmFsVXJsO1xuICAgICAgICAgICAgICAgICAgICBMb2dnZXIuZGVidWcoJ1ByZXYgdXJsIHNldCB0bycsIHJlcS5zZXNzaW9uIS5wcmV2aW91c1VybCk7XG4gICAgICAgICAgICAgICAgICAgIHJlcS5zZXNzaW9uIS5zYXZlKChlcnIpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChlcnIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBMb2dnZXIuZXJyb3IoZXJyLCAnRXJyb3Igd2hpbGUgc2F2aW5nIHNlc3Npb24nKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIHJlcy5yZWRpcmVjdEJhY2sgPSAoZGVmYXVsdFVybD86IHN0cmluZykgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChyZXEuc2Vzc2lvbiAmJiB0eXBlb2YgcmVxLnNlc3Npb24ucHJldmlvdXNVcmwgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlcy5yZWRpcmVjdChyZXEuc2Vzc2lvbi5wcmV2aW91c1VybCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIGlmICh0eXBlb2YgZGVmYXVsdFVybCA9PT0gJ3N0cmluZycpIHtcbiAgICAgICAgICAgICAgICAgICAgcmVzLnJlZGlyZWN0KGRlZmF1bHRVcmwpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBTZXJ2ZXJFcnJvcignVGhlcmUgaXMgbm8gcHJldmlvdXMgdXJsIGFuZCBubyBkZWZhdWx0IHJlZGlyZWN0aW9uIHVybCB3YXMgcHJvdmlkZWQuJyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgbmV4dCgpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB9XG5cbn0iXX0=
|
11
dist/components/RedisComponent.d.ts
vendored
Normal file
11
dist/components/RedisComponent.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
import { Store } from "express-session";
|
||||||
|
export default class RedisComponent extends ApplicationComponent<void> {
|
||||||
|
private redisClient?;
|
||||||
|
private store?;
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
getStore(): Store;
|
||||||
|
canServe(): boolean;
|
||||||
|
}
|
46
dist/components/RedisComponent.js
vendored
Normal file
46
dist/components/RedisComponent.js
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import redis from "redis";
|
||||||
|
import config from "config";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import session from "express-session";
|
||||||
|
import connect_redis from "connect-redis";
|
||||||
|
const RedisStore = connect_redis(session);
|
||||||
|
export default class RedisComponent extends ApplicationComponent {
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), {});
|
||||||
|
this.redisClient.on('error', (err) => {
|
||||||
|
Logger.error(err, 'An error occurred with redis.');
|
||||||
|
});
|
||||||
|
this.store = new RedisStore({
|
||||||
|
client: this.redisClient,
|
||||||
|
prefix: 'wms-sess:',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.redisClient) {
|
||||||
|
yield this.close('Redis connection', this.redisClient, this.redisClient.quit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getStore() {
|
||||||
|
if (!this.store)
|
||||||
|
throw `Redis store was not initialized.`;
|
||||||
|
return this.store;
|
||||||
|
}
|
||||||
|
canServe() {
|
||||||
|
return this.redisClient !== undefined && this.redisClient.connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVkaXNDb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvUmVkaXNDb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRCxPQUFPLEtBQW9CLE1BQU0sT0FBTyxDQUFDO0FBQ3pDLE9BQU8sTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUM1QixPQUFPLE1BQU0sTUFBTSxXQUFXLENBQUM7QUFDL0IsT0FBTyxPQUFnQixNQUFNLGlCQUFpQixDQUFDO0FBQy9DLE9BQU8sYUFBYSxNQUFNLGVBQWUsQ0FBQztBQUUxQyxNQUFNLFVBQVUsR0FBRyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUM7QUFFMUMsTUFBTSxDQUFDLE9BQU8sT0FBTyxjQUFlLFNBQVEsb0JBQTBCO0lBSXJELEtBQUssQ0FBQyxHQUFZLEVBQUUsTUFBYzs7WUFDM0MsSUFBSSxDQUFDLFdBQVcsR0FBRyxLQUFLLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztZQUM5RixJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFRLEVBQUUsRUFBRTtnQkFDdEMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsK0JBQStCLENBQUMsQ0FBQztZQUN2RCxDQUFDLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxVQUFVLENBQUM7Z0JBQ3hCLE1BQU0sRUFBRSxJQUFJLENBQUMsV0FBVztnQkFDeEIsTUFBTSxFQUFFLFdBQVc7YUFDdEIsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztLQUFBO0lBRVksSUFBSTs7WUFDYixJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUU7Z0JBQ2xCLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7YUFDakY7UUFDTCxDQUFDO0tBQUE7SUFFTSxRQUFRO1FBQ1gsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLO1lBQUUsTUFBTSxrQ0FBa0MsQ0FBQztRQUMxRCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDdEIsQ0FBQztJQUVNLFFBQVE7UUFDWCxPQUFPLElBQUksQ0FBQyxXQUFXLEtBQUssU0FBUyxJQUFJLElBQUksQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDO0lBQ3hFLENBQUM7Q0FDSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBcHBsaWNhdGlvbkNvbXBvbmVudCBmcm9tIFwiLi4vQXBwbGljYXRpb25Db21wb25lbnRcIjtcbmltcG9ydCB7RXhwcmVzcywgUm91dGVyfSBmcm9tIFwiZXhwcmVzc1wiO1xuaW1wb3J0IHJlZGlzLCB7UmVkaXNDbGllbnR9IGZyb20gXCJyZWRpc1wiO1xuaW1wb3J0IGNvbmZpZyBmcm9tIFwiY29uZmlnXCI7XG5pbXBvcnQgTG9nZ2VyIGZyb20gXCIuLi9Mb2dnZXJcIjtcbmltcG9ydCBzZXNzaW9uLCB7U3RvcmV9IGZyb20gXCJleHByZXNzLXNlc3Npb25cIjtcbmltcG9ydCBjb25uZWN0X3JlZGlzIGZyb20gXCJjb25uZWN0LXJlZGlzXCI7XG5cbmNvbnN0IFJlZGlzU3RvcmUgPSBjb25uZWN0X3JlZGlzKHNlc3Npb24pO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBSZWRpc0NvbXBvbmVudCBleHRlbmRzIEFwcGxpY2F0aW9uQ29tcG9uZW50PHZvaWQ+IHtcbiAgICBwcml2YXRlIHJlZGlzQ2xpZW50PzogUmVkaXNDbGllbnQ7XG4gICAgcHJpdmF0ZSBzdG9yZT86IFN0b3JlO1xuXG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgdGhpcy5yZWRpc0NsaWVudCA9IHJlZGlzLmNyZWF0ZUNsaWVudChjb25maWcuZ2V0KCdyZWRpcy5wb3J0JyksIGNvbmZpZy5nZXQoJ3JlZGlzLmhvc3QnKSwge30pO1xuICAgICAgICB0aGlzLnJlZGlzQ2xpZW50Lm9uKCdlcnJvcicsIChlcnI6IGFueSkgPT4ge1xuICAgICAgICAgICAgTG9nZ2VyLmVycm9yKGVyciwgJ0FuIGVycm9yIG9jY3VycmVkIHdpdGggcmVkaXMuJyk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLnN0b3JlID0gbmV3IFJlZGlzU3RvcmUoe1xuICAgICAgICAgICAgY2xpZW50OiB0aGlzLnJlZGlzQ2xpZW50LFxuICAgICAgICAgICAgcHJlZml4OiAnd21zLXNlc3M6JyxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICh0aGlzLnJlZGlzQ2xpZW50KSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmNsb3NlKCdSZWRpcyBjb25uZWN0aW9uJywgdGhpcy5yZWRpc0NsaWVudCwgdGhpcy5yZWRpc0NsaWVudC5xdWl0KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHB1YmxpYyBnZXRTdG9yZSgpOiBTdG9yZSB7XG4gICAgICAgIGlmICghdGhpcy5zdG9yZSkgdGhyb3cgYFJlZGlzIHN0b3JlIHdhcyBub3QgaW5pdGlhbGl6ZWQuYDtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3RvcmU7XG4gICAgfVxuXG4gICAgcHVibGljIGNhblNlcnZlKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5yZWRpc0NsaWVudCAhPT0gdW5kZWZpbmVkICYmIHRoaXMucmVkaXNDbGllbnQuY29ubmVjdGVkO1xuICAgIH1cbn0iXX0=
|
10
dist/components/ServeStaticDirectoryComponent.d.ts
vendored
Normal file
10
dist/components/ServeStaticDirectoryComponent.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
import { PathParams } from "express-serve-static-core";
|
||||||
|
export default class ServeStaticDirectoryComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly root;
|
||||||
|
private readonly path?;
|
||||||
|
constructor(root: string, routePath?: PathParams);
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
33
dist/components/ServeStaticDirectoryComponent.js
vendored
Normal file
33
dist/components/ServeStaticDirectoryComponent.js
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import express from "express";
|
||||||
|
export default class ServeStaticDirectoryComponent extends ApplicationComponent {
|
||||||
|
constructor(root, routePath) {
|
||||||
|
super();
|
||||||
|
this.root = root;
|
||||||
|
this.path = routePath;
|
||||||
|
}
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (typeof this.path !== 'undefined') {
|
||||||
|
router.use(this.path, express.static(this.root, { maxAge: 1000 * 3600 * 72 }));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
router.use(express.static(this.root, { maxAge: 1000 * 3600 * 72 }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VydmVTdGF0aWNEaXJlY3RvcnlDb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImNvbXBvbmVudHMvU2VydmVTdGF0aWNEaXJlY3RvcnlDb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxvQkFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUMzRCxPQUFPLE9BQTBCLE1BQU0sU0FBUyxDQUFDO0FBR2pELE1BQU0sQ0FBQyxPQUFPLE9BQU8sNkJBQThCLFNBQVEsb0JBQTBCO0lBSWpGLFlBQVksSUFBWSxFQUFFLFNBQXNCO1FBQzVDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLElBQUksR0FBRyxTQUFTLENBQUM7SUFDMUIsQ0FBQztJQUVZLEtBQUssQ0FBQyxHQUFZLEVBQUUsTUFBYzs7WUFDM0MsSUFBSSxPQUFPLElBQUksQ0FBQyxJQUFJLEtBQUssV0FBVyxFQUFFO2dCQUNsQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLEVBQUMsTUFBTSxFQUFFLElBQUksR0FBRyxJQUFJLEdBQUcsRUFBRSxFQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ2hGO2lCQUFNO2dCQUNILE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLEVBQUMsTUFBTSxFQUFFLElBQUksR0FBRyxJQUFJLEdBQUcsRUFBRSxFQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ3JFO1FBQ0wsQ0FBQztLQUFBO0lBRVksSUFBSTs7UUFDakIsQ0FBQztLQUFBO0NBRUoiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQXBwbGljYXRpb25Db21wb25lbnQgZnJvbSBcIi4uL0FwcGxpY2F0aW9uQ29tcG9uZW50XCI7XG5pbXBvcnQgZXhwcmVzcywge0V4cHJlc3MsIFJvdXRlcn0gZnJvbSBcImV4cHJlc3NcIjtcbmltcG9ydCB7UGF0aFBhcmFtc30gZnJvbSBcImV4cHJlc3Mtc2VydmUtc3RhdGljLWNvcmVcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU2VydmVTdGF0aWNEaXJlY3RvcnlDb21wb25lbnQgZXh0ZW5kcyBBcHBsaWNhdGlvbkNvbXBvbmVudDx2b2lkPiB7XG4gICAgcHJpdmF0ZSByZWFkb25seSByb290OiBzdHJpbmc7XG4gICAgcHJpdmF0ZSByZWFkb25seSBwYXRoPzogUGF0aFBhcmFtcztcblxuICAgIGNvbnN0cnVjdG9yKHJvb3Q6IHN0cmluZywgcm91dGVQYXRoPzogUGF0aFBhcmFtcykge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLnJvb3QgPSByb290O1xuICAgICAgICB0aGlzLnBhdGggPSByb3V0ZVBhdGg7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KGFwcDogRXhwcmVzcywgcm91dGVyOiBSb3V0ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKHR5cGVvZiB0aGlzLnBhdGggIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgICAgICByb3V0ZXIudXNlKHRoaXMucGF0aCwgZXhwcmVzcy5zdGF0aWModGhpcy5yb290LCB7bWF4QWdlOiAxMDAwICogMzYwMCAqIDcyfSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcm91dGVyLnVzZShleHByZXNzLnN0YXRpYyh0aGlzLnJvb3QsIHttYXhBZ2U6IDEwMDAgKiAzNjAwICogNzJ9KSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB9XG5cbn0iXX0=
|
9
dist/components/SessionComponent.d.ts
vendored
Normal file
9
dist/components/SessionComponent.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import RedisComponent from "./RedisComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
export default class SessionComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly storeComponent;
|
||||||
|
constructor(storeComponent: RedisComponent);
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
59
dist/components/SessionComponent.js
vendored
Normal file
59
dist/components/SessionComponent.js
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import session from "express-session";
|
||||||
|
import config from "config";
|
||||||
|
import flash from "connect-flash";
|
||||||
|
export default class SessionComponent extends ApplicationComponent {
|
||||||
|
constructor(storeComponent) {
|
||||||
|
super();
|
||||||
|
this.storeComponent = storeComponent;
|
||||||
|
}
|
||||||
|
start(app, router) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
router.use(session({
|
||||||
|
saveUninitialized: true,
|
||||||
|
secret: config.get('session.secret'),
|
||||||
|
store: this.storeComponent.getStore(),
|
||||||
|
resave: true,
|
||||||
|
cookie: {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: config.get('session.cookie.secure'),
|
||||||
|
},
|
||||||
|
rolling: true,
|
||||||
|
}));
|
||||||
|
router.use(flash());
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
res.locals.session = req.session;
|
||||||
|
let _flash = null;
|
||||||
|
res.locals.flash = () => {
|
||||||
|
if (!_flash) {
|
||||||
|
_flash = {
|
||||||
|
info: req.flash('info'),
|
||||||
|
success: req.flash('success'),
|
||||||
|
warning: req.flash('warning'),
|
||||||
|
error: req.flash('error'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return _flash;
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2Vzc2lvbkNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIuLyIsInNvdXJjZXMiOlsiY29tcG9uZW50cy9TZXNzaW9uQ29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sb0JBQW9CLE1BQU0seUJBQXlCLENBQUM7QUFDM0QsT0FBTyxPQUFPLE1BQU0saUJBQWlCLENBQUM7QUFDdEMsT0FBTyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRTVCLE9BQU8sS0FBSyxNQUFNLGVBQWUsQ0FBQztBQUdsQyxNQUFNLENBQUMsT0FBTyxPQUFPLGdCQUFpQixTQUFRLG9CQUEwQjtJQUlwRSxZQUFtQixjQUE4QjtRQUM3QyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxjQUFjLEdBQUcsY0FBYyxDQUFDO0lBQ3pDLENBQUM7SUFFWSxLQUFLLENBQUMsR0FBWSxFQUFFLE1BQWM7O1lBQzNDLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDO2dCQUNmLGlCQUFpQixFQUFFLElBQUk7Z0JBQ3ZCLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLGdCQUFnQixDQUFDO2dCQUNwQyxLQUFLLEVBQUUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLEVBQUU7Z0JBQ3JDLE1BQU0sRUFBRSxJQUFJO2dCQUNaLE1BQU0sRUFBRTtvQkFDSixRQUFRLEVBQUUsSUFBSTtvQkFDZCxNQUFNLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsQ0FBQztpQkFDOUM7Z0JBQ0QsT0FBTyxFQUFFLElBQUk7YUFDaEIsQ0FBQyxDQUFDLENBQUM7WUFFSixNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUM7WUFFcEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7Z0JBQzFCLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFO29CQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztpQkFDOUM7Z0JBRUQsR0FBRyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQztnQkFFakMsSUFBSSxNQUFNLEdBQVEsSUFBSSxDQUFDO2dCQUN2QixHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssR0FBRyxHQUFHLEVBQUU7b0JBQ3BCLElBQUksQ0FBQyxNQUFNLEVBQUU7d0JBQ1QsTUFBTSxHQUFHOzRCQUNMLElBQUksRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQzs0QkFDdkIsT0FBTyxFQUFFLEdBQUcsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDOzRCQUM3QixPQUFPLEVBQUUsR0FBRyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUM7NEJBQzdCLEtBQUssRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQzt5QkFDNUIsQ0FBQztxQkFDTDtvQkFDRCxPQUFPLE1BQU0sQ0FBQztnQkFDbEIsQ0FBQyxDQUFDO2dCQUNGLElBQUksRUFBRSxDQUFDO1lBQ1gsQ0FBQyxDQUFDLENBQUM7UUFDUCxDQUFDO0tBQUE7SUFFWSxJQUFJOztRQUNqQixDQUFDO0tBQUE7Q0FDSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBBcHBsaWNhdGlvbkNvbXBvbmVudCBmcm9tIFwiLi4vQXBwbGljYXRpb25Db21wb25lbnRcIjtcbmltcG9ydCBzZXNzaW9uIGZyb20gXCJleHByZXNzLXNlc3Npb25cIjtcbmltcG9ydCBjb25maWcgZnJvbSBcImNvbmZpZ1wiO1xuaW1wb3J0IFJlZGlzQ29tcG9uZW50IGZyb20gXCIuL1JlZGlzQ29tcG9uZW50XCI7XG5pbXBvcnQgZmxhc2ggZnJvbSBcImNvbm5lY3QtZmxhc2hcIjtcbmltcG9ydCB7RXhwcmVzcywgUm91dGVyfSBmcm9tIFwiZXhwcmVzc1wiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTZXNzaW9uQ29tcG9uZW50IGV4dGVuZHMgQXBwbGljYXRpb25Db21wb25lbnQ8dm9pZD4ge1xuICAgIHByaXZhdGUgcmVhZG9ubHkgc3RvcmVDb21wb25lbnQ6IFJlZGlzQ29tcG9uZW50O1xuXG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3Ioc3RvcmVDb21wb25lbnQ6IFJlZGlzQ29tcG9uZW50KSB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgICAgIHRoaXMuc3RvcmVDb21wb25lbnQgPSBzdG9yZUNvbXBvbmVudDtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RhcnQoYXBwOiBFeHByZXNzLCByb3V0ZXI6IFJvdXRlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByb3V0ZXIudXNlKHNlc3Npb24oe1xuICAgICAgICAgICAgc2F2ZVVuaW5pdGlhbGl6ZWQ6IHRydWUsXG4gICAgICAgICAgICBzZWNyZXQ6IGNvbmZpZy5nZXQoJ3Nlc3Npb24uc2VjcmV0JyksXG4gICAgICAgICAgICBzdG9yZTogdGhpcy5zdG9yZUNvbXBvbmVudC5nZXRTdG9yZSgpLFxuICAgICAgICAgICAgcmVzYXZlOiB0cnVlLFxuICAgICAgICAgICAgY29va2llOiB7XG4gICAgICAgICAgICAgICAgaHR0cE9ubHk6IHRydWUsXG4gICAgICAgICAgICAgICAgc2VjdXJlOiBjb25maWcuZ2V0KCdzZXNzaW9uLmNvb2tpZS5zZWN1cmUnKSxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICByb2xsaW5nOiB0cnVlLFxuICAgICAgICB9KSk7XG5cbiAgICAgICAgcm91dGVyLnVzZShmbGFzaCgpKTtcblxuICAgICAgICByb3V0ZXIudXNlKChyZXEsIHJlcywgbmV4dCkgPT4ge1xuICAgICAgICAgICAgaWYgKCFyZXEuc2Vzc2lvbikge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignU2Vzc2lvbiBpcyB1bmF2YWlsYWJsZS4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzLmxvY2Fscy5zZXNzaW9uID0gcmVxLnNlc3Npb247XG5cbiAgICAgICAgICAgIGxldCBfZmxhc2g6IGFueSA9IG51bGw7XG4gICAgICAgICAgICByZXMubG9jYWxzLmZsYXNoID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGlmICghX2ZsYXNoKSB7XG4gICAgICAgICAgICAgICAgICAgIF9mbGFzaCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGluZm86IHJlcS5mbGFzaCgnaW5mbycpLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3VjY2VzczogcmVxLmZsYXNoKCdzdWNjZXNzJyksXG4gICAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nOiByZXEuZmxhc2goJ3dhcm5pbmcnKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGVycm9yOiByZXEuZmxhc2goJ2Vycm9yJyksXG4gICAgICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiBfZmxhc2g7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgbmV4dCgpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB9XG59Il19
|
14
dist/components/WebSocketServerComponent.d.ts
vendored
Normal file
14
dist/components/WebSocketServerComponent.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import { Express, Router } from "express";
|
||||||
|
import ExpressAppComponent from "./ExpressAppComponent";
|
||||||
|
import Application from "../Application";
|
||||||
|
import RedisComponent from "./RedisComponent";
|
||||||
|
export default class WebSocketServerComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly application;
|
||||||
|
private readonly expressAppComponent;
|
||||||
|
private readonly storeComponent;
|
||||||
|
private wss?;
|
||||||
|
constructor(application: Application, expressAppComponent: ExpressAppComponent, storeComponent: RedisComponent);
|
||||||
|
start(app: Express, router: Router): Promise<void>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
73
dist/components/WebSocketServerComponent.js
vendored
Normal file
73
dist/components/WebSocketServerComponent.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
dist/db/Migration.d.ts
vendored
Normal file
7
dist/db/Migration.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default abstract class Migration {
|
||||||
|
readonly version: number;
|
||||||
|
constructor(version: number);
|
||||||
|
shouldRun(currentVersion: number): Promise<boolean>;
|
||||||
|
abstract install(): Promise<void>;
|
||||||
|
abstract rollback(): Promise<void>;
|
||||||
|
}
|
20
dist/db/Migration.js
vendored
Normal file
20
dist/db/Migration.js
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default class Migration {
|
||||||
|
constructor(version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
shouldRun(currentVersion) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return this.version > currentVersion;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uLmpzIiwic291cmNlUm9vdCI6Ii4vIiwic291cmNlcyI6WyJkYi9NaWdyYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsTUFBTSxDQUFDLE9BQU8sT0FBZ0IsU0FBUztJQUduQyxZQUFZLE9BQWU7UUFDdkIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDM0IsQ0FBQztJQUVLLFNBQVMsQ0FBQyxjQUFzQjs7WUFDbEMsT0FBTyxJQUFJLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQztRQUN6QyxDQUFDO0tBQUE7Q0FLSiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGFic3RyYWN0IGNsYXNzIE1pZ3JhdGlvbiB7XG4gICAgcHVibGljIHJlYWRvbmx5IHZlcnNpb246IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKHZlcnNpb246IG51bWJlcikge1xuICAgICAgICB0aGlzLnZlcnNpb24gPSB2ZXJzaW9uO1xuICAgIH1cblxuICAgIGFzeW5jIHNob3VsZFJ1bihjdXJyZW50VmVyc2lvbjogbnVtYmVyKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzLnZlcnNpb24gPiBjdXJyZW50VmVyc2lvbjtcbiAgICB9XG5cbiAgICBhYnN0cmFjdCBhc3luYyBpbnN0YWxsKCk6IFByb21pc2U8dm9pZD47XG5cbiAgICBhYnN0cmFjdCBhc3luYyByb2xsYmFjaygpOiBQcm9taXNlPHZvaWQ+O1xufSJdfQ==
|
58
dist/db/Model.d.ts
vendored
Normal file
58
dist/db/Model.d.ts
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import Validator from "./Validator";
|
||||||
|
import { Connection } from "mysql";
|
||||||
|
import Query from "./Query";
|
||||||
|
import { Request } from "express";
|
||||||
|
export default abstract class Model {
|
||||||
|
static getById<T extends Model>(id: number): Promise<T | null>;
|
||||||
|
static paginate<T extends Model>(request: Request, perPage?: number): Promise<T[]>;
|
||||||
|
protected static select(...fields: string[]): Query;
|
||||||
|
protected static update(data: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): Query;
|
||||||
|
protected static delete(): Query;
|
||||||
|
protected static models<T extends Model>(query: Query): Promise<T[]>;
|
||||||
|
static loadRelation<T extends Model>(models: T[], relation: string, model: Function, localField: string): Promise<void>;
|
||||||
|
private static getFactory;
|
||||||
|
protected readonly properties: ModelProperty<any>[];
|
||||||
|
private readonly relations;
|
||||||
|
id?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
constructor(data: any);
|
||||||
|
protected abstract defineProperties(): void;
|
||||||
|
protected defineProperty<T>(name: string, validator?: Validator<T> | RegExp): void;
|
||||||
|
private updateWithData;
|
||||||
|
protected beforeSave(exists: boolean, connection: Connection): Promise<void>;
|
||||||
|
protected afterSave(): Promise<void>;
|
||||||
|
save(connection?: Connection, postHook?: (callback: () => Promise<void>) => void): Promise<void>;
|
||||||
|
private saveTransaction;
|
||||||
|
static get table(): string;
|
||||||
|
get table(): string;
|
||||||
|
exists(): Promise<boolean>;
|
||||||
|
delete(): Promise<void>;
|
||||||
|
validate(onlyFormat?: boolean, connection?: Connection): Promise<void[]>;
|
||||||
|
private cache;
|
||||||
|
protected relation<T extends Model>(name: string): T | null;
|
||||||
|
}
|
||||||
|
export interface ModelFactory<T extends Model> {
|
||||||
|
(data: any): T;
|
||||||
|
}
|
||||||
|
declare class ModelProperty<T> {
|
||||||
|
readonly name: string;
|
||||||
|
private readonly validator;
|
||||||
|
private val?;
|
||||||
|
constructor(name: string, validator: Validator<T>);
|
||||||
|
validate(onlyFormat: boolean, connection?: Connection): Promise<void>;
|
||||||
|
get value(): T | undefined;
|
||||||
|
set value(val: T | undefined);
|
||||||
|
}
|
||||||
|
export declare class ModelCache {
|
||||||
|
private static readonly caches;
|
||||||
|
static cache(instance: Model): void;
|
||||||
|
static forget(instance: Model): void;
|
||||||
|
static all(table: string): {
|
||||||
|
[key: number]: Model;
|
||||||
|
} | undefined;
|
||||||
|
static get(table: string, id: number): Model | undefined;
|
||||||
|
}
|
||||||
|
export declare const EMAIL_REGEX: RegExp;
|
||||||
|
export {};
|
291
dist/db/Model.js
vendored
Normal file
291
dist/db/Model.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
dist/db/MysqlConnectionManager.d.ts
vendored
Normal file
23
dist/db/MysqlConnectionManager.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Connection, FieldInfo, Pool } from 'mysql';
|
||||||
|
import Migration from "./Migration";
|
||||||
|
export interface QueryResult {
|
||||||
|
readonly results: any[];
|
||||||
|
readonly fields: FieldInfo[];
|
||||||
|
readonly other?: any;
|
||||||
|
foundRows?: number;
|
||||||
|
}
|
||||||
|
export declare function query(queryString: string, values?: any, connection?: Connection): Promise<QueryResult>;
|
||||||
|
export default class MysqlConnectionManager {
|
||||||
|
private static currentPool?;
|
||||||
|
private static databaseReady;
|
||||||
|
private static readonly migrations;
|
||||||
|
static registerMigration(migration: (version: number) => Migration): void;
|
||||||
|
static prepare(): Promise<void>;
|
||||||
|
static get pool(): Pool;
|
||||||
|
private static createPool;
|
||||||
|
static endPool(): Promise<void>;
|
||||||
|
static query(queryString: string, values?: any, connection?: Connection): Promise<QueryResult>;
|
||||||
|
static wrapTransaction<T>(transaction: (connection: Connection) => Promise<T>): Promise<T>;
|
||||||
|
private static rejectAndRollback;
|
||||||
|
private static handleMigrations;
|
||||||
|
}
|
168
dist/db/MysqlConnectionManager.js
vendored
Normal file
168
dist/db/MysqlConnectionManager.js
vendored
Normal file
File diff suppressed because one or more lines are too long
47
dist/db/Query.d.ts
vendored
Normal file
47
dist/db/Query.d.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { QueryResult } from "./MysqlConnectionManager";
|
||||||
|
import { Connection } from "mysql";
|
||||||
|
export default class Query {
|
||||||
|
static select(table: string, ...fields: string[]): Query;
|
||||||
|
static update(table: string, data: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): Query;
|
||||||
|
static delete(table: string): Query;
|
||||||
|
private readonly type;
|
||||||
|
private readonly table;
|
||||||
|
private readonly fields;
|
||||||
|
private _where;
|
||||||
|
private _limit?;
|
||||||
|
private _offset?;
|
||||||
|
private _sortBy?;
|
||||||
|
private _sortDirection?;
|
||||||
|
private _foundRows;
|
||||||
|
private constructor();
|
||||||
|
where(field: string, value: string | Date | Query | any, operator?: WhereOperator, test?: WhereTest): Query;
|
||||||
|
whereNot(field: string, value: string | Date | Query | any, operator?: WhereOperator): Query;
|
||||||
|
orWhere(field: string, value: string | Date | Query | any): Query;
|
||||||
|
whereIn(field: string, value: any[]): Query;
|
||||||
|
limit(limit: number, offset?: number): Query;
|
||||||
|
first(): Query;
|
||||||
|
sortBy(field: string, direction?: 'ASC' | 'DESC'): Query;
|
||||||
|
withTotalRowCount(): Query;
|
||||||
|
toString(final?: boolean): string;
|
||||||
|
build(): string;
|
||||||
|
get variables(): any[];
|
||||||
|
isCacheable(): boolean;
|
||||||
|
execute(connection?: Connection): Promise<QueryResult>;
|
||||||
|
}
|
||||||
|
export declare enum QueryType {
|
||||||
|
SELECT = 0,
|
||||||
|
UPDATE = 1,
|
||||||
|
DELETE = 2
|
||||||
|
}
|
||||||
|
declare enum WhereOperator {
|
||||||
|
AND = "AND",
|
||||||
|
OR = "OR"
|
||||||
|
}
|
||||||
|
declare enum WhereTest {
|
||||||
|
EQUALS = "=",
|
||||||
|
DIFFERENT = "!=",
|
||||||
|
IN = " IN "
|
||||||
|
}
|
||||||
|
export {};
|
176
dist/db/Query.js
vendored
Normal file
176
dist/db/Query.js
vendored
Normal file
File diff suppressed because one or more lines are too long
86
dist/db/Validator.d.ts
vendored
Normal file
86
dist/db/Validator.d.ts
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
import Query from "./Query";
|
||||||
|
import { Connection } from "mysql";
|
||||||
|
export default class Validator<T> {
|
||||||
|
private readonly steps;
|
||||||
|
private readonly validationAttributes;
|
||||||
|
private _min?;
|
||||||
|
private _max?;
|
||||||
|
/**
|
||||||
|
* @param thingName The name of the thing to validate.
|
||||||
|
* @param value The value to verify.
|
||||||
|
* @param onlyFormat {@code true} to only validate format properties, {@code false} otherwise.
|
||||||
|
* @param connection A connection to use in case of wrapped transactions.
|
||||||
|
*/
|
||||||
|
execute(thingName: string, value: T | undefined, onlyFormat: boolean, connection?: Connection): Promise<void>;
|
||||||
|
defined(): Validator<T>;
|
||||||
|
acceptUndefined(): Validator<T>;
|
||||||
|
equals(other?: T): Validator<T>;
|
||||||
|
regexp(regexp: RegExp): Validator<T>;
|
||||||
|
length(length: number): Validator<T>;
|
||||||
|
/**
|
||||||
|
* @param minLength included
|
||||||
|
* @param maxLength included
|
||||||
|
*/
|
||||||
|
between(minLength: number, maxLength: number): Validator<T>;
|
||||||
|
/**
|
||||||
|
* @param min included
|
||||||
|
*/
|
||||||
|
min(min: number): Validator<T>;
|
||||||
|
/**
|
||||||
|
* @param max included
|
||||||
|
*/
|
||||||
|
max(max: number): Validator<T>;
|
||||||
|
unique(model: Model, querySupplier?: () => Query): Validator<T>;
|
||||||
|
exists(modelClass: Function, foreignKey?: string): Validator<T>;
|
||||||
|
private addStep;
|
||||||
|
getValidationAttributes(): string[];
|
||||||
|
step(step: number): Validator<T>;
|
||||||
|
}
|
||||||
|
export declare class ValidationBag extends Error {
|
||||||
|
private readonly messages;
|
||||||
|
addMessage(err: ValidationError): void;
|
||||||
|
hasMessages(): boolean;
|
||||||
|
getMessages(): {
|
||||||
|
[p: string]: ValidationError;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export declare abstract class ValidationError extends Error {
|
||||||
|
thingName?: string;
|
||||||
|
value?: any;
|
||||||
|
get name(): string;
|
||||||
|
}
|
||||||
|
export declare class BadLengthValidationError extends ValidationError {
|
||||||
|
private readonly expectedLength;
|
||||||
|
private readonly maxLength?;
|
||||||
|
constructor(expectedLength: number, maxLength?: number);
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class BadValueValidationError extends ValidationError {
|
||||||
|
private readonly expectedValue;
|
||||||
|
constructor(expectedValue: any);
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class OutOfRangeValidationError extends ValidationError {
|
||||||
|
private readonly min?;
|
||||||
|
private readonly max?;
|
||||||
|
constructor(min?: number, max?: number);
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class InvalidFormatValidationError extends ValidationError {
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class UndefinedValueValidationError extends ValidationError {
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class AlreadyExistsValidationError extends ValidationError {
|
||||||
|
private readonly table;
|
||||||
|
constructor(table: string);
|
||||||
|
get message(): string;
|
||||||
|
}
|
||||||
|
export declare class UnknownRelationValidationError extends ValidationError {
|
||||||
|
private readonly table;
|
||||||
|
private readonly foreignKey?;
|
||||||
|
constructor(table: string, foreignKey?: string);
|
||||||
|
get message(): string;
|
||||||
|
}
|
261
dist/db/Validator.js
vendored
Normal file
261
dist/db/Validator.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.d.ts
vendored
Normal file
1
dist/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Application } from "./Application";
|
2
dist/index.js
vendored
Normal file
2
dist/index.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Application } from "./Application";
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiLi8iLCJzb3VyY2VzIjpbImluZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBQyxPQUFPLElBQUksV0FBVyxFQUFDLE1BQU0sZUFBZSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHtkZWZhdWx0IGFzIEFwcGxpY2F0aW9ufSBmcm9tIFwiLi9BcHBsaWNhdGlvblwiOyJdfQ==
|
8
dist/migrations/CreateLogsTable.d.ts
vendored
Normal file
8
dist/migrations/CreateLogsTable.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Migration from "../db/Migration";
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateLogsTable extends Migration {
|
||||||
|
install(): Promise<void>;
|
||||||
|
rollback(): Promise<void>;
|
||||||
|
}
|
37
dist/migrations/CreateLogsTable.js
vendored
Normal file
37
dist/migrations/CreateLogsTable.js
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import Migration from "../db/Migration";
|
||||||
|
import { query } from "../db/MysqlConnectionManager";
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateLogsTable extends Migration {
|
||||||
|
install() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield query('CREATE TABLE logs(' +
|
||||||
|
'id INT NOT NULL AUTO_INCREMENT,' +
|
||||||
|
'level TINYINT UNSIGNED NOT NULL,' +
|
||||||
|
'message TEXT NOT NULL,' +
|
||||||
|
'log_id BINARY(16),' +
|
||||||
|
'error_name VARCHAR(128),' +
|
||||||
|
'error_message VARCHAR(512),' +
|
||||||
|
'error_stack TEXT,' +
|
||||||
|
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
||||||
|
'PRIMARY KEY (id)' +
|
||||||
|
')');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rollback() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield query('DROP TABLE logs');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ3JlYXRlTG9nc1RhYmxlLmpzIiwic291cmNlUm9vdCI6Ii4vIiwic291cmNlcyI6WyJtaWdyYXRpb25zL0NyZWF0ZUxvZ3NUYWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQSxPQUFPLFNBQVMsTUFBTSxpQkFBaUIsQ0FBQztBQUN4QyxPQUFPLEVBQUMsS0FBSyxFQUFDLE1BQU0sOEJBQThCLENBQUM7QUFFbkQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLGVBQWdCLFNBQVEsU0FBUztJQUM1QyxPQUFPOztZQUNULE1BQU0sS0FBSyxDQUFDLG9CQUFvQjtnQkFDNUIsaUNBQWlDO2dCQUNqQyxrQ0FBa0M7Z0JBQ2xDLHdCQUF3QjtnQkFDeEIsb0JBQW9CO2dCQUNwQiwwQkFBMEI7Z0JBQzFCLDZCQUE2QjtnQkFDN0IsbUJBQW1CO2dCQUNuQiw2Q0FBNkM7Z0JBQzdDLGtCQUFrQjtnQkFDbEIsR0FBRyxDQUFDLENBQUM7UUFDYixDQUFDO0tBQUE7SUFFSyxRQUFROztZQUNWLE1BQU0sS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFDbkMsQ0FBQztLQUFBO0NBQ0oiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgTWlncmF0aW9uIGZyb20gXCIuLi9kYi9NaWdyYXRpb25cIjtcbmltcG9ydCB7cXVlcnl9IGZyb20gXCIuLi9kYi9NeXNxbENvbm5lY3Rpb25NYW5hZ2VyXCI7XG5cbi8qKlxuICogTXVzdCBiZSB0aGUgZmlyc3QgbWlncmF0aW9uXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIENyZWF0ZUxvZ3NUYWJsZSBleHRlbmRzIE1pZ3JhdGlvbiB7XG4gICAgYXN5bmMgaW5zdGFsbCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgcXVlcnkoJ0NSRUFURSBUQUJMRSBsb2dzKCcgK1xuICAgICAgICAgICAgJ2lkIElOVCBOT1QgTlVMTCBBVVRPX0lOQ1JFTUVOVCwnICtcbiAgICAgICAgICAgICdsZXZlbCBUSU5ZSU5UIFVOU0lHTkVEIE5PVCBOVUxMLCcgK1xuICAgICAgICAgICAgJ21lc3NhZ2UgVEVYVCBOT1QgTlVMTCwnICtcbiAgICAgICAgICAgICdsb2dfaWQgQklOQVJZKDE2KSwnICtcbiAgICAgICAgICAgICdlcnJvcl9uYW1lIFZBUkNIQVIoMTI4KSwnICtcbiAgICAgICAgICAgICdlcnJvcl9tZXNzYWdlIFZBUkNIQVIoNTEyKSwnICtcbiAgICAgICAgICAgICdlcnJvcl9zdGFjayBURVhULCcgK1xuICAgICAgICAgICAgJ2NyZWF0ZWRfYXQgREFURVRJTUUgTk9UIE5VTEwgREVGQVVMVCBOT1coKSwnICtcbiAgICAgICAgICAgICdQUklNQVJZIEtFWSAoaWQpJyArXG4gICAgICAgICAgICAnKScpO1xuICAgIH1cblxuICAgIGFzeW5jIHJvbGxiYWNrKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBhd2FpdCBxdWVyeSgnRFJPUCBUQUJMRSBsb2dzJyk7XG4gICAgfVxufSJdfQ==
|
9
dist/migrations/CreateMigrationsTable.d.ts
vendored
Normal file
9
dist/migrations/CreateMigrationsTable.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Migration from "../db/Migration";
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateMigrationsTable extends Migration {
|
||||||
|
shouldRun(currentVersion: number): Promise<boolean>;
|
||||||
|
install(): Promise<void>;
|
||||||
|
rollback(): Promise<void>;
|
||||||
|
}
|
48
dist/migrations/CreateMigrationsTable.js
vendored
Normal file
48
dist/migrations/CreateMigrationsTable.js
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import Migration from "../db/Migration";
|
||||||
|
import { query } from "../db/MysqlConnectionManager";
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateMigrationsTable extends Migration {
|
||||||
|
shouldRun(currentVersion) {
|
||||||
|
const _super = Object.create(null, {
|
||||||
|
shouldRun: { get: () => super.shouldRun }
|
||||||
|
});
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
yield query('SELECT 1 FROM migrations LIMIT 1');
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e.code !== 'ER_NO_SUCH_TABLE') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yield _super.shouldRun.call(this, currentVersion);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
install() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield query('CREATE TABLE migrations(' +
|
||||||
|
'id INT NOT NULL,' +
|
||||||
|
'name VARCHAR(64) NOT NULL,' +
|
||||||
|
'migration_date DATE,' +
|
||||||
|
'PRIMARY KEY (id)' +
|
||||||
|
')');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rollback() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield query('DROP TABLE migrations');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ3JlYXRlTWlncmF0aW9uc1RhYmxlLmpzIiwic291cmNlUm9vdCI6Ii4vIiwic291cmNlcyI6WyJtaWdyYXRpb25zL0NyZWF0ZU1pZ3JhdGlvbnNUYWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQSxPQUFPLFNBQVMsTUFBTSxpQkFBaUIsQ0FBQztBQUN4QyxPQUFPLEVBQUMsS0FBSyxFQUFDLE1BQU0sOEJBQThCLENBQUM7QUFFbkQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLHFCQUFzQixTQUFRLFNBQVM7SUFDbEQsU0FBUyxDQUFDLGNBQXNCOzs7OztZQUNsQyxJQUFJO2dCQUNBLE1BQU0sS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUM7YUFDbkQ7WUFBQyxPQUFPLENBQUMsRUFBRTtnQkFDUixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssa0JBQWtCLEVBQUU7b0JBQy9CLE9BQU8sS0FBSyxDQUFDO2lCQUNoQjthQUNKO1lBRUQsT0FBTyxNQUFNLE9BQU0sU0FBUyxZQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ2pELENBQUM7S0FBQTtJQUVLLE9BQU87O1lBQ1QsTUFBTSxLQUFLLENBQUMsMEJBQTBCO2dCQUNsQyxrQkFBa0I7Z0JBQ2xCLDRCQUE0QjtnQkFDNUIsc0JBQXNCO2dCQUN0QixrQkFBa0I7Z0JBQ2xCLEdBQUcsQ0FBQyxDQUFDO1FBQ2IsQ0FBQztLQUFBO0lBRUssUUFBUTs7WUFDVixNQUFNLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO1FBQ3pDLENBQUM7S0FBQTtDQUNKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IE1pZ3JhdGlvbiBmcm9tIFwiLi4vZGIvTWlncmF0aW9uXCI7XG5pbXBvcnQge3F1ZXJ5fSBmcm9tIFwiLi4vZGIvTXlzcWxDb25uZWN0aW9uTWFuYWdlclwiO1xuXG4vKipcbiAqIE11c3QgYmUgdGhlIGZpcnN0IG1pZ3JhdGlvblxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBDcmVhdGVNaWdyYXRpb25zVGFibGUgZXh0ZW5kcyBNaWdyYXRpb24ge1xuICAgIGFzeW5jIHNob3VsZFJ1bihjdXJyZW50VmVyc2lvbjogbnVtYmVyKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCBxdWVyeSgnU0VMRUNUIDEgRlJPTSBtaWdyYXRpb25zIExJTUlUIDEnKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgaWYgKGUuY29kZSAhPT0gJ0VSX05PX1NVQ0hfVEFCTEUnKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGF3YWl0IHN1cGVyLnNob3VsZFJ1bihjdXJyZW50VmVyc2lvbik7XG4gICAgfVxuXG4gICAgYXN5bmMgaW5zdGFsbCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgcXVlcnkoJ0NSRUFURSBUQUJMRSBtaWdyYXRpb25zKCcgK1xuICAgICAgICAgICAgJ2lkIElOVCBOT1QgTlVMTCwnICtcbiAgICAgICAgICAgICduYW1lIFZBUkNIQVIoNjQpIE5PVCBOVUxMLCcgK1xuICAgICAgICAgICAgJ21pZ3JhdGlvbl9kYXRlIERBVEUsJyArXG4gICAgICAgICAgICAnUFJJTUFSWSBLRVkgKGlkKScgK1xuICAgICAgICAgICAgJyknKTtcbiAgICB9XG5cbiAgICBhc3luYyByb2xsYmFjaygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgcXVlcnkoJ0RST1AgVEFCTEUgbWlncmF0aW9ucycpO1xuICAgIH1cbn0iXX0=
|
21
dist/models/Log.d.ts
vendored
Normal file
21
dist/models/Log.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import Model from "../db/Model";
|
||||||
|
import { LogLevelKeys } from "../Logger";
|
||||||
|
export default class Log extends Model {
|
||||||
|
private level?;
|
||||||
|
message?: string;
|
||||||
|
private log_id?;
|
||||||
|
private error_name?;
|
||||||
|
private error_message?;
|
||||||
|
private error_stack?;
|
||||||
|
private created_at?;
|
||||||
|
protected defineProperties(): void;
|
||||||
|
getLevel(): LogLevelKeys;
|
||||||
|
setLevel(level: LogLevelKeys): void;
|
||||||
|
getLogID(): string | null;
|
||||||
|
setLogID(buffer: Buffer): void;
|
||||||
|
getErrorName(): string;
|
||||||
|
getErrorMessage(): string;
|
||||||
|
getErrorStack(): string;
|
||||||
|
setError(error?: Error): void;
|
||||||
|
}
|
56
dist/models/Log.js
vendored
Normal file
56
dist/models/Log.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "wms-core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Node web framework",
|
||||||
|
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
||||||
|
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"prepublishOnly": "yarn build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^3.8.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.6",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"ws": "^7.2.3"
|
||||||
|
}
|
||||||
|
}
|
157
src/Application.ts
Normal file
157
src/Application.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import express, {NextFunction, Request, Response, Router} from 'express';
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
HttpError,
|
||||||
|
NotFoundHttpError,
|
||||||
|
ServerError,
|
||||||
|
ServiceUnavailableHttpError
|
||||||
|
} from "./HttpError";
|
||||||
|
import {lib} from "nunjucks";
|
||||||
|
import Logger from "./Logger";
|
||||||
|
import WebSocketListener from "./WebSocketListener";
|
||||||
|
import ApplicationComponent from "./ApplicationComponent";
|
||||||
|
import TemplateError = lib.TemplateError;
|
||||||
|
import Controller from "./Controller";
|
||||||
|
|
||||||
|
export default abstract class Application {
|
||||||
|
private readonly version: string;
|
||||||
|
private readonly controllers: Controller[] = [];
|
||||||
|
private readonly webSocketListeners: { [p: string]: WebSocketListener } = {};
|
||||||
|
private readonly components: ApplicationComponent<any>[] = [];
|
||||||
|
|
||||||
|
private ready: boolean = false;
|
||||||
|
|
||||||
|
protected constructor(version: string) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract async init(): Promise<void>;
|
||||||
|
|
||||||
|
protected use(thing: Controller | WebSocketListener | ApplicationComponent<any>) {
|
||||||
|
if (thing instanceof Controller) {
|
||||||
|
this.controllers.push(thing);
|
||||||
|
} else if (thing instanceof WebSocketListener) {
|
||||||
|
const path = thing.path();
|
||||||
|
this.webSocketListeners[path] = thing;
|
||||||
|
Logger.info(`Added websocket listener on ${path}`);
|
||||||
|
} else {
|
||||||
|
this.components.push(thing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
Logger.info(`${this.constructor.name} v${this.version} - hi`);
|
||||||
|
process.once('SIGINT', () => {
|
||||||
|
this.stop().catch(console.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register all components and alike
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
// Init express
|
||||||
|
const app = express();
|
||||||
|
const router = express.Router({});
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
// Error handler
|
||||||
|
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (res.headersSent) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorID: string;
|
||||||
|
|
||||||
|
let logStr = `${req.method} ${req.originalUrl} - `;
|
||||||
|
if (err instanceof BadRequestError || err instanceof ServiceUnavailableHttpError) {
|
||||||
|
logStr += `${err.errorCode} ${err.name}`;
|
||||||
|
errorID = Logger.silentError(err, logStr);
|
||||||
|
} else {
|
||||||
|
errorID = Logger.error(err, logStr + `500 Internal Error`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let httpError: HttpError;
|
||||||
|
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
httpError = err;
|
||||||
|
} else if (err instanceof TemplateError && err.cause instanceof HttpError) {
|
||||||
|
httpError = err.cause;
|
||||||
|
} else {
|
||||||
|
httpError = new ServerError('Internal server error.', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(httpError.errorCode);
|
||||||
|
res.format({
|
||||||
|
html: () => {
|
||||||
|
res.render('errors/' + httpError.errorCode + '.njk', {
|
||||||
|
error_code: httpError.errorCode,
|
||||||
|
error_message: httpError.message,
|
||||||
|
error_instructions: httpError.instructions,
|
||||||
|
error_id: errorID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
json: () => {
|
||||||
|
res.json({
|
||||||
|
status: 'error',
|
||||||
|
code: httpError.errorCode,
|
||||||
|
message: httpError.message,
|
||||||
|
instructions: httpError.instructions,
|
||||||
|
error_id: errorID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
default: () => {
|
||||||
|
res.type('txt').send(`${httpError.errorCode} - ${httpError.message}\n\n${httpError.instructions}\n\nError ID: ${errorID}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start all components
|
||||||
|
for (const component of this.components) {
|
||||||
|
await component.start(app, router);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
this.routes(router);
|
||||||
|
|
||||||
|
this.ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
Logger.info('Stopping application...');
|
||||||
|
|
||||||
|
for (const component of this.components) {
|
||||||
|
await component.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info(`${this.constructor.name} v${this.version} - bye`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private routes(rootRouter: Router) {
|
||||||
|
for (const controller of this.controllers) {
|
||||||
|
if (controller.hasGlobalHandlers()) {
|
||||||
|
controller.setupGlobalHandlers(rootRouter);
|
||||||
|
|
||||||
|
Logger.info(`Registered global middlewares for controller ${controller.constructor.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const controller of this.controllers) {
|
||||||
|
const router = express.Router();
|
||||||
|
controller.setupRoutes(router);
|
||||||
|
rootRouter.use(controller.getRoutesPrefix(), router);
|
||||||
|
|
||||||
|
Logger.info(`> Registered routes for controller ${controller.constructor.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootRouter.use((req: Request) => {
|
||||||
|
throw new NotFoundHttpError('page', req.originalUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWebSocketListeners(): { [p: string]: WebSocketListener } {
|
||||||
|
return this.webSocketListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isReady(): boolean {
|
||||||
|
return this.ready;
|
||||||
|
}
|
||||||
|
}
|
48
src/ApplicationComponent.ts
Normal file
48
src/ApplicationComponent.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import {Express, Router} from "express";
|
||||||
|
import Logger from "./Logger";
|
||||||
|
import {sleep} from "./Utils";
|
||||||
|
|
||||||
|
export default abstract class ApplicationComponent<T> {
|
||||||
|
private val?: T;
|
||||||
|
|
||||||
|
public abstract async start(app: Express, router: Router): Promise<void>;
|
||||||
|
|
||||||
|
public abstract async stop(): Promise<void>;
|
||||||
|
|
||||||
|
protected export(val: T) {
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public import(): T {
|
||||||
|
if (!this.val) throw 'Cannot import if nothing was exported.';
|
||||||
|
return this.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async prepare(name: string, prepare: () => Promise<void>): Promise<void> {
|
||||||
|
let err;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
await prepare();
|
||||||
|
err = null;
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
Logger.error(err, `${name} failed to prepare; retrying in 5s...`)
|
||||||
|
await sleep(5000);
|
||||||
|
}
|
||||||
|
} while (err);
|
||||||
|
Logger.info(`${name} ready!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async close(thingName: string, thing: any, fn: Function): Promise<void> {
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => fn.call(thing, (err: any) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
}));
|
||||||
|
|
||||||
|
Logger.info(`${thingName} closed.`);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(e, `An error occurred while closing the ${thingName}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/Controller.ts
Normal file
120
src/Controller.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import {RequestHandler, Router} from "express";
|
||||||
|
import {PathParams} from "express-serve-static-core";
|
||||||
|
import config from "config";
|
||||||
|
import Logger from "./Logger";
|
||||||
|
|
||||||
|
export default abstract class Controller {
|
||||||
|
private static readonly routes: { [p: string]: string } = {};
|
||||||
|
|
||||||
|
public static route(route: string, params: RouteParams = [], absolute: boolean = false): string {
|
||||||
|
let path = this.routes[route];
|
||||||
|
if (path === undefined) throw new Error(`Unknown route for name ${route}.`);
|
||||||
|
|
||||||
|
if (typeof params === 'string' || typeof params === 'number') {
|
||||||
|
path = path.replace(/:[a-zA-Z_-]+\??/, '' + params);
|
||||||
|
} else if (Array.isArray(params)) {
|
||||||
|
let i = 0;
|
||||||
|
for (const match of path.matchAll(/:[a-zA-Z_-]+\??/)) {
|
||||||
|
if (match.length > 0) {
|
||||||
|
path = path.replace(match[0], typeof params[i] !== 'undefined' ? params[i] : '');
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
path = path.replace(/\/+/, '/');
|
||||||
|
} else {
|
||||||
|
for (const key in params) {
|
||||||
|
if (params.hasOwnProperty(key)) {
|
||||||
|
path = path.replace(new RegExp(`:${key}\\??`), params[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${absolute ? config.get<string>('public_url') : ''}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private router?: Router;
|
||||||
|
|
||||||
|
public getGlobalHandlers(): RequestHandler[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasGlobalHandlers(): boolean {
|
||||||
|
return this.getGlobalHandlers().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setupGlobalHandlers(router: Router): void {
|
||||||
|
for (const globalHandler of this.getGlobalHandlers()) {
|
||||||
|
router.use(this.wrap(globalHandler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRoutesPrefix(): string {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract routes(): void;
|
||||||
|
|
||||||
|
public setupRoutes(router: Router): void {
|
||||||
|
this.router = router;
|
||||||
|
this.routes();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected use(handler: RequestHandler) {
|
||||||
|
this.router?.use(this.wrap(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) {
|
||||||
|
this.registerRoutes(path, handler, routeName);
|
||||||
|
for (const middleware of middlewares) {
|
||||||
|
this.router?.get(path, this.wrap(middleware));
|
||||||
|
}
|
||||||
|
this.router?.get(path, this.wrap(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected post(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) {
|
||||||
|
this.registerRoutes(path, handler, routeName);
|
||||||
|
for (const middleware of middlewares) {
|
||||||
|
this.router?.post(path, this.wrap(middleware));
|
||||||
|
}
|
||||||
|
this.router?.post(path, this.wrap(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrap(handler: RequestHandler): RequestHandler {
|
||||||
|
return (req, res, next) => {
|
||||||
|
const promise = handler.call(this, req, res, next);
|
||||||
|
if (promise instanceof Promise) {
|
||||||
|
promise.catch(err => next(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerRoutes(path: PathParams, handler: RequestHandler, routeName?: string) {
|
||||||
|
if (typeof routeName !== 'string') {
|
||||||
|
routeName = handler.name
|
||||||
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
||||||
|
.replace(/(^_|get_|post_)/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeName.length === 0) return;
|
||||||
|
|
||||||
|
let routePath = null;
|
||||||
|
if (path instanceof Array && path.length > 0) {
|
||||||
|
path = path[0];
|
||||||
|
}
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
const prefix = this.getRoutesPrefix();
|
||||||
|
routePath = (prefix !== '/' ? prefix : '') + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Controller.routes[routeName]) {
|
||||||
|
if (typeof routePath === 'string') {
|
||||||
|
Logger.info(`Route ${routeName} has path ${routePath}`);
|
||||||
|
Controller.routes[routeName] = routePath;
|
||||||
|
} else {
|
||||||
|
Logger.warn(`Cannot assign path to route ${routeName}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RouteParams = { [p: string]: string } | string[] | string | number;
|
79
src/HttpError.ts
Normal file
79
src/HttpError.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {WrappingError} from "./Utils";
|
||||||
|
|
||||||
|
export abstract class HttpError extends WrappingError {
|
||||||
|
public readonly instructions: string;
|
||||||
|
|
||||||
|
constructor(message: string, instructions: string, cause?: Error) {
|
||||||
|
super(message, cause);
|
||||||
|
this.instructions = instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract get errorCode(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadRequestError extends HttpError {
|
||||||
|
public readonly url: string;
|
||||||
|
|
||||||
|
constructor(message: string, instructions: string, url: string, cause?: Error) {
|
||||||
|
super(message, instructions, cause);
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ForbiddenHttpError extends BadRequestError {
|
||||||
|
constructor(thing: string, url: string, cause?: Error) {
|
||||||
|
super(
|
||||||
|
`You don't have access to this ${thing}.`,
|
||||||
|
`${url} doesn't belong to *you*.`,
|
||||||
|
url,
|
||||||
|
cause
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotFoundHttpError extends BadRequestError {
|
||||||
|
constructor(thing: string, url: string, cause?: Error) {
|
||||||
|
super(
|
||||||
|
`${thing.charAt(0).toUpperCase()}${thing.substr(1)} not found.`,
|
||||||
|
`${url} doesn't exist or was deleted.`,
|
||||||
|
url,
|
||||||
|
cause
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerError extends HttpError {
|
||||||
|
constructor(message: string, cause?: Error) {
|
||||||
|
super(message, `Maybe you should contact us; see instructions below.`, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServiceUnavailableHttpError extends ServerError {
|
||||||
|
constructor(message: string, cause?: Error) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 503;
|
||||||
|
}
|
||||||
|
}
|
121
src/Logger.ts
Normal file
121
src/Logger.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import config from "config";
|
||||||
|
import {v4 as uuid} from "uuid";
|
||||||
|
import Log from "./models/Log";
|
||||||
|
|
||||||
|
const LOG_LEVEL: LogLevelKeys = <LogLevelKeys>config.get<string>('log_level');
|
||||||
|
const DB_LOG_LEVEL: LogLevelKeys = <LogLevelKeys>config.get<string>('db_log_level');
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
public static silentError(error: Error, ...message: any[]): string {
|
||||||
|
return this.log('ERROR', message, error, true) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static error(error: Error, ...message: any[]): string {
|
||||||
|
return this.log('ERROR', message, error) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static warn(...message: any[]) {
|
||||||
|
this.log('WARN', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static info(...message: any[]) {
|
||||||
|
this.log('INFO', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static debug(...message: any[]) {
|
||||||
|
this.log('DEBUG', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static dev(...message: any[]) {
|
||||||
|
this.log('DEV', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static log(level: LogLevelKeys, message: any[], error?: Error, silent: boolean = false): string | null {
|
||||||
|
const levelIndex = LogLevel[level];
|
||||||
|
if (levelIndex <= LogLevel[LOG_LEVEL]) {
|
||||||
|
if (error) {
|
||||||
|
if (levelIndex > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`);
|
||||||
|
} else {
|
||||||
|
if (levelIndex <= LogLevel.ERROR) this.warn(`No error attached with log level ${level}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const computedMsg = message.map(v => {
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
return v;
|
||||||
|
} else {
|
||||||
|
return JSON.stringify(v, (key: string, value: any) => {
|
||||||
|
if (value instanceof Object) {
|
||||||
|
if (value.type === 'Buffer') {
|
||||||
|
return `Buffer<${Buffer.from(value.data).toString('hex')}>`;
|
||||||
|
} else if (value !== v) {
|
||||||
|
return `[object Object]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value === 'string' && value.length > 96) {
|
||||||
|
return value.substr(0, 96) + '...';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}, 4);
|
||||||
|
}
|
||||||
|
}).join(' ');
|
||||||
|
|
||||||
|
const log = new Log({});
|
||||||
|
log.setLevel(level);
|
||||||
|
log.message = computedMsg;
|
||||||
|
log.setError(error);
|
||||||
|
|
||||||
|
let logID = Buffer.alloc(16);
|
||||||
|
uuid({}, logID);
|
||||||
|
log.setLogID(logID);
|
||||||
|
|
||||||
|
|
||||||
|
let output = `[${level}] `;
|
||||||
|
let pad = output.length;
|
||||||
|
if (levelIndex <= LogLevel[DB_LOG_LEVEL]) output += `${log.getLogID()} - `;
|
||||||
|
output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad));
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case "ERROR":
|
||||||
|
if (silent || !error) {
|
||||||
|
console.error(output);
|
||||||
|
} else {
|
||||||
|
console.error(output, error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "WARN":
|
||||||
|
console.warn(output);
|
||||||
|
break;
|
||||||
|
case "INFO":
|
||||||
|
console.info(output);
|
||||||
|
break;
|
||||||
|
case "DEBUG":
|
||||||
|
case "DEV":
|
||||||
|
console.debug(output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levelIndex <= LogLevel[DB_LOG_LEVEL]) {
|
||||||
|
log.save().catch(err => {
|
||||||
|
if (!silent && err.message.indexOf('ECONNREFUSED') < 0) {
|
||||||
|
console.error({save_err: err, error});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return log.getLogID();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
ERROR,
|
||||||
|
WARN,
|
||||||
|
INFO,
|
||||||
|
DEBUG,
|
||||||
|
DEV,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogLevelKeys = keyof typeof LogLevel;
|
139
src/Mail.ts
Normal file
139
src/Mail.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import nodemailer, {SentMessageInfo, Transporter} from "nodemailer";
|
||||||
|
import config from "config";
|
||||||
|
import {Options} from "nodemailer/lib/mailer";
|
||||||
|
import nunjucks from 'nunjucks';
|
||||||
|
import * as util from "util";
|
||||||
|
import {WrappingError} from "./Utils";
|
||||||
|
import mjml2html from "mjml";
|
||||||
|
import * as querystring from "querystring";
|
||||||
|
import Logger from "./Logger";
|
||||||
|
|
||||||
|
export function mailRoute(template: string): string {
|
||||||
|
return `/mail/${template}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Mail {
|
||||||
|
private static transporter: Transporter;
|
||||||
|
|
||||||
|
private static getTransporter(): Transporter {
|
||||||
|
if (!this.transporter) throw new MailError('Mail system was not prepared.');
|
||||||
|
return this.transporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async prepare(): Promise<void> {
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: config.get('mail.host'),
|
||||||
|
port: config.get('mail.port'),
|
||||||
|
secure: config.get('mail.secure'),
|
||||||
|
auth: {
|
||||||
|
user: config.get('mail.username'),
|
||||||
|
pass: config.get('mail.password'),
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
rejectUnauthorized: !config.get('mail.allow_invalid_tls')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await util.promisify(transporter.verify)();
|
||||||
|
this.transporter = transporter;
|
||||||
|
} catch (e) {
|
||||||
|
throw new MailError('Connection to mail service unsuccessful.', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static end() {
|
||||||
|
this.transporter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parse(template: string, data: any, textOnly: boolean): string {
|
||||||
|
data.text = textOnly;
|
||||||
|
const nunjucksResult = nunjucks.render(template, data);
|
||||||
|
if (textOnly) return nunjucksResult;
|
||||||
|
|
||||||
|
const mjmlResult = mjml2html(nunjucksResult, {});
|
||||||
|
|
||||||
|
if (mjmlResult.errors.length > 0) {
|
||||||
|
throw new MailError(`Error while parsing mail template ${template}: ${JSON.stringify(mjmlResult.errors, null, 4)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mjmlResult.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly template: MailTemplate;
|
||||||
|
private readonly options: Options = {};
|
||||||
|
private readonly data: { [p: string]: any };
|
||||||
|
|
||||||
|
constructor(template: MailTemplate, data: { [p: string]: any } = {}) {
|
||||||
|
this.template = template;
|
||||||
|
this.data = data;
|
||||||
|
this.options.subject = this.template.getSubject(data);
|
||||||
|
|
||||||
|
this.verifyData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private verifyData() {
|
||||||
|
for (const forbiddenField of [
|
||||||
|
'to',
|
||||||
|
]) {
|
||||||
|
if (this.data[forbiddenField] !== undefined) {
|
||||||
|
throw new MailError(`Can't use reserved data.${forbiddenField}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async send(...to: string[]): Promise<SentMessageInfo[]> {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const destEmail of to) {
|
||||||
|
// Reset options
|
||||||
|
this.options.html = this.options.text = undefined;
|
||||||
|
|
||||||
|
// Set options
|
||||||
|
this.options.to = destEmail;
|
||||||
|
|
||||||
|
// Set data
|
||||||
|
this.data.mail_subject = this.options.subject;
|
||||||
|
this.data.mail_to = this.options.to;
|
||||||
|
this.data.mail_link = `${config.get<string>('public_url')}${mailRoute(this.template.template)}?${querystring.stringify(this.data)}`;
|
||||||
|
|
||||||
|
// Log
|
||||||
|
Logger.dev('Send mail', this.options);
|
||||||
|
|
||||||
|
// Render email
|
||||||
|
this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false);
|
||||||
|
this.options.text = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, true);
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
results.push(await Mail.getTransporter().sendMail(this.options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MailTemplate {
|
||||||
|
private readonly _template: string;
|
||||||
|
private readonly subject: (data: any) => string;
|
||||||
|
|
||||||
|
constructor(template: string, subject: (data: any) => string) {
|
||||||
|
this._template = template;
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get template(): string {
|
||||||
|
return this._template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSubject(data: any): string {
|
||||||
|
return 'Watch My Stream - ' + this.subject(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MailError extends WrappingError {
|
||||||
|
constructor(message: string = 'An error occurred while sending mail.', cause?: Error) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
24
src/Pagination.ts
Normal file
24
src/Pagination.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import Model from "./db/Model";
|
||||||
|
|
||||||
|
export default class Pagination<T extends Model> {
|
||||||
|
private readonly models: T[];
|
||||||
|
public readonly page: number;
|
||||||
|
public readonly perPage: number;
|
||||||
|
public readonly totalCount: number;
|
||||||
|
|
||||||
|
constructor(models: T[], page: number, perPage: number, totalCount: number) {
|
||||||
|
this.models = models;
|
||||||
|
this.page = page;
|
||||||
|
this.perPage = perPage;
|
||||||
|
this.totalCount = totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPrevious(): boolean {
|
||||||
|
return this.page > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasNext(): boolean {
|
||||||
|
return this.models.length >= this.perPage && this.page * this.perPage < this.totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
src/Utils.ts
Normal file
35
src/Utils.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
|
export async function sleep(ms: number): Promise<void> {
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
setTimeout(() => resolve(), ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class WrappingError extends Error {
|
||||||
|
public readonly cause?: Error;
|
||||||
|
|
||||||
|
protected constructor(message: string, cause?: Error) {
|
||||||
|
super(message);
|
||||||
|
this.cause = cause;
|
||||||
|
|
||||||
|
if (cause !== undefined) {
|
||||||
|
this.stack = (this.stack || '') + `\n> Caused by: ${cause.stack}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cryptoRandomDictionary(size: number, dictionary: string): string {
|
||||||
|
const randomBytes = crypto.randomBytes(size);
|
||||||
|
const output = new Array(size);
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
output[i] = dictionary[Math.floor((randomBytes[i] / 255) * dictionary.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.join('');
|
||||||
|
}
|
8
src/WebSocketListener.ts
Normal file
8
src/WebSocketListener.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import WebSocket from "ws";
|
||||||
|
import {IncomingMessage} from "http";
|
||||||
|
|
||||||
|
export default abstract class WebSocketListener {
|
||||||
|
public abstract path(): string;
|
||||||
|
|
||||||
|
public abstract async handle(socket: WebSocket, request: IncomingMessage, session: Express.SessionData): Promise<void>;
|
||||||
|
}
|
54
src/components/CsrfProtectionComponent.ts
Normal file
54
src/components/CsrfProtectionComponent.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import {BadRequestError} from "../HttpError";
|
||||||
|
|
||||||
|
export default class CsrfProtectionComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.getCSRFToken = () => {
|
||||||
|
if (typeof req.session!.csrf !== 'string') {
|
||||||
|
req.session!.csrf = crypto.randomBytes(64).toString('base64');
|
||||||
|
}
|
||||||
|
return req.session!.csrf;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!['GET', 'HEAD', 'OPTIONS'].find(s => s === req.method)) {
|
||||||
|
if (req.session.csrf === undefined) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `You weren't assigned any CSRF token.`);
|
||||||
|
} else if (req.body.csrf === undefined) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `You didn't provide any CSRF token.`);
|
||||||
|
} else if (req.session.csrf !== req.body.csrf) {
|
||||||
|
throw new InvalidCsrfTokenError(req.baseUrl, `Tokens don't match.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidCsrfTokenError extends BadRequestError {
|
||||||
|
constructor(url: string, details: string, cause?: Error) {
|
||||||
|
super(
|
||||||
|
`Invalid CSRF token`,
|
||||||
|
`${details} We can't process this request. Please try again.`,
|
||||||
|
url,
|
||||||
|
cause
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return 'Invalid CSRF Token';
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCode(): number {
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
}
|
40
src/components/ExpressAppComponent.ts
Normal file
40
src/components/ExpressAppComponent.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import express, {Express, Router} from "express";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import {Server} from "http";
|
||||||
|
|
||||||
|
export default class ExpressAppComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly port: number;
|
||||||
|
private server?: Server;
|
||||||
|
|
||||||
|
constructor(port: number) {
|
||||||
|
super();
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
this.server = app.listen(this.port, 'localhost', () => {
|
||||||
|
Logger.info(`Web server running on localhost:${this.port}.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use(express.json());
|
||||||
|
router.use(express.urlencoded());
|
||||||
|
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
req.models = {};
|
||||||
|
req.modelCollections = {};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
await this.close('Webserver', this.server, this.server.close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getServer(): Server {
|
||||||
|
if (!this.server) throw 'Server was not initialized.';
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
}
|
48
src/components/FormHelperComponent.ts
Normal file
48
src/components/FormHelperComponent.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
|
||||||
|
export default class FormHelperComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.query = req.query;
|
||||||
|
|
||||||
|
let _validation: any = null;
|
||||||
|
res.locals.validation = () => {
|
||||||
|
if (!_validation) {
|
||||||
|
const v = req.flash('validation');
|
||||||
|
_validation = v.length > 0 ? v[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _previousFormData: any = null;
|
||||||
|
res.locals.previousFormData = () => {
|
||||||
|
if (!_previousFormData) {
|
||||||
|
const v = req.flash('previousFormData');
|
||||||
|
_previousFormData = v.length > 0 ? v [0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _previousFormData;
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (['GET', 'POST'].find(m => m === req.method)) {
|
||||||
|
if (typeof req.body === 'object') {
|
||||||
|
req.flash('previousFormData', req.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/components/LogRequestsComponent.ts
Normal file
21
src/components/LogRequestsComponent.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import onFinished from "on-finished";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
|
||||||
|
export default class LogRequestsComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
onFinished(res, (err) => {
|
||||||
|
if (!err) {
|
||||||
|
Logger.info(`${req.method} ${req.originalUrl} - ${res.statusCode}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/components/MailComponent.ts
Normal file
14
src/components/MailComponent.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
import Mail from "../Mail";
|
||||||
|
|
||||||
|
export default class MailComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
await this.prepare('Mail connection', () => Mail.prepare());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
Mail.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
src/components/MaintenanceComponent.ts
Normal file
40
src/components/MaintenanceComponent.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, NextFunction, Request, Response, Router} from "express";
|
||||||
|
import {ServiceUnavailableHttpError} from "../HttpError";
|
||||||
|
import Application from "../Application";
|
||||||
|
|
||||||
|
export default class MaintenanceComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly application: Application;
|
||||||
|
private readonly canServe: () => boolean;
|
||||||
|
|
||||||
|
constructor(application: Application, canServe: () => boolean) {
|
||||||
|
super();
|
||||||
|
this.application = application;
|
||||||
|
this.canServe = canServe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (res.headersSent) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.application.isReady()) {
|
||||||
|
res.header({'Retry-After': 60});
|
||||||
|
res.locals.refresh_after = 5;
|
||||||
|
throw new ServiceUnavailableHttpError('Watch My Stream is readying up. Please wait a few seconds...');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.canServe()) {
|
||||||
|
res.locals.refresh_after = 30;
|
||||||
|
throw new ServiceUnavailableHttpError('Watch My Stream is unavailable due to failure of dependent services.');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
src/components/MysqlComponent.ts
Normal file
18
src/components/MysqlComponent.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
import MysqlConnectionManager from "../db/MysqlConnectionManager";
|
||||||
|
|
||||||
|
export default class MysqlComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
await this.prepare('Mysql connection', () => MysqlConnectionManager.prepare());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
await MysqlConnectionManager.endPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public canServe(): boolean {
|
||||||
|
return MysqlConnectionManager.pool !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
src/components/RedirectBackComponent.ts
Normal file
43
src/components/RedirectBackComponent.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
import onFinished from "on-finished";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import {ServerError} from "../HttpError";
|
||||||
|
|
||||||
|
export default class RedirectBackComponent extends ApplicationComponent<void> {
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished(res, (err) => {
|
||||||
|
if (!err && res.statusCode === 200) {
|
||||||
|
req.session!.previousUrl = req.originalUrl;
|
||||||
|
Logger.debug('Prev url set to', req.session!.previousUrl);
|
||||||
|
req.session!.save((err) => {
|
||||||
|
if (err) {
|
||||||
|
Logger.error(err, 'Error while saving session');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.redirectBack = (defaultUrl?: string) => {
|
||||||
|
if (req.session && typeof req.session.previousUrl === 'string') {
|
||||||
|
res.redirect(req.session.previousUrl);
|
||||||
|
} else if (typeof defaultUrl === 'string') {
|
||||||
|
res.redirect(defaultUrl);
|
||||||
|
} else {
|
||||||
|
throw new ServerError('There is no previous url and no default redirection url was provided.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
src/components/RedisComponent.ts
Normal file
40
src/components/RedisComponent.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
import redis, {RedisClient} from "redis";
|
||||||
|
import config from "config";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import session, {Store} from "express-session";
|
||||||
|
import connect_redis from "connect-redis";
|
||||||
|
|
||||||
|
const RedisStore = connect_redis(session);
|
||||||
|
|
||||||
|
export default class RedisComponent extends ApplicationComponent<void> {
|
||||||
|
private redisClient?: RedisClient;
|
||||||
|
private store?: Store;
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), {});
|
||||||
|
this.redisClient.on('error', (err: any) => {
|
||||||
|
Logger.error(err, 'An error occurred with redis.');
|
||||||
|
});
|
||||||
|
this.store = new RedisStore({
|
||||||
|
client: this.redisClient,
|
||||||
|
prefix: 'wms-sess:',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (this.redisClient) {
|
||||||
|
await this.close('Redis connection', this.redisClient, this.redisClient.quit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStore(): Store {
|
||||||
|
if (!this.store) throw `Redis store was not initialized.`;
|
||||||
|
return this.store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canServe(): boolean {
|
||||||
|
return this.redisClient !== undefined && this.redisClient.connected;
|
||||||
|
}
|
||||||
|
}
|
26
src/components/ServeStaticDirectoryComponent.ts
Normal file
26
src/components/ServeStaticDirectoryComponent.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import express, {Express, Router} from "express";
|
||||||
|
import {PathParams} from "express-serve-static-core";
|
||||||
|
|
||||||
|
export default class ServeStaticDirectoryComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly root: string;
|
||||||
|
private readonly path?: PathParams;
|
||||||
|
|
||||||
|
constructor(root: string, routePath?: PathParams) {
|
||||||
|
super();
|
||||||
|
this.root = root;
|
||||||
|
this.path = routePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
if (typeof this.path !== 'undefined') {
|
||||||
|
router.use(this.path, express.static(this.root, {maxAge: 1000 * 3600 * 72}));
|
||||||
|
} else {
|
||||||
|
router.use(express.static(this.root, {maxAge: 1000 * 3600 * 72}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
src/components/SessionComponent.ts
Normal file
57
src/components/SessionComponent.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import session from "express-session";
|
||||||
|
import config from "config";
|
||||||
|
import RedisComponent from "./RedisComponent";
|
||||||
|
import flash from "connect-flash";
|
||||||
|
import {Express, Router} from "express";
|
||||||
|
|
||||||
|
export default class SessionComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly storeComponent: RedisComponent;
|
||||||
|
|
||||||
|
|
||||||
|
public constructor(storeComponent: RedisComponent) {
|
||||||
|
super();
|
||||||
|
this.storeComponent = storeComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
router.use(session({
|
||||||
|
saveUninitialized: true,
|
||||||
|
secret: config.get('session.secret'),
|
||||||
|
store: this.storeComponent.getStore(),
|
||||||
|
resave: true,
|
||||||
|
cookie: {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: config.get('session.cookie.secure'),
|
||||||
|
},
|
||||||
|
rolling: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.use(flash());
|
||||||
|
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session) {
|
||||||
|
throw new Error('Session is unavailable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.session = req.session;
|
||||||
|
|
||||||
|
let _flash: any = null;
|
||||||
|
res.locals.flash = () => {
|
||||||
|
if (!_flash) {
|
||||||
|
_flash = {
|
||||||
|
info: req.flash('info'),
|
||||||
|
success: req.flash('success'),
|
||||||
|
warning: req.flash('warning'),
|
||||||
|
error: req.flash('error'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return _flash;
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
79
src/components/WebSocketServerComponent.ts
Normal file
79
src/components/WebSocketServerComponent.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import ApplicationComponent from "../ApplicationComponent";
|
||||||
|
import {Express, Request, Router} from "express";
|
||||||
|
import WebSocket, {Server as WebSocketServer} from "ws";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
import cookie from "cookie";
|
||||||
|
import cookieParser from "cookie-parser";
|
||||||
|
import config from "config";
|
||||||
|
import ExpressAppComponent from "./ExpressAppComponent";
|
||||||
|
import Application from "../Application";
|
||||||
|
import RedisComponent from "./RedisComponent";
|
||||||
|
import WebSocketListener from "../WebSocketListener";
|
||||||
|
|
||||||
|
export default class WebSocketServerComponent extends ApplicationComponent<void> {
|
||||||
|
private readonly application: Application;
|
||||||
|
private readonly expressAppComponent: ExpressAppComponent;
|
||||||
|
private readonly storeComponent: RedisComponent;
|
||||||
|
|
||||||
|
private wss?: WebSocket.Server;
|
||||||
|
|
||||||
|
constructor(application: Application, expressAppComponent: ExpressAppComponent, storeComponent: RedisComponent) {
|
||||||
|
super();
|
||||||
|
this.expressAppComponent = expressAppComponent;
|
||||||
|
this.application = application;
|
||||||
|
this.storeComponent = storeComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(app: Express, router: Router): Promise<void> {
|
||||||
|
const listeners: { [p: string]: WebSocketListener } = this.application.getWebSocketListeners();
|
||||||
|
this.wss = new WebSocketServer({
|
||||||
|
server: this.expressAppComponent.getServer(),
|
||||||
|
}, () => {
|
||||||
|
Logger.info(`Websocket server started over webserver.`);
|
||||||
|
}).on('error', (err) => {
|
||||||
|
Logger.error(err, 'An error occurred in the websocket server.');
|
||||||
|
}).on('connection', (socket, request) => {
|
||||||
|
const listener = request.url ? listeners[request.url] : null;
|
||||||
|
|
||||||
|
if (!listener) {
|
||||||
|
socket.close(1002, `Path not found ${request.url}`);
|
||||||
|
return;
|
||||||
|
} else if (!request.headers.cookie) {
|
||||||
|
socket.close(1002, `Can't process request without cookies.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.debug(`Websocket on ${request.url}`);
|
||||||
|
|
||||||
|
const cookies = cookie.parse(request.headers.cookie);
|
||||||
|
const sid = cookieParser.signedCookie(cookies['connect.sid'], config.get('session.secret'));
|
||||||
|
|
||||||
|
if (!sid) {
|
||||||
|
socket.close(1002);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = this.storeComponent.getStore();
|
||||||
|
store.get(sid, (err, session) => {
|
||||||
|
if (err || !session) {
|
||||||
|
Logger.error(err, 'Error while initializing session in websocket.');
|
||||||
|
socket.close(1011);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.id = sid;
|
||||||
|
|
||||||
|
store.createSession(<Request>request, session);
|
||||||
|
listener.handle(socket, request, session).catch(err => {
|
||||||
|
Logger.error(err, 'Error in websocket listener.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (this.wss) {
|
||||||
|
await this.close('WebSocket server', this.wss, this.wss.close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/db/Migration.ts
Normal file
15
src/db/Migration.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export default abstract class Migration {
|
||||||
|
public readonly version: number;
|
||||||
|
|
||||||
|
constructor(version: number) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
async shouldRun(currentVersion: number): Promise<boolean> {
|
||||||
|
return this.version > currentVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract async install(): Promise<void>;
|
||||||
|
|
||||||
|
abstract async rollback(): Promise<void>;
|
||||||
|
}
|
311
src/db/Model.ts
Normal file
311
src/db/Model.ts
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
import MysqlConnectionManager, {query} from "./MysqlConnectionManager";
|
||||||
|
import Validator from "./Validator";
|
||||||
|
import {Connection} from "mysql";
|
||||||
|
import Query from "./Query";
|
||||||
|
import {Request} from "express";
|
||||||
|
import Pagination from "../Pagination";
|
||||||
|
|
||||||
|
export default abstract class Model {
|
||||||
|
public static async getById<T extends Model>(id: number): Promise<T | null> {
|
||||||
|
const cachedModel = ModelCache.get(this.table, id);
|
||||||
|
if (cachedModel?.constructor === this) {
|
||||||
|
return <T>cachedModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = await this.models<T>(this.select().where('id', id).first());
|
||||||
|
return models.length > 0 ? models[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async paginate<T extends Model>(request: Request, perPage: number = 20): Promise<T[]> {
|
||||||
|
let page = request.params.page ? parseInt(request.params.page) : 1;
|
||||||
|
let query: Query = this.select().limit(perPage, (page - 1) * perPage).withTotalRowCount();
|
||||||
|
if (request.params.sortBy) {
|
||||||
|
const dir = request.params.sortDirection;
|
||||||
|
query = query.sortBy(request.params.sortBy, dir === 'ASC' || dir === 'DESC' ? dir : undefined);
|
||||||
|
} else {
|
||||||
|
query = query.sortBy('id');
|
||||||
|
}
|
||||||
|
const models = await this.models<T>(query);
|
||||||
|
// @ts-ignore
|
||||||
|
models.pagination = new Pagination(models, page, perPage, models.totalCount);
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static select(...fields: string[]): Query {
|
||||||
|
return Query.select(this.table, ...fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static update(data: { [key: string]: any }): Query {
|
||||||
|
return Query.update(this.table, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static delete(): Query {
|
||||||
|
return Query.delete(this.table);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static async models<T extends Model>(query: Query): Promise<T[]> {
|
||||||
|
const results = await query.execute();
|
||||||
|
const models: T[] = [];
|
||||||
|
const factory = this.getFactory<T>();
|
||||||
|
for (const result of results.results) {
|
||||||
|
const cachedModel = ModelCache.get(this.table, result.id);
|
||||||
|
if (cachedModel && cachedModel.constructor === this) {
|
||||||
|
cachedModel.updateWithData(result);
|
||||||
|
models.push(<T>cachedModel);
|
||||||
|
} else {
|
||||||
|
models.push(factory(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
models.totalCount = results.foundRows;
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async loadRelation<T extends Model>(models: T[], relation: string, model: Function, localField: string) {
|
||||||
|
const loadMap: { [p: number]: (model: T) => void } = {};
|
||||||
|
const ids = models.map(m => {
|
||||||
|
m.relations[relation] = null;
|
||||||
|
if (m[localField]) loadMap[m[localField]] = v => m.relations[relation] = v;
|
||||||
|
return m[localField];
|
||||||
|
}).filter(id => id);
|
||||||
|
for (const v of await (<any>model).models((<any>model).select().whereIn('id', ids))) {
|
||||||
|
loadMap[v.id!](v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getFactory<T extends Model>(factory?: ModelFactory<T>): ModelFactory<T> {
|
||||||
|
if (factory === undefined) {
|
||||||
|
factory = (<any>this).FACTORY;
|
||||||
|
if (factory === undefined) factory = data => new (<any>this)(data);
|
||||||
|
}
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected readonly properties: ModelProperty<any>[] = [];
|
||||||
|
private readonly relations: { [p: string]: (Model | null) } = {};
|
||||||
|
public id?: number;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
public constructor(data: any) {
|
||||||
|
this.defineProperty<number>('id', new Validator());
|
||||||
|
this.defineProperties();
|
||||||
|
this.updateWithData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract defineProperties(): void;
|
||||||
|
|
||||||
|
protected defineProperty<T>(name: string, validator?: Validator<T> | RegExp) {
|
||||||
|
if (validator === undefined) validator = new Validator();
|
||||||
|
if (validator instanceof RegExp) {
|
||||||
|
const regexp = validator;
|
||||||
|
validator = new Validator().regexp(regexp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prop = new ModelProperty<T>(name, validator);
|
||||||
|
this.properties.push(prop);
|
||||||
|
Object.defineProperty(this, name, {
|
||||||
|
get: () => prop.value,
|
||||||
|
set: (value: T) => prop.value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateWithData(data: any) {
|
||||||
|
this.id = data['id'];
|
||||||
|
|
||||||
|
for (const prop of this.properties) {
|
||||||
|
if (data[prop.name] !== undefined) {
|
||||||
|
this[prop.name] = data[prop.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async beforeSave(exists: boolean, connection: Connection): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async afterSave(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(connection?: Connection, postHook?: (callback: () => Promise<void>) => void): Promise<void> {
|
||||||
|
await this.validate(false, connection);
|
||||||
|
|
||||||
|
const exists = await this.exists();
|
||||||
|
let needs_full_update = false;
|
||||||
|
|
||||||
|
if (connection) {
|
||||||
|
needs_full_update = await this.saveTransaction(connection, exists, needs_full_update);
|
||||||
|
} else {
|
||||||
|
needs_full_update = await MysqlConnectionManager.wrapTransaction(async connection => this.saveTransaction(connection, exists, needs_full_update));
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = async () => {
|
||||||
|
if (needs_full_update) {
|
||||||
|
this.updateWithData((await (<Model><unknown>this.constructor).select().where('id', this.id!).first().execute()).results[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
this.cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.afterSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (connection) {
|
||||||
|
postHook!(callback);
|
||||||
|
} else {
|
||||||
|
await callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveTransaction(connection: Connection, exists: boolean, needs_full_update: boolean): Promise<boolean> {
|
||||||
|
// Before save
|
||||||
|
await this.beforeSave(exists, connection);
|
||||||
|
if (exists && this.hasOwnProperty('updated_at')) {
|
||||||
|
this.updated_at = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = [];
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
for (const prop of this.properties) {
|
||||||
|
if (prop.value !== undefined) {
|
||||||
|
props.push(prop.name + '=?');
|
||||||
|
values.push(prop.value);
|
||||||
|
} else {
|
||||||
|
needs_full_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values.push(this.id);
|
||||||
|
await query(`UPDATE ${this.table} SET ${props.join(',')} WHERE id=?`, values, connection);
|
||||||
|
} else {
|
||||||
|
const props_holders = [];
|
||||||
|
for (const prop of this.properties) {
|
||||||
|
if (prop.value !== undefined) {
|
||||||
|
props.push(prop.name);
|
||||||
|
props_holders.push('?');
|
||||||
|
values.push(prop.value);
|
||||||
|
} else {
|
||||||
|
needs_full_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = await query(`INSERT INTO ${this.table} (${props.join(', ')}) VALUES(${props_holders.join(', ')})`, values, connection);
|
||||||
|
|
||||||
|
this.id = result.other.insertId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return needs_full_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get table(): string {
|
||||||
|
return this.name
|
||||||
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
||||||
|
.replace(/^_/, '')
|
||||||
|
+ 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get table(): string {
|
||||||
|
// @ts-ignore
|
||||||
|
return this.constructor.table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(): Promise<boolean> {
|
||||||
|
if (!this.id) return false;
|
||||||
|
|
||||||
|
const result = await query(`SELECT 1 FROM ${this.table} WHERE id=? LIMIT 1`, [
|
||||||
|
this.id,
|
||||||
|
]);
|
||||||
|
return result.results.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
if (!(await this.exists())) throw new Error('This model instance doesn\'t exist in DB.');
|
||||||
|
|
||||||
|
await query(`DELETE FROM ${this.table} WHERE id=?`, [
|
||||||
|
this.id,
|
||||||
|
]);
|
||||||
|
ModelCache.forget(this);
|
||||||
|
this.id = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(onlyFormat: boolean = false, connection?: Connection): Promise<void[]> {
|
||||||
|
return await Promise.all(this.properties.map(prop => prop.validate(onlyFormat, connection)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private cache() {
|
||||||
|
ModelCache.cache(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected relation<T extends Model>(name: string): T | null {
|
||||||
|
if (this.relations[name] === undefined) throw new Error('Model not loaded');
|
||||||
|
return <T | null>this.relations[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelFactory<T extends Model> {
|
||||||
|
(data: any): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModelProperty<T> {
|
||||||
|
public readonly name: string;
|
||||||
|
private readonly validator: Validator<T>;
|
||||||
|
private val?: T;
|
||||||
|
|
||||||
|
constructor(name: string, validator: Validator<T>) {
|
||||||
|
this.name = name;
|
||||||
|
this.validator = validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(onlyFormat: boolean, connection?: Connection): Promise<void> {
|
||||||
|
return await this.validator.execute(this.name, this.value, onlyFormat, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value(): T | undefined {
|
||||||
|
return this.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set value(val: T | undefined) {
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModelCache {
|
||||||
|
private static readonly caches: {
|
||||||
|
[key: string]: {
|
||||||
|
[key: number]: Model
|
||||||
|
}
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
public static cache(instance: Model) {
|
||||||
|
if (instance.id === undefined) throw new Error('Cannot cache an instance with an undefined id.');
|
||||||
|
|
||||||
|
let tableCache = this.caches[instance.table];
|
||||||
|
if (!tableCache) tableCache = this.caches[instance.table] = {};
|
||||||
|
|
||||||
|
if (!tableCache[instance.id]) tableCache[instance.id] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static forget(instance: Model) {
|
||||||
|
if (instance.id === undefined) throw new Error('Cannot forget an instance with an undefined id.');
|
||||||
|
|
||||||
|
let tableCache = this.caches[instance.table];
|
||||||
|
if (!tableCache) return;
|
||||||
|
|
||||||
|
if (tableCache[instance.id]) delete tableCache[instance.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static all(table: string): {
|
||||||
|
[key: number]: Model
|
||||||
|
} | undefined {
|
||||||
|
return this.caches[table];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get(table: string, id: number): Model | undefined {
|
||||||
|
const tableCache = this.all(table);
|
||||||
|
if (!tableCache) return undefined;
|
||||||
|
return tableCache[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
169
src/db/MysqlConnectionManager.ts
Normal file
169
src/db/MysqlConnectionManager.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import mysql, {Connection, FieldInfo, Pool} from 'mysql';
|
||||||
|
import config from 'config';
|
||||||
|
import Migration from "./Migration";
|
||||||
|
import Logger from "../Logger";
|
||||||
|
|
||||||
|
export interface QueryResult {
|
||||||
|
readonly results: any[];
|
||||||
|
readonly fields: FieldInfo[];
|
||||||
|
readonly other?: any;
|
||||||
|
foundRows?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function query(queryString: string, values?: any, connection?: Connection): Promise<QueryResult> {
|
||||||
|
return await MysqlConnectionManager.query(queryString, values, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MysqlConnectionManager {
|
||||||
|
private static currentPool?: Pool;
|
||||||
|
private static databaseReady: boolean = false;
|
||||||
|
private static readonly migrations: Migration[] = [];
|
||||||
|
|
||||||
|
public static registerMigration(migration: (version: number) => Migration) {
|
||||||
|
this.migrations.push(migration(this.migrations.length + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async prepare() {
|
||||||
|
if (config.get('mysql.create_database_automatically') === true) {
|
||||||
|
const dbName = config.get('mysql.database');
|
||||||
|
Logger.info(`Creating database ${dbName}...`);
|
||||||
|
const connection = mysql.createConnection({
|
||||||
|
host: config.get('mysql.host'),
|
||||||
|
user: config.get('mysql.user'),
|
||||||
|
password: config.get('mysql.password'),
|
||||||
|
});
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
connection.query(`CREATE DATABASE IF NOT EXISTS ${dbName}`, (error) => {
|
||||||
|
if (error !== null) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
connection.end();
|
||||||
|
Logger.info(`Database ${dbName} created!`);
|
||||||
|
}
|
||||||
|
this.databaseReady = true;
|
||||||
|
|
||||||
|
await this.handleMigrations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get pool(): Pool {
|
||||||
|
if (this.currentPool === undefined) {
|
||||||
|
this.currentPool = this.createPool();
|
||||||
|
}
|
||||||
|
return this.currentPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createPool(): Pool {
|
||||||
|
return mysql.createPool({
|
||||||
|
connectionLimit: config.get('mysql.connectionLimit'),
|
||||||
|
host: config.get('mysql.host'),
|
||||||
|
user: config.get('mysql.user'),
|
||||||
|
password: config.get('mysql.password'),
|
||||||
|
database: config.get('mysql.database'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async endPool(): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (this.currentPool !== undefined) {
|
||||||
|
this.currentPool.end(() => {
|
||||||
|
Logger.info('Mysql connection pool ended.');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
this.currentPool = undefined;
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async query(queryString: string, values?: any, connection?: Connection): Promise<QueryResult> {
|
||||||
|
return await new Promise<QueryResult>((resolve, reject) => {
|
||||||
|
Logger.dev('Mysql query:', queryString, '; values:', values);
|
||||||
|
(connection ? connection : this.pool).query(queryString, values, (error, results, fields) => {
|
||||||
|
if (error !== null) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
results: Array.isArray(results) ? results : [],
|
||||||
|
fields: fields !== undefined ? fields : [],
|
||||||
|
other: Array.isArray(results) ? null : results
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async wrapTransaction<T>(transaction: (connection: Connection) => Promise<T>): Promise<T> {
|
||||||
|
return await new Promise<T>((resolve, reject) => {
|
||||||
|
this.pool.getConnection((err, connection) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.beginTransaction((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
this.pool.releaseConnection(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction(connection).then(val => {
|
||||||
|
connection.commit((err) => {
|
||||||
|
if (err) {
|
||||||
|
this.rejectAndRollback(connection, err, reject);
|
||||||
|
this.pool.releaseConnection(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pool.releaseConnection(connection);
|
||||||
|
resolve(val);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
this.rejectAndRollback(connection, err, reject);
|
||||||
|
this.pool.releaseConnection(connection);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static rejectAndRollback(connection: Connection, err: any, reject: (err: any) => void) {
|
||||||
|
connection.rollback((rollbackErr) => {
|
||||||
|
if (rollbackErr) {
|
||||||
|
reject(err + '\n' + rollbackErr);
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async handleMigrations() {
|
||||||
|
let currentVersion = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await query('SELECT id FROM migrations ORDER BY id DESC LIMIT 1');
|
||||||
|
currentVersion = result.results[0].id;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ECONNREFUSED' || e.code !== 'ER_NO_SUCH_TABLE') {
|
||||||
|
throw new Error('Cannot run migrations: ' + e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const migration of this.migrations) {
|
||||||
|
if (await migration.shouldRun(currentVersion)) {
|
||||||
|
Logger.info('Running migration ', migration.version, migration.constructor.name);
|
||||||
|
await migration.install();
|
||||||
|
await query('INSERT INTO migrations VALUES(?, ?, NOW())', [
|
||||||
|
migration.version,
|
||||||
|
migration.constructor.name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
214
src/db/Query.ts
Normal file
214
src/db/Query.ts
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import {query, QueryResult} from "./MysqlConnectionManager";
|
||||||
|
import {Connection} from "mysql";
|
||||||
|
|
||||||
|
export default class Query {
|
||||||
|
public static select(table: string, ...fields: string[]): Query {
|
||||||
|
return new Query(QueryType.SELECT, table, fields.length > 0 ? fields : ['*']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static update(table: string, data: {
|
||||||
|
[key: string]: any
|
||||||
|
}) {
|
||||||
|
const fields = [];
|
||||||
|
for (let key in data) {
|
||||||
|
if (data.hasOwnProperty(key)) {
|
||||||
|
fields.push(new UpdateFieldValue(key, data[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Query(QueryType.UPDATE, table, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static delete(table: string) {
|
||||||
|
return new Query(QueryType.DELETE, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly type: QueryType;
|
||||||
|
private readonly table: string;
|
||||||
|
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
|
||||||
|
private _where: WhereFieldValue[] = [];
|
||||||
|
private _limit?: number;
|
||||||
|
private _offset?: number;
|
||||||
|
private _sortBy?: string;
|
||||||
|
private _sortDirection?: 'ASC' | 'DESC';
|
||||||
|
private _foundRows: boolean = false;
|
||||||
|
|
||||||
|
private constructor(type: QueryType, table: string, fields?: (string | SelectFieldValue | UpdateFieldValue)[]) {
|
||||||
|
this.type = type;
|
||||||
|
this.table = table;
|
||||||
|
this.fields = fields || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public where(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND, test: WhereTest = WhereTest.EQUALS): Query {
|
||||||
|
this._where.push(new WhereFieldValue(field, value, operator, test));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public whereNot(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND): Query {
|
||||||
|
return this.where(field, value, operator, WhereTest.DIFFERENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public orWhere(field: string, value: string | Date | Query | any): Query {
|
||||||
|
return this.where(field, value, WhereOperator.OR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public whereIn(field: string, value: any[]): Query {
|
||||||
|
return this.where(field, value, WhereOperator.AND, WhereTest.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public limit(limit: number, offset: number = 0): Query {
|
||||||
|
this._limit = limit;
|
||||||
|
this._offset = offset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public first(): Query {
|
||||||
|
return this.limit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sortBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): Query {
|
||||||
|
this._sortBy = field;
|
||||||
|
this._sortDirection = direction;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withTotalRowCount(): Query {
|
||||||
|
this._foundRows = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(final: boolean = false): string {
|
||||||
|
let query = '';
|
||||||
|
|
||||||
|
let fields = this.fields?.join(',');
|
||||||
|
|
||||||
|
let where = '';
|
||||||
|
if (this._where.length > 0) {
|
||||||
|
where = `WHERE ${this._where[0]}`;
|
||||||
|
for (let i = 1; i < this._where.length; i++) {
|
||||||
|
where += this._where[i].toString(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let limit = '';
|
||||||
|
if (typeof this._limit === 'number') {
|
||||||
|
limit = `LIMIT ${this._limit}`;
|
||||||
|
if (typeof this._offset === 'number' && this._offset !== 0) {
|
||||||
|
limit += ` OFFSET ${this._offset}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let orderBy = '';
|
||||||
|
if (typeof this._sortBy === 'string') {
|
||||||
|
orderBy = `ORDER BY ${this._sortBy} ${this._sortDirection}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.type) {
|
||||||
|
case QueryType.SELECT:
|
||||||
|
query = `SELECT ${this._foundRows ? 'SQL_CALC_FOUND_ROWS' : ''} ${fields} FROM ${this.table} ${where} ${orderBy} ${limit}`;
|
||||||
|
break;
|
||||||
|
case QueryType.UPDATE:
|
||||||
|
query = `UPDATE ${this.table} SET ${fields} ${where} ${orderBy} ${limit}`;
|
||||||
|
break;
|
||||||
|
case QueryType.DELETE:
|
||||||
|
query = `DELETE FROM ${this.table} ${where} ${orderBy} ${limit}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return final ? query : `(${query})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): string {
|
||||||
|
return this.toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get variables(): any[] {
|
||||||
|
const variables: any[] = [];
|
||||||
|
this.fields?.filter(v => v instanceof FieldValue)
|
||||||
|
.flatMap(v => (<FieldValue>v).variables)
|
||||||
|
.forEach(v => variables.push(v));
|
||||||
|
this._where.flatMap(v => v.variables)
|
||||||
|
.forEach(v => variables.push(v));
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isCacheable(): boolean {
|
||||||
|
return this.type === QueryType.SELECT && this.fields.length === 1 && this.fields[0] === '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(connection?: Connection): Promise<QueryResult> {
|
||||||
|
const queryResult = await query(this.build(), this.variables, connection);
|
||||||
|
if (this._foundRows) {
|
||||||
|
const foundRows = await query('SELECT FOUND_ROWS() as r', undefined, connection);
|
||||||
|
queryResult.foundRows = foundRows.results[0].r;
|
||||||
|
}
|
||||||
|
return queryResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryType {
|
||||||
|
SELECT,
|
||||||
|
UPDATE,
|
||||||
|
DELETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WhereOperator {
|
||||||
|
AND = 'AND',
|
||||||
|
OR = 'OR',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WhereTest {
|
||||||
|
EQUALS = '=',
|
||||||
|
DIFFERENT = '!=',
|
||||||
|
IN = ' IN ',
|
||||||
|
}
|
||||||
|
|
||||||
|
class FieldValue {
|
||||||
|
protected readonly field: string;
|
||||||
|
protected value: any;
|
||||||
|
|
||||||
|
constructor(field: string, value: any) {
|
||||||
|
this.field = field;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(first: boolean = true): string {
|
||||||
|
return `${!first ? ',' : ''}${this.field}${this.test}${this.value instanceof Query ? this.value : (Array.isArray(this.value) ? '(?)' : '?')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get test(): string {
|
||||||
|
return '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get variables(): any[] {
|
||||||
|
return this.value instanceof Query ? this.value.variables : [this.value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectFieldValue extends FieldValue {
|
||||||
|
public toString(first: boolean = true): string {
|
||||||
|
return `(${this.value instanceof Query ? this.value : '?'}) AS ${this.field}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateFieldValue extends FieldValue {
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhereFieldValue extends FieldValue {
|
||||||
|
private readonly operator: WhereOperator;
|
||||||
|
private readonly _test: WhereTest;
|
||||||
|
|
||||||
|
constructor(field: string, value: any, operator: WhereOperator, test: WhereTest) {
|
||||||
|
super(field, value);
|
||||||
|
this.operator = operator;
|
||||||
|
this._test = test;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(first: boolean = true): string {
|
||||||
|
return (!first ? ` ${this.operator} ` : '') + super.toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get test(): string {
|
||||||
|
return this._test;
|
||||||
|
}
|
||||||
|
}
|
308
src/db/Validator.ts
Normal file
308
src/db/Validator.ts
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
import Query from "./Query";
|
||||||
|
import {Connection} from "mysql";
|
||||||
|
|
||||||
|
export default class Validator<T> {
|
||||||
|
private readonly steps: ValidationStep<T>[] = [];
|
||||||
|
private readonly validationAttributes: string[] = [];
|
||||||
|
|
||||||
|
private _min?: number;
|
||||||
|
private _max?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param thingName The name of the thing to validate.
|
||||||
|
* @param value The value to verify.
|
||||||
|
* @param onlyFormat {@code true} to only validate format properties, {@code false} otherwise.
|
||||||
|
* @param connection A connection to use in case of wrapped transactions.
|
||||||
|
*/
|
||||||
|
async execute(thingName: string, value: T | undefined, onlyFormat: boolean, connection?: Connection): Promise<void> {
|
||||||
|
const bag = new ValidationBag();
|
||||||
|
|
||||||
|
for (const step of this.steps) {
|
||||||
|
if (onlyFormat && !step.isFormat) continue;
|
||||||
|
|
||||||
|
const result = step.verifyStep(value, thingName, connection);
|
||||||
|
if ((result === false || result instanceof Promise && (await result) === false) && step.throw) {
|
||||||
|
const error: ValidationError = step.throw();
|
||||||
|
error.thingName = thingName;
|
||||||
|
error.value = value;
|
||||||
|
bag.addMessage(error);
|
||||||
|
} else if (step.interrupt !== undefined && step.interrupt(value)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bag.hasMessages()) {
|
||||||
|
throw bag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public defined(): Validator<T> {
|
||||||
|
this.validationAttributes.push('required');
|
||||||
|
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => val !== undefined,
|
||||||
|
throw: () => new UndefinedValueValidationError(),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public acceptUndefined(): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: () => true,
|
||||||
|
throw: null,
|
||||||
|
interrupt: val => val === undefined || val === null,
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public equals(other?: T): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => val === other,
|
||||||
|
throw: () => new BadValueValidationError(other),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public regexp(regexp: RegExp): Validator<T> {
|
||||||
|
this.validationAttributes.push(`pattern="${regexp}"`);
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => regexp.test(<string><unknown>val),
|
||||||
|
throw: () => new InvalidFormatValidationError(),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public length(length: number): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => (<any>val).length === length,
|
||||||
|
throw: () => new BadLengthValidationError(length),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param minLength included
|
||||||
|
* @param maxLength included
|
||||||
|
*/
|
||||||
|
public between(minLength: number, maxLength: number): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => {
|
||||||
|
const length = (<any>val).length;
|
||||||
|
return length >= minLength && length <= maxLength;
|
||||||
|
},
|
||||||
|
throw: () => new BadLengthValidationError(minLength, maxLength),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min included
|
||||||
|
*/
|
||||||
|
public min(min: number): Validator<T> {
|
||||||
|
this.validationAttributes.push(`min="${min}"`);
|
||||||
|
this._min = min;
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => {
|
||||||
|
return (<any>val) >= min;
|
||||||
|
},
|
||||||
|
throw: () => new OutOfRangeValidationError(this._min, this._max),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param max included
|
||||||
|
*/
|
||||||
|
public max(max: number): Validator<T> {
|
||||||
|
this.validationAttributes.push(`max="${max}"`);
|
||||||
|
this._max = max;
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: val => {
|
||||||
|
return (<any>val) <= max;
|
||||||
|
},
|
||||||
|
throw: () => new OutOfRangeValidationError(this._min, this._max),
|
||||||
|
isFormat: true,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unique(model: Model, querySupplier?: () => Query): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: async (val, thingName, c) => {
|
||||||
|
let query: Query;
|
||||||
|
if (querySupplier) {
|
||||||
|
query = querySupplier().where(thingName, val);
|
||||||
|
} else {
|
||||||
|
query = (<any>model.constructor).select('1').where(thingName, val);
|
||||||
|
}
|
||||||
|
if (typeof model.id === 'number') query = query.whereNot('id', model.id);
|
||||||
|
return (await query.execute(c)).results.length === 0;
|
||||||
|
},
|
||||||
|
throw: () => new AlreadyExistsValidationError(model.table),
|
||||||
|
isFormat: false,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public exists(modelClass: Function, foreignKey?: string): Validator<T> {
|
||||||
|
this.addStep({
|
||||||
|
verifyStep: async (val, thingName, c) => (await (<any>modelClass).select('1').where(foreignKey !== undefined ? foreignKey : thingName, val).execute(c)).results.length >= 1,
|
||||||
|
throw: () => new UnknownRelationValidationError((<any>modelClass).table, foreignKey),
|
||||||
|
isFormat: false,
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addStep(step: ValidationStep<T>) {
|
||||||
|
this.steps.push(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValidationAttributes(): string[] {
|
||||||
|
return this.validationAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public step(step: number): Validator<T> {
|
||||||
|
this.validationAttributes.push(`step="${step}"`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValidationStep<T> {
|
||||||
|
interrupt?: (val?: T) => boolean;
|
||||||
|
|
||||||
|
verifyStep(val: T | undefined, thingName: string, connection?: Connection): boolean | Promise<boolean>;
|
||||||
|
|
||||||
|
throw: ((val?: T) => ValidationError) | null;
|
||||||
|
|
||||||
|
readonly isFormat: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidationBag extends Error {
|
||||||
|
private readonly messages: { [p: string]: any } = {};
|
||||||
|
|
||||||
|
public addMessage(err: ValidationError) {
|
||||||
|
if (!err.thingName) {
|
||||||
|
throw new Error('Null thing name');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages[err.thingName] = {
|
||||||
|
name: err.name,
|
||||||
|
message: err.message,
|
||||||
|
value: err.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasMessages(): boolean {
|
||||||
|
return Object.keys(this.messages).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMessages(): { [p: string]: ValidationError } {
|
||||||
|
return this.messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ValidationError extends Error {
|
||||||
|
public thingName?: string;
|
||||||
|
public value?: any;
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadLengthValidationError extends ValidationError {
|
||||||
|
private readonly expectedLength: number;
|
||||||
|
private readonly maxLength?: number;
|
||||||
|
|
||||||
|
constructor(expectedLength: number, maxLength?: number) {
|
||||||
|
super();
|
||||||
|
this.expectedLength = expectedLength;
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
return `${this.thingName} expected length: ${this.expectedLength}${this.maxLength !== undefined ? ` to ${this.maxLength}` : ''}; ` +
|
||||||
|
`actual length: ${this.value.length}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadValueValidationError extends ValidationError {
|
||||||
|
private readonly expectedValue: any;
|
||||||
|
|
||||||
|
constructor(expectedValue: any) {
|
||||||
|
super();
|
||||||
|
this.expectedValue = expectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
return `Expected: ${this.expectedValue}; got: ${this.value}.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OutOfRangeValidationError extends ValidationError {
|
||||||
|
private readonly min?: number;
|
||||||
|
private readonly max?: number;
|
||||||
|
|
||||||
|
constructor(min?: number, max?: number) {
|
||||||
|
super();
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
if (this.min === undefined) {
|
||||||
|
return `${this.thingName} must be at most ${this.max}`;
|
||||||
|
} else if (this.max === undefined) {
|
||||||
|
return `${this.thingName} must be at least ${this.min}`;
|
||||||
|
}
|
||||||
|
return `${this.thingName} must be between ${this.min} and ${this.max}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidFormatValidationError extends ValidationError {
|
||||||
|
public get message(): string {
|
||||||
|
return `"${this.value}" is not a valid ${this.thingName}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UndefinedValueValidationError extends ValidationError {
|
||||||
|
public get message(): string {
|
||||||
|
return `${this.thingName} is required.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AlreadyExistsValidationError extends ValidationError {
|
||||||
|
private readonly table: string;
|
||||||
|
|
||||||
|
constructor(table: string) {
|
||||||
|
super();
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
return `${this.value} already exists in ${this.table}.${this.thingName}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnknownRelationValidationError extends ValidationError {
|
||||||
|
private readonly table: string;
|
||||||
|
private readonly foreignKey?: string;
|
||||||
|
|
||||||
|
constructor(table: string, foreignKey?: string) {
|
||||||
|
super();
|
||||||
|
this.table = table;
|
||||||
|
this.foreignKey = foreignKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
return `${this.thingName}=${this.value} relation was not found in ${this.table}${this.foreignKey !== undefined ? `.${this.foreignKey}` : ''}.`;
|
||||||
|
}
|
||||||
|
}
|
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {default as Application} from "./Application";
|
25
src/migrations/CreateLogsTable.ts
Normal file
25
src/migrations/CreateLogsTable.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Migration from "../db/Migration";
|
||||||
|
import {query} from "../db/MysqlConnectionManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateLogsTable extends Migration {
|
||||||
|
async install(): Promise<void> {
|
||||||
|
await query('CREATE TABLE logs(' +
|
||||||
|
'id INT NOT NULL AUTO_INCREMENT,' +
|
||||||
|
'level TINYINT UNSIGNED NOT NULL,' +
|
||||||
|
'message TEXT NOT NULL,' +
|
||||||
|
'log_id BINARY(16),' +
|
||||||
|
'error_name VARCHAR(128),' +
|
||||||
|
'error_message VARCHAR(512),' +
|
||||||
|
'error_stack TEXT,' +
|
||||||
|
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
||||||
|
'PRIMARY KEY (id)' +
|
||||||
|
')');
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollback(): Promise<void> {
|
||||||
|
await query('DROP TABLE logs');
|
||||||
|
}
|
||||||
|
}
|
32
src/migrations/CreateMigrationsTable.ts
Normal file
32
src/migrations/CreateMigrationsTable.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import Migration from "../db/Migration";
|
||||||
|
import {query} from "../db/MysqlConnectionManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be the first migration
|
||||||
|
*/
|
||||||
|
export default class CreateMigrationsTable extends Migration {
|
||||||
|
async shouldRun(currentVersion: number): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await query('SELECT 1 FROM migrations LIMIT 1');
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'ER_NO_SUCH_TABLE') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await super.shouldRun(currentVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(): Promise<void> {
|
||||||
|
await query('CREATE TABLE migrations(' +
|
||||||
|
'id INT NOT NULL,' +
|
||||||
|
'name VARCHAR(64) NOT NULL,' +
|
||||||
|
'migration_date DATE,' +
|
||||||
|
'PRIMARY KEY (id)' +
|
||||||
|
')');
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollback(): Promise<void> {
|
||||||
|
await query('DROP TABLE migrations');
|
||||||
|
}
|
||||||
|
}
|
69
src/models/Log.ts
Normal file
69
src/models/Log.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import Model from "../db/Model";
|
||||||
|
import {LogLevel, LogLevelKeys} from "../Logger";
|
||||||
|
import Validator from "../db/Validator";
|
||||||
|
|
||||||
|
export default class Log extends Model {
|
||||||
|
private level?: number;
|
||||||
|
public message?: string;
|
||||||
|
private log_id?: Buffer;
|
||||||
|
private error_name?: string;
|
||||||
|
private error_message?: string;
|
||||||
|
private error_stack?: string;
|
||||||
|
private created_at?: Date;
|
||||||
|
|
||||||
|
protected defineProperties(): void {
|
||||||
|
this.defineProperty<number>('level', new Validator<number>().defined());
|
||||||
|
this.defineProperty<string>('message', new Validator<string>().defined().between(0, 65535));
|
||||||
|
this.defineProperty<Buffer>('log_id', new Validator<Buffer>().acceptUndefined().length(16));
|
||||||
|
this.defineProperty<string>('error_name', new Validator<string>().acceptUndefined().between(0, 128));
|
||||||
|
this.defineProperty<string>('error_message', new Validator<string>().acceptUndefined().between(0, 512));
|
||||||
|
this.defineProperty<string>('error_stack', new Validator<string>().acceptUndefined().between(0, 65535));
|
||||||
|
this.defineProperty<Date>('created_at', new Validator<Date>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLevel(): LogLevelKeys {
|
||||||
|
if (typeof this.level !== 'number') return 'ERROR';
|
||||||
|
return <LogLevelKeys>LogLevel[this.level];
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLevel(level: LogLevelKeys) {
|
||||||
|
this.level = LogLevel[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLogID(): string | null {
|
||||||
|
if (!this.log_id) return null;
|
||||||
|
const chars = this.log_id!.toString('hex');
|
||||||
|
let out = '';
|
||||||
|
let i = 0;
|
||||||
|
for (const l of [8, 4, 4, 4, 12]) {
|
||||||
|
if (i > 0) out += '-';
|
||||||
|
out += chars.substr(i, l);
|
||||||
|
i += l;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLogID(buffer: Buffer) {
|
||||||
|
this.log_id = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorName(): string {
|
||||||
|
return this.error_name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorMessage(): string {
|
||||||
|
return this.error_message || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorStack(): string {
|
||||||
|
return this.error_stack || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public setError(error?: Error) {
|
||||||
|
if (!error) return;
|
||||||
|
|
||||||
|
this.error_name = error.name;
|
||||||
|
this.error_message = error.message;
|
||||||
|
this.error_stack = error.stack;
|
||||||
|
}
|
||||||
|
}
|
20
src/types/Express.d.ts
vendored
Normal file
20
src/types/Express.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Environment} from "nunjucks";
|
||||||
|
import Model from "../db/Model";
|
||||||
|
|
||||||
|
declare module 'express-serve-static-core' {
|
||||||
|
export interface Request {
|
||||||
|
env: Environment;
|
||||||
|
models: { [p: string]: Model | null };
|
||||||
|
modelCollections: { [p: string]: Model[] | null };
|
||||||
|
|
||||||
|
flash(): { [key: string]: string[] };
|
||||||
|
|
||||||
|
flash(message: string): any;
|
||||||
|
|
||||||
|
flash(event: string, message: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
redirectBack(defaultUrl?: string): any;
|
||||||
|
}
|
||||||
|
}
|
32
tsconfig.json
Normal file
32
tsconfig.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES6",
|
||||||
|
"declaration": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"rootDir": "src",
|
||||||
|
"sourceRoot": ".",
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
"src/types"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es2020"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
3132
tsconfig.tsbuildinfo
Normal file
3132
tsconfig.tsbuildinfo
Normal file
File diff suppressed because it is too large
Load Diff
439
yarn.lock
Normal file
439
yarn.lock
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/body-parser@*":
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
|
||||||
|
integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/connect" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/connect@*":
|
||||||
|
version "3.4.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
|
||||||
|
integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/express-serve-static-core@*":
|
||||||
|
version "4.17.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz#a00ac7dadd746ae82477443e4d480a6a93ea083c"
|
||||||
|
integrity sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
"@types/range-parser" "*"
|
||||||
|
|
||||||
|
"@types/express@^4.17.6":
|
||||||
|
version "4.17.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e"
|
||||||
|
integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==
|
||||||
|
dependencies:
|
||||||
|
"@types/body-parser" "*"
|
||||||
|
"@types/express-serve-static-core" "*"
|
||||||
|
"@types/qs" "*"
|
||||||
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/mime@*":
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||||
|
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
|
||||||
|
|
||||||
|
"@types/node@*":
|
||||||
|
version "13.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54"
|
||||||
|
integrity sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==
|
||||||
|
|
||||||
|
"@types/qs@*":
|
||||||
|
version "6.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7"
|
||||||
|
integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==
|
||||||
|
|
||||||
|
"@types/range-parser@*":
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||||
|
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
|
||||||
|
|
||||||
|
"@types/serve-static@*":
|
||||||
|
version "1.13.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
|
||||||
|
integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
|
||||||
|
dependencies:
|
||||||
|
"@types/express-serve-static-core" "*"
|
||||||
|
"@types/mime" "*"
|
||||||
|
|
||||||
|
accepts@~1.3.7:
|
||||||
|
version "1.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
||||||
|
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
negotiator "0.6.2"
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
body-parser@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||||
|
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.7.0"
|
||||||
|
raw-body "2.4.0"
|
||||||
|
type-is "~1.6.17"
|
||||||
|
|
||||||
|
bytes@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
|
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||||
|
|
||||||
|
content-disposition@0.5.3:
|
||||||
|
version "0.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||||
|
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
content-type@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
|
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||||
|
|
||||||
|
cookie@0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||||
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
depd@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
|
destroy@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||||
|
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
|
encodeurl@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
|
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||||
|
|
||||||
|
escape-html@~1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||||
|
|
||||||
|
etag@~1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
|
express@^4.17.1:
|
||||||
|
version "4.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||||
|
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.7"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.19.0"
|
||||||
|
content-disposition "0.5.3"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.4.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "~1.1.2"
|
||||||
|
fresh "0.5.2"
|
||||||
|
merge-descriptors "1.0.1"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
path-to-regexp "0.1.7"
|
||||||
|
proxy-addr "~2.0.5"
|
||||||
|
qs "6.7.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
send "0.17.1"
|
||||||
|
serve-static "1.14.1"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
finalhandler@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||||
|
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
forwarded@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||||
|
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||||
|
|
||||||
|
http-errors@1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.3"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
http-errors@~1.7.2:
|
||||||
|
version "1.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||||
|
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.4"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
version "0.4.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
inherits@2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
inherits@2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1:
|
||||||
|
version "1.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||||
|
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
merge-descriptors@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
|
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||||
|
|
||||||
|
methods@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
|
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||||
|
|
||||||
|
mime-db@1.43.0:
|
||||||
|
version "1.43.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||||
|
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
|
||||||
|
|
||||||
|
mime-types@~2.1.24:
|
||||||
|
version "2.1.26"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||||
|
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.43.0"
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
ms@2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
|
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||||
|
|
||||||
|
negotiator@0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||||
|
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||||
|
|
||||||
|
on-finished@~2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||||
|
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
parseurl@~1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
|
path-to-regexp@0.1.7:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
|
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||||
|
|
||||||
|
proxy-addr@~2.0.5:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||||
|
integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
|
||||||
|
dependencies:
|
||||||
|
forwarded "~0.1.2"
|
||||||
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
qs@6.7.0:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
|
|
||||||
|
range-parser@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||||
|
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
safe-buffer@5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
send@0.17.1:
|
||||||
|
version "0.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||||
|
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
destroy "~1.0.4"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "~1.7.2"
|
||||||
|
mime "1.6.0"
|
||||||
|
ms "2.1.1"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
|
||||||
|
serve-static@1.14.1:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||||
|
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||||
|
dependencies:
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
send "0.17.1"
|
||||||
|
|
||||||
|
setprototypeof@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||||
|
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||||
|
|
||||||
|
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||||
|
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||||
|
|
||||||
|
toidentifier@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
|
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||||
|
|
||||||
|
type-is@~1.6.17, type-is@~1.6.18:
|
||||||
|
version "1.6.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
|
typescript@^3.8.3:
|
||||||
|
version "3.8.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
|
||||||
|
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
|
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||||
|
|
||||||
|
ws@^7.2.3:
|
||||||
|
version "7.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
|
||||||
|
integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==
|
Loading…
Reference in New Issue
Block a user