@flink-app/flink 0.3.8 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/FlinkApp.d.ts +6 -0
- package/dist/src/FlinkApp.js +12 -7
- package/package.json +2 -2
- package/readme.md +14 -10
- package/src/FlinkApp.ts +459 -513
package/dist/src/FlinkApp.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsJson } from "body-parser";
|
|
1
2
|
import { Express } from "express";
|
|
2
3
|
import { JSONSchema7 } from "json-schema";
|
|
3
4
|
import { Db } from "mongodb";
|
|
@@ -88,6 +89,10 @@ export interface FlinkOptions {
|
|
|
88
89
|
* Optional root folder of app. Defaults to `./`
|
|
89
90
|
*/
|
|
90
91
|
appRoot?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Options for json body parser
|
|
94
|
+
*/
|
|
95
|
+
jsonOptions?: OptionsJson;
|
|
91
96
|
}
|
|
92
97
|
export interface HandlerConfig {
|
|
93
98
|
schema?: {
|
|
@@ -124,6 +129,7 @@ export declare class FlinkApp<C extends FlinkContext> {
|
|
|
124
129
|
private auth?;
|
|
125
130
|
private corsOpts;
|
|
126
131
|
private routingConfigured;
|
|
132
|
+
private jsonOptions?;
|
|
127
133
|
private repos;
|
|
128
134
|
/**
|
|
129
135
|
* Internal cache used to track registered handlers and potentially any overlapping routes
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -99,6 +99,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
99
99
|
this.plugins = opts.plugins || [];
|
|
100
100
|
this.corsOpts = __assign(__assign({}, defaultCorsOptions), opts.cors);
|
|
101
101
|
this.auth = opts.auth;
|
|
102
|
+
this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
|
|
102
103
|
}
|
|
103
104
|
Object.defineProperty(FlinkApp.prototype, "ctx", {
|
|
104
105
|
get: function () {
|
|
@@ -140,7 +141,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
140
141
|
}
|
|
141
142
|
this.expressApp = express_1.default();
|
|
142
143
|
this.expressApp.use(cors_1.default(this.corsOpts));
|
|
143
|
-
this.expressApp.use(body_parser_1.default.json());
|
|
144
|
+
this.expressApp.use(body_parser_1.default.json(this.jsonOptions));
|
|
144
145
|
this.expressApp.use(function (req, res, next) {
|
|
145
146
|
req.reqId = uuid_1.v4();
|
|
146
147
|
next();
|
|
@@ -212,10 +213,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
212
213
|
node_color_log_1.default.error("Failed to register handler '" + handler.__file + "': Missing 'path' in route props");
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
|
-
var dup = this.handlers.find(function (h) {
|
|
216
|
-
return h.routeProps.path === routeProps.path &&
|
|
217
|
-
h.routeProps.method === routeProps.method;
|
|
218
|
-
});
|
|
216
|
+
var dup = this.handlers.find(function (h) { return h.routeProps.path === routeProps.path && h.routeProps.method === routeProps.method; });
|
|
219
217
|
var methodAndPath = routeProps.method.toUpperCase() + " " + routeProps.path;
|
|
220
218
|
if (dup) {
|
|
221
219
|
// TODO: Not sure if there is a case where you'd want to overwrite a route?
|
|
@@ -302,6 +300,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
302
300
|
case 5:
|
|
303
301
|
err_1 = _a.sent();
|
|
304
302
|
node_color_log_1.default.warn("Handler '" + methodAndRoute_1 + "' threw unhandled exception " + err_1);
|
|
303
|
+
console.error(err_1);
|
|
305
304
|
return [2 /*return*/, res.status(500).json(FlinkErrors_1.internalServerError(err_1))];
|
|
306
305
|
case 6:
|
|
307
306
|
if (schema.resSchema && !utils_1.isError(handlerRes)) {
|
|
@@ -374,6 +373,8 @@ var FlinkApp = /** @class */ (function () {
|
|
|
374
373
|
};
|
|
375
374
|
FlinkApp.prototype.addRepo = function (instanceName, repoInstance) {
|
|
376
375
|
this.repos[instanceName] = repoInstance;
|
|
376
|
+
// TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
|
|
377
|
+
// repoInstance.ctx = this.ctx;
|
|
377
378
|
};
|
|
378
379
|
/**
|
|
379
380
|
* Constructs the app context. Will inject context in all components
|
|
@@ -381,8 +382,8 @@ var FlinkApp = /** @class */ (function () {
|
|
|
381
382
|
*/
|
|
382
383
|
FlinkApp.prototype.buildContext = function () {
|
|
383
384
|
return __awaiter(this, void 0, void 0, function () {
|
|
384
|
-
var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx;
|
|
385
|
-
return __generator(this, function (
|
|
385
|
+
var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, _c, repo;
|
|
386
|
+
return __generator(this, function (_d) {
|
|
386
387
|
if (this.dbOpts) {
|
|
387
388
|
for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
|
|
388
389
|
_a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
|
|
@@ -406,6 +407,10 @@ var FlinkApp = /** @class */ (function () {
|
|
|
406
407
|
plugins: pluginCtx,
|
|
407
408
|
auth: this.auth,
|
|
408
409
|
};
|
|
410
|
+
for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
|
|
411
|
+
repo = _c[_b];
|
|
412
|
+
repo.ctx = this.ctx;
|
|
413
|
+
}
|
|
409
414
|
return [2 /*return*/];
|
|
410
415
|
});
|
|
411
416
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/flink",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"rimraf": "^3.0.2",
|
|
63
63
|
"ts-node": "^9.1.1"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "14e613ca807ffc6f2eac3bdd0e30aebee2778932"
|
|
66
66
|
}
|
package/readme.md
CHANGED
|
@@ -107,12 +107,12 @@ export default () => {};
|
|
|
107
107
|
|
|
108
108
|
The following building blocks exists in Flink:
|
|
109
109
|
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
110
|
+
- Handler - a handler is responsible for handling API requests and return a response. Normally a handler has some type of logic and invokes a _repo_ to CRUD data from database.
|
|
111
|
+
- Repo - a repository is used to abstract data access to database. A repo is used to access a mongo db and a repo is used per collection.
|
|
112
|
+
- App Context - the app context is the glue that ties parts of the app together. By defining and creating an app context you make sure that i.e. handlers can get access to repositories.
|
|
113
|
+
- Schemas - models that defines API requests and responses. These are typescript interfaces which will during compile time be converted into JSON schemas used to validate requests and responses and also used to generate API documentation.
|
|
114
|
+
- Flink app - is the entry
|
|
115
|
+
- Plugins - plugability is built into core of Flink. These can be external npm modules, or plugins inside your project. Plugins can for example extend the `request` object and add additional information such as auth user which can be used in handlers. Similar to how middleware works in express although a bit more constrained.
|
|
116
116
|
|
|
117
117
|
### Handlers
|
|
118
118
|
|
|
@@ -164,10 +164,10 @@ Then handler method must be of type `Handler` or `GetHandler`.
|
|
|
164
164
|
|
|
165
165
|
The handler function has generic type arguments which defines:
|
|
166
166
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
167
|
+
- Application context
|
|
168
|
+
- Request schema (optional)
|
|
169
|
+
- Response schema (optional)
|
|
170
|
+
- Params (optional)
|
|
171
171
|
|
|
172
172
|
> Note: `GetHandler<Ctx, ResSchema>` is just syntactic sugar since get handlers does not have request schemas so that type argument does not exist. Otherwise it is the same as `Handler<Ctx, ReqSchema, ResSchema>`
|
|
173
173
|
|
|
@@ -217,3 +217,7 @@ export const Props: RouteProps {
|
|
|
217
217
|
method: HttpMethod.get
|
|
218
218
|
}
|
|
219
219
|
```
|
|
220
|
+
|
|
221
|
+
## 🤕 Known issues
|
|
222
|
+
|
|
223
|
+
- Current TypeScript compiler is slow when project gets larger
|
package/src/FlinkApp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
|
-
import bodyParser from "body-parser";
|
|
3
|
+
import bodyParser, { OptionsJson } from "body-parser";
|
|
4
4
|
import cors from "cors";
|
|
5
5
|
import express, { Express, Request } from "express";
|
|
6
6
|
import { JSONSchema7 } from "json-schema";
|
|
@@ -10,13 +10,7 @@ import { v4 } from "uuid";
|
|
|
10
10
|
import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
|
|
11
11
|
import { FlinkContext } from "./FlinkContext";
|
|
12
12
|
import { internalServerError, notFound, unauthorized } from "./FlinkErrors";
|
|
13
|
-
import {
|
|
14
|
-
Handler,
|
|
15
|
-
HandlerFile,
|
|
16
|
-
HttpMethod,
|
|
17
|
-
QueryParamMetadata,
|
|
18
|
-
RouteProps,
|
|
19
|
-
} from "./FlinkHttpHandler";
|
|
13
|
+
import { Handler, HandlerFile, HttpMethod, QueryParamMetadata, RouteProps } from "./FlinkHttpHandler";
|
|
20
14
|
import { FlinkPlugin } from "./FlinkPlugin";
|
|
21
15
|
import { FlinkRepo } from "./FlinkRepo";
|
|
22
16
|
import { FlinkResponse } from "./FlinkResponse";
|
|
@@ -27,9 +21,9 @@ const ajv = new Ajv();
|
|
|
27
21
|
addFormats(ajv);
|
|
28
22
|
|
|
29
23
|
const defaultCorsOptions: FlinkOptions["cors"] = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
allowedHeaders: "",
|
|
25
|
+
credentials: true,
|
|
26
|
+
origin: [/.*/],
|
|
33
27
|
};
|
|
34
28
|
|
|
35
29
|
export type JSONSchema = JSONSchema7;
|
|
@@ -39,8 +33,8 @@ export type JSONSchema = JSONSchema7;
|
|
|
39
33
|
* are picked up by typescript compiler
|
|
40
34
|
*/
|
|
41
35
|
export const autoRegisteredHandlers: {
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
handler: HandlerFile;
|
|
37
|
+
assumedHttpMethod: HttpMethod;
|
|
44
38
|
}[] = [];
|
|
45
39
|
|
|
46
40
|
/**
|
|
@@ -48,586 +42,538 @@ export const autoRegisteredHandlers: {
|
|
|
48
42
|
* are picked up by typescript compiler
|
|
49
43
|
*/
|
|
50
44
|
export const autoRegisteredRepos: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
collectionName: string;
|
|
46
|
+
repoInstanceName: string;
|
|
47
|
+
Repo: any;
|
|
54
48
|
}[] = [];
|
|
55
49
|
|
|
56
50
|
export interface FlinkOptions {
|
|
57
|
-
/**
|
|
58
|
-
* Name of application, will only show in logs and in HTTP header.
|
|
59
|
-
*/
|
|
60
|
-
name: string;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* HTTP port
|
|
64
|
-
* @default 3333
|
|
65
|
-
*/
|
|
66
|
-
port?: number;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Configuration related to database.
|
|
70
|
-
* Leave empty if no database is needed.
|
|
71
|
-
*/
|
|
72
|
-
db?: {
|
|
73
51
|
/**
|
|
74
|
-
*
|
|
75
|
-
* @example mongodb://localhost:27017/my-db
|
|
52
|
+
* Name of application, will only show in logs and in HTTP header.
|
|
76
53
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
plugins?: FlinkPlugin[];
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Plugin used for authentication.
|
|
106
|
-
*/
|
|
107
|
-
auth?: FlinkAuthPlugin;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Optional cors options.
|
|
111
|
-
*/
|
|
112
|
-
cors?: {
|
|
54
|
+
name: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* HTTP port
|
|
58
|
+
* @default 3333
|
|
59
|
+
*/
|
|
60
|
+
port?: number;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Configuration related to database.
|
|
64
|
+
* Leave empty if no database is needed.
|
|
65
|
+
*/
|
|
66
|
+
db?: {
|
|
67
|
+
/**
|
|
68
|
+
* Uri to mongodb including any username and password.
|
|
69
|
+
* @example mongodb://localhost:27017/my-db
|
|
70
|
+
*/
|
|
71
|
+
uri: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional debug options, used to log and debug Flink internals.
|
|
76
|
+
*/
|
|
77
|
+
debug?: boolean;
|
|
78
|
+
|
|
113
79
|
/**
|
|
114
|
-
*
|
|
80
|
+
* Callback invoked after database was connected
|
|
81
|
+
* end before application starts.
|
|
82
|
+
*
|
|
83
|
+
* A good place to for example ensure database indexes.
|
|
115
84
|
*/
|
|
116
|
-
|
|
85
|
+
onDbConnection?: (db: Db) => Promise<void>;
|
|
117
86
|
|
|
118
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Callback invoked so Flink can load files from host project.
|
|
89
|
+
* @deprecated not needed anymore since new `flink run`
|
|
90
|
+
*/
|
|
91
|
+
loader?: (file: string) => Promise<any>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Optional list of plugins that should be configured and used.
|
|
95
|
+
*/
|
|
96
|
+
plugins?: FlinkPlugin[];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Plugin used for authentication.
|
|
100
|
+
*/
|
|
101
|
+
auth?: FlinkAuthPlugin;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Optional cors options.
|
|
105
|
+
*/
|
|
106
|
+
cors?: {
|
|
107
|
+
/**
|
|
108
|
+
* Origin(s) to allow
|
|
109
|
+
*/
|
|
110
|
+
origin?: RegExp[];
|
|
111
|
+
|
|
112
|
+
credentials?: boolean;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Specify allowed headers for CORS, can be a comma separated string if multiple
|
|
116
|
+
* Defaults to none.
|
|
117
|
+
*/
|
|
118
|
+
allowedHeaders?: string;
|
|
119
|
+
};
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
|
-
*
|
|
122
|
-
* Defaults to none.
|
|
122
|
+
* Optional root folder of app. Defaults to `./`
|
|
123
123
|
*/
|
|
124
|
-
|
|
125
|
-
};
|
|
124
|
+
appRoot?: string;
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Options for json body parser
|
|
129
|
+
*/
|
|
130
|
+
jsonOptions?: OptionsJson;
|
|
131
|
+
|
|
132
|
+
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
export interface HandlerConfig {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
schema?: {
|
|
137
|
+
reqSchema?: JSONSchema;
|
|
138
|
+
resSchema?: JSONSchema;
|
|
139
|
+
};
|
|
140
|
+
routeProps: RouteProps;
|
|
141
|
+
queryMetadata: QueryParamMetadata[];
|
|
142
|
+
paramsMetadata: QueryParamMetadata[];
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
export interface HandlerConfigWithMethod extends HandlerConfig {
|
|
144
|
-
|
|
146
|
+
routeProps: RouteProps & { method: HttpMethod };
|
|
145
147
|
}
|
|
146
|
-
export interface HandlerConfigWithSchemaRefs
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
};
|
|
148
|
+
export interface HandlerConfigWithSchemaRefs extends Omit<HandlerConfig, "schema" | "origin"> {
|
|
149
|
+
schema?: {
|
|
150
|
+
reqSchema?: string;
|
|
151
|
+
resSchema?: string;
|
|
152
|
+
};
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
export class FlinkApp<C extends FlinkContext> {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Internal cache used to track registered handlers and potentially any overlapping routes
|
|
176
|
-
*/
|
|
177
|
-
private handlerRouteCache = new Map<string, string>();
|
|
178
|
-
|
|
179
|
-
constructor(opts: FlinkOptions) {
|
|
180
|
-
this.name = opts.name;
|
|
181
|
-
this.port = opts.port || 3333;
|
|
182
|
-
this.dbOpts = opts.db;
|
|
183
|
-
this.debug = !!opts.debug;
|
|
184
|
-
this.onDbConnection = opts.onDbConnection;
|
|
185
|
-
this.plugins = opts.plugins || [];
|
|
186
|
-
this.corsOpts = { ...defaultCorsOptions, ...opts.cors };
|
|
187
|
-
this.auth = opts.auth;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
get ctx() {
|
|
191
|
-
if (!this._ctx) {
|
|
192
|
-
throw new Error("Context is not yet initialized");
|
|
193
|
-
}
|
|
194
|
-
return this._ctx;
|
|
195
|
-
}
|
|
156
|
+
public name: string;
|
|
157
|
+
public expressApp?: Express;
|
|
158
|
+
public db?: Db;
|
|
159
|
+
public handlers: HandlerConfig[] = [];
|
|
160
|
+
public port?: number;
|
|
161
|
+
public started = false;
|
|
162
|
+
|
|
163
|
+
private _ctx?: C;
|
|
164
|
+
private dbOpts?: FlinkOptions["db"];
|
|
165
|
+
private debug = false;
|
|
166
|
+
private onDbConnection?: FlinkOptions["onDbConnection"];
|
|
167
|
+
|
|
168
|
+
private plugins: FlinkPlugin[] = [];
|
|
169
|
+
private auth?: FlinkAuthPlugin;
|
|
170
|
+
private corsOpts: FlinkOptions["cors"];
|
|
171
|
+
private routingConfigured = false;
|
|
172
|
+
private jsonOptions? : OptionsJson;
|
|
173
|
+
|
|
174
|
+
private repos: { [x: string]: FlinkRepo<C> } = {};
|
|
196
175
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Internal cache used to track registered handlers and potentially any overlapping routes
|
|
178
|
+
*/
|
|
179
|
+
private handlerRouteCache = new Map<string, string>();
|
|
180
|
+
|
|
181
|
+
constructor(opts: FlinkOptions) {
|
|
182
|
+
this.name = opts.name;
|
|
183
|
+
this.port = opts.port || 3333;
|
|
184
|
+
this.dbOpts = opts.db;
|
|
185
|
+
this.debug = !!opts.debug;
|
|
186
|
+
this.onDbConnection = opts.onDbConnection;
|
|
187
|
+
this.plugins = opts.plugins || [];
|
|
188
|
+
this.corsOpts = { ...defaultCorsOptions, ...opts.cors };
|
|
189
|
+
this.auth = opts.auth;
|
|
190
|
+
this.jsonOptions = opts.jsonOptions || { limit : "1mb"}
|
|
206
191
|
}
|
|
207
192
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
`Build context took ${Date.now() - offsetTime} ms`
|
|
214
|
-
);
|
|
215
|
-
offsetTime = Date.now();
|
|
193
|
+
get ctx() {
|
|
194
|
+
if (!this._ctx) {
|
|
195
|
+
throw new Error("Context is not yet initialized");
|
|
196
|
+
}
|
|
197
|
+
return this._ctx;
|
|
216
198
|
}
|
|
217
199
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`Registered JSON schemas took ${Date.now() - offsetTime} ms`
|
|
222
|
-
);
|
|
223
|
-
offsetTime = Date.now();
|
|
224
|
-
}
|
|
200
|
+
async start() {
|
|
201
|
+
const startTime = Date.now();
|
|
202
|
+
let offsetTime = 0;
|
|
225
203
|
|
|
226
|
-
|
|
204
|
+
await this.initDb();
|
|
227
205
|
|
|
228
|
-
|
|
206
|
+
if (this.debug) {
|
|
207
|
+
offsetTime = Date.now();
|
|
208
|
+
log.bgColorLog("cyan", `Init db took ${offsetTime - startTime} ms`);
|
|
209
|
+
}
|
|
229
210
|
|
|
230
|
-
|
|
211
|
+
await this.buildContext();
|
|
231
212
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
213
|
+
if (this.debug) {
|
|
214
|
+
log.bgColorLog("cyan", `Build context took ${Date.now() - offsetTime} ms`);
|
|
215
|
+
offsetTime = Date.now();
|
|
216
|
+
}
|
|
236
217
|
|
|
237
|
-
|
|
218
|
+
if (this.debug) {
|
|
219
|
+
log.bgColorLog("cyan", `Registered JSON schemas took ${Date.now() - offsetTime} ms`);
|
|
220
|
+
offsetTime = Date.now();
|
|
221
|
+
}
|
|
238
222
|
|
|
239
|
-
|
|
240
|
-
let db;
|
|
223
|
+
this.expressApp = express();
|
|
241
224
|
|
|
242
|
-
|
|
243
|
-
db = await this.initPluginDb(plugin);
|
|
244
|
-
}
|
|
225
|
+
this.expressApp.use(cors(this.corsOpts));
|
|
245
226
|
|
|
246
|
-
|
|
247
|
-
await plugin.init(this, db);
|
|
248
|
-
}
|
|
227
|
+
this.expressApp.use(bodyParser.json(this.jsonOptions));
|
|
249
228
|
|
|
250
|
-
|
|
251
|
-
|
|
229
|
+
this.expressApp.use((req, res, next) => {
|
|
230
|
+
req.reqId = v4();
|
|
231
|
+
next();
|
|
232
|
+
});
|
|
252
233
|
|
|
253
|
-
|
|
234
|
+
// TODO: Add better more fine grained control when plugins are initialized, i.e. in what order
|
|
254
235
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"cyan",
|
|
258
|
-
`Register handlers took ${Date.now() - offsetTime} ms`
|
|
259
|
-
);
|
|
260
|
-
offsetTime = Date.now();
|
|
261
|
-
}
|
|
236
|
+
for (const plugin of this.plugins) {
|
|
237
|
+
let db;
|
|
262
238
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.expressApp!.use((req, res, next) => {
|
|
267
|
-
res.status(404).json(notFound());
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
this.routingConfigured = true;
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
this.expressApp?.listen(this.port, () => {
|
|
274
|
-
log.fontColorLog(
|
|
275
|
-
"magenta",
|
|
276
|
-
`⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
this.started = true;
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
return this;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Manually registers a handler.
|
|
287
|
-
*
|
|
288
|
-
* Typescript compiler will scan handler function and set schemas
|
|
289
|
-
* which are derived from handler function type arguments.
|
|
290
|
-
*/
|
|
291
|
-
public addHandler(
|
|
292
|
-
handler: HandlerFile,
|
|
293
|
-
routePropsOverride?: Partial<HandlerConfig["routeProps"]>
|
|
294
|
-
) {
|
|
295
|
-
if (this.routingConfigured) {
|
|
296
|
-
throw new Error(
|
|
297
|
-
"Cannot add handler after routes has been registered, make sure to invoke earlier"
|
|
298
|
-
);
|
|
299
|
-
}
|
|
239
|
+
if (plugin.db) {
|
|
240
|
+
db = await this.initPluginDb(plugin);
|
|
241
|
+
}
|
|
300
242
|
|
|
301
|
-
|
|
243
|
+
if (plugin.init) {
|
|
244
|
+
await plugin.init(this, db);
|
|
245
|
+
}
|
|
302
246
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
`Failed to register handler '${handler.__file}': Missing 'method' in route props, either set it or name handler file with HTTP method as prefix`
|
|
306
|
-
);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
247
|
+
log.info(`Initialized plugin '${plugin.id}'`);
|
|
248
|
+
}
|
|
309
249
|
|
|
310
|
-
|
|
311
|
-
log.error(
|
|
312
|
-
`Failed to register handler '${handler.__file}': Missing 'path' in route props`
|
|
313
|
-
);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
250
|
+
await this.registerAutoRegisterableHandlers();
|
|
316
251
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
252
|
+
if (this.debug) {
|
|
253
|
+
log.bgColorLog("cyan", `Register handlers took ${Date.now() - offsetTime} ms`);
|
|
254
|
+
offsetTime = Date.now();
|
|
255
|
+
}
|
|
322
256
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
257
|
+
// Register 404 with slight delay to allow all manually added routes to be added
|
|
258
|
+
// TODO: Is there a better solution to force this handler to always run last?
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
this.expressApp!.use((req, res, next) => {
|
|
261
|
+
res.status(404).json(notFound());
|
|
262
|
+
});
|
|
326
263
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
log.warn(`${methodAndPath} overlaps existing route`);
|
|
330
|
-
}
|
|
264
|
+
this.routingConfigured = true;
|
|
265
|
+
});
|
|
331
266
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
...routeProps,
|
|
335
|
-
method: routeProps.method!,
|
|
336
|
-
path: routeProps.path!,
|
|
337
|
-
},
|
|
338
|
-
schema: {
|
|
339
|
-
reqSchema: handler.__schemas?.reqSchema,
|
|
340
|
-
resSchema: handler.__schemas?.resSchema,
|
|
341
|
-
},
|
|
342
|
-
queryMetadata: handler.__query || [],
|
|
343
|
-
paramsMetadata: handler.__params || [],
|
|
344
|
-
};
|
|
267
|
+
this.expressApp?.listen(this.port, () => {
|
|
268
|
+
log.fontColorLog("magenta", `⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`);
|
|
345
269
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
`Expected request schema ${handler.__schemas.reqSchema} for handler ${methodAndPath} but no such schema was found`
|
|
349
|
-
);
|
|
350
|
-
}
|
|
270
|
+
this.started = true;
|
|
271
|
+
});
|
|
351
272
|
|
|
352
|
-
|
|
353
|
-
log.warn(
|
|
354
|
-
`Expected response schema ${handler.__schemas.resSchema} for handler ${methodAndPath} but no such schema was found`
|
|
355
|
-
);
|
|
273
|
+
return this;
|
|
356
274
|
}
|
|
357
275
|
|
|
358
|
-
|
|
359
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Manually registers a handler.
|
|
278
|
+
*
|
|
279
|
+
* Typescript compiler will scan handler function and set schemas
|
|
280
|
+
* which are derived from handler function type arguments.
|
|
281
|
+
*/
|
|
282
|
+
public addHandler(handler: HandlerFile, routePropsOverride?: Partial<HandlerConfig["routeProps"]>) {
|
|
283
|
+
if (this.routingConfigured) {
|
|
284
|
+
throw new Error("Cannot add handler after routes has been registered, make sure to invoke earlier");
|
|
285
|
+
}
|
|
360
286
|
|
|
361
|
-
|
|
362
|
-
this.handlers.push(handlerConfig);
|
|
287
|
+
const routeProps = { ...(handler.Route || {}), ...routePropsOverride };
|
|
363
288
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
289
|
+
if (!routeProps.method) {
|
|
290
|
+
log.error(
|
|
291
|
+
`Failed to register handler '${handler.__file}': Missing 'method' in route props, either set it or name handler file with HTTP method as prefix`
|
|
292
|
+
);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
367
295
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
296
|
+
if (!routeProps.path) {
|
|
297
|
+
log.error(`Failed to register handler '${handler.__file}': Missing 'path' in route props`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const dup = this.handlers.find((h) => h.routeProps.path === routeProps.path && h.routeProps.method === routeProps.method);
|
|
371
302
|
|
|
372
|
-
|
|
373
|
-
const methodAndRoute = `${method.toUpperCase()} ${routeProps.path}`;
|
|
303
|
+
const methodAndPath = `${routeProps.method.toUpperCase()} ${routeProps.path}`;
|
|
374
304
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return res.status(401).json(unauthorized());
|
|
379
|
-
}
|
|
305
|
+
if (dup) {
|
|
306
|
+
// TODO: Not sure if there is a case where you'd want to overwrite a route?
|
|
307
|
+
log.warn(`${methodAndPath} overlaps existing route`);
|
|
380
308
|
}
|
|
381
309
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
310
|
+
const handlerConfig: HandlerConfigWithMethod = {
|
|
311
|
+
routeProps: {
|
|
312
|
+
...routeProps,
|
|
313
|
+
method: routeProps.method!,
|
|
314
|
+
path: routeProps.path!,
|
|
315
|
+
},
|
|
316
|
+
schema: {
|
|
317
|
+
reqSchema: handler.__schemas?.reqSchema,
|
|
318
|
+
resSchema: handler.__schemas?.resSchema,
|
|
319
|
+
},
|
|
320
|
+
queryMetadata: handler.__query || [],
|
|
321
|
+
paramsMetadata: handler.__params || [],
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
if (handler.__schemas?.reqSchema && !handlerConfig.schema?.reqSchema) {
|
|
325
|
+
log.warn(`Expected request schema ${handler.__schemas.reqSchema} for handler ${methodAndPath} but no such schema was found`);
|
|
326
|
+
}
|
|
394
327
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return res.status(400).json({
|
|
398
|
-
status: 400,
|
|
399
|
-
error: {
|
|
400
|
-
id: v4(),
|
|
401
|
-
title: "Bad request",
|
|
402
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
403
|
-
validate.errors
|
|
404
|
-
)}`,
|
|
405
|
-
},
|
|
406
|
-
});
|
|
407
|
-
}
|
|
328
|
+
if (handler.__schemas?.resSchema && !handlerConfig.schema?.resSchema) {
|
|
329
|
+
log.warn(`Expected response schema ${handler.__schemas.resSchema} for handler ${methodAndPath} but no such schema was found`);
|
|
408
330
|
}
|
|
409
331
|
|
|
410
|
-
|
|
411
|
-
|
|
332
|
+
this.registerHandler(handlerConfig, handler.default);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private registerHandler(handlerConfig: HandlerConfig, handler: Handler<any>) {
|
|
336
|
+
this.handlers.push(handlerConfig);
|
|
412
337
|
|
|
413
|
-
|
|
338
|
+
const { routeProps, schema = {} } = handlerConfig;
|
|
339
|
+
const { method } = routeProps;
|
|
340
|
+
const app = this.expressApp!;
|
|
414
341
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
data,
|
|
418
|
-
});
|
|
419
|
-
return;
|
|
342
|
+
if (!method) {
|
|
343
|
+
log.error(`Route ${routeProps.path} is missing http method`);
|
|
420
344
|
}
|
|
421
345
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
346
|
+
if (method) {
|
|
347
|
+
const methodAndRoute = `${method.toUpperCase()} ${routeProps.path}`;
|
|
348
|
+
|
|
349
|
+
app[method](routeProps.path, async (req, res) => {
|
|
350
|
+
if (routeProps.permissions) {
|
|
351
|
+
if (!(await this.authenticate(req, routeProps.permissions))) {
|
|
352
|
+
return res.status(401).json(unauthorized());
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (schema.reqSchema) {
|
|
357
|
+
const validate = ajv.compile(schema.reqSchema);
|
|
358
|
+
const valid = validate(req.body);
|
|
359
|
+
|
|
360
|
+
if (!valid) {
|
|
361
|
+
log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validate.errors, null, 2)}`);
|
|
362
|
+
|
|
363
|
+
log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
|
|
364
|
+
|
|
365
|
+
return res.status(400).json({
|
|
366
|
+
status: 400,
|
|
367
|
+
error: {
|
|
368
|
+
id: v4(),
|
|
369
|
+
title: "Bad request",
|
|
370
|
+
detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (routeProps.mockApi && schema.resSchema) {
|
|
377
|
+
log.warn(`Mock response for ${req.method.toUpperCase()} ${req.path}`);
|
|
378
|
+
|
|
379
|
+
const data = generateMockData(schema.resSchema);
|
|
380
|
+
|
|
381
|
+
res.status(200).json({
|
|
382
|
+
status: 200,
|
|
383
|
+
data,
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let handlerRes: FlinkResponse<any>;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
// 👇 This is where the actual handler gets invoked
|
|
392
|
+
handlerRes = await handler({
|
|
393
|
+
req,
|
|
394
|
+
ctx: this.ctx,
|
|
395
|
+
origin: routeProps.origin,
|
|
396
|
+
});
|
|
397
|
+
} catch (err) {
|
|
398
|
+
log.warn(`Handler '${methodAndRoute}' threw unhandled exception ${err}`);
|
|
399
|
+
console.error(err);
|
|
400
|
+
return res.status(500).json(internalServerError(err as any));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (schema.resSchema && !isError(handlerRes)) {
|
|
404
|
+
const validate = ajv.compile(schema.resSchema);
|
|
405
|
+
const valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
406
|
+
|
|
407
|
+
if (!valid) {
|
|
408
|
+
log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validate.errors, null, 2)}`);
|
|
409
|
+
log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
|
|
410
|
+
// log.debug(JSON.stringify(schema, null, 2));
|
|
411
|
+
|
|
412
|
+
return res.status(500).json({
|
|
413
|
+
status: 500,
|
|
414
|
+
error: {
|
|
415
|
+
id: v4(),
|
|
416
|
+
title: "Bad response",
|
|
417
|
+
detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
res.set(handlerRes.headers);
|
|
424
|
+
|
|
425
|
+
res.status(handlerRes.status || 200).json(handlerRes);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (this.handlerRouteCache.has(methodAndRoute)) {
|
|
429
|
+
log.error(`Cannot register handler ${methodAndRoute} - route already registered`);
|
|
430
|
+
return process.exit(1); // TODO: Do we need to exit?
|
|
431
|
+
} else {
|
|
432
|
+
this.handlerRouteCache.set(methodAndRoute, JSON.stringify(routeProps));
|
|
433
|
+
log.info(`Registered route ${methodAndRoute}`);
|
|
434
|
+
}
|
|
436
435
|
}
|
|
436
|
+
}
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Register handlers found within the `/src/handlers`
|
|
440
|
+
* directory in Flink App.
|
|
441
|
+
*
|
|
442
|
+
* Will not register any handlers added programmatically.
|
|
443
|
+
*/
|
|
444
|
+
private async registerAutoRegisterableHandlers() {
|
|
445
|
+
for (const { handler, assumedHttpMethod } of autoRegisteredHandlers) {
|
|
446
|
+
if (!handler.Route) {
|
|
447
|
+
log.error(`Missing Props in handler ${handler.__file}`);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!handler.default) {
|
|
452
|
+
log.error(`Missing exported handler function in handler ${handler.__file}`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
this.registerHandler(
|
|
457
|
+
{
|
|
458
|
+
routeProps: {
|
|
459
|
+
...handler.Route,
|
|
460
|
+
method: handler.Route.method || assumedHttpMethod,
|
|
461
|
+
origin: this.name,
|
|
462
|
+
},
|
|
463
|
+
schema: {
|
|
464
|
+
reqSchema: handler.__schemas?.reqSchema,
|
|
465
|
+
resSchema: handler.__schemas?.resSchema,
|
|
466
|
+
},
|
|
467
|
+
queryMetadata: handler.__query || [],
|
|
468
|
+
paramsMetadata: handler.__params || [],
|
|
469
|
+
},
|
|
470
|
+
handler.default
|
|
449
471
|
);
|
|
450
|
-
log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
|
|
451
|
-
// log.debug(JSON.stringify(schema, null, 2));
|
|
452
|
-
|
|
453
|
-
return res.status(500).json({
|
|
454
|
-
status: 500,
|
|
455
|
-
error: {
|
|
456
|
-
id: v4(),
|
|
457
|
-
title: "Bad response",
|
|
458
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
459
|
-
validate.errors
|
|
460
|
-
)}`,
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
472
|
}
|
|
473
|
+
}
|
|
465
474
|
|
|
466
|
-
|
|
475
|
+
public addRepo(instanceName: string, repoInstance: FlinkRepo<C>) {
|
|
476
|
+
this.repos[instanceName] = repoInstance;
|
|
477
|
+
// TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
|
|
478
|
+
// repoInstance.ctx = this.ctx;
|
|
479
|
+
}
|
|
467
480
|
|
|
468
|
-
|
|
469
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Constructs the app context. Will inject context in all components
|
|
483
|
+
* except for handlers which are handled in later stage.
|
|
484
|
+
*/
|
|
485
|
+
private async buildContext() {
|
|
486
|
+
if (this.dbOpts) {
|
|
487
|
+
for (const { collectionName, repoInstanceName, Repo } of autoRegisteredRepos) {
|
|
488
|
+
const repoInstance: FlinkRepo<C> = new Repo(collectionName, this.db);
|
|
470
489
|
|
|
471
|
-
|
|
472
|
-
log.error(
|
|
473
|
-
`Cannot register handler ${methodAndRoute} - route already registered`
|
|
474
|
-
);
|
|
475
|
-
return process.exit(1); // TODO: Do we need to exit?
|
|
476
|
-
} else {
|
|
477
|
-
this.handlerRouteCache.set(methodAndRoute, JSON.stringify(routeProps));
|
|
478
|
-
log.info(`Registered route ${methodAndRoute}`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Register handlers found within the `/src/handlers`
|
|
485
|
-
* directory in Flink App.
|
|
486
|
-
*
|
|
487
|
-
* Will not register any handlers added programmatically.
|
|
488
|
-
*/
|
|
489
|
-
private async registerAutoRegisterableHandlers() {
|
|
490
|
-
for (const { handler, assumedHttpMethod } of autoRegisteredHandlers) {
|
|
491
|
-
if (!handler.Route) {
|
|
492
|
-
log.error(`Missing Props in handler ${handler.__file}`);
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (!handler.default) {
|
|
497
|
-
log.error(
|
|
498
|
-
`Missing exported handler function in handler ${handler.__file}`
|
|
499
|
-
);
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
this.registerHandler(
|
|
504
|
-
{
|
|
505
|
-
routeProps: {
|
|
506
|
-
...handler.Route,
|
|
507
|
-
method: handler.Route.method || assumedHttpMethod,
|
|
508
|
-
origin: this.name,
|
|
509
|
-
},
|
|
510
|
-
schema: {
|
|
511
|
-
reqSchema: handler.__schemas?.reqSchema,
|
|
512
|
-
resSchema: handler.__schemas?.resSchema,
|
|
513
|
-
},
|
|
514
|
-
queryMetadata: handler.__query || [],
|
|
515
|
-
paramsMetadata: handler.__params || [],
|
|
516
|
-
},
|
|
517
|
-
handler.default
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
public addRepo(instanceName: string, repoInstance: FlinkRepo<C>) {
|
|
523
|
-
this.repos[instanceName] = repoInstance;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Constructs the app context. Will inject context in all components
|
|
528
|
-
* except for handlers which are handled in later stage.
|
|
529
|
-
*/
|
|
530
|
-
private async buildContext() {
|
|
531
|
-
if (this.dbOpts) {
|
|
532
|
-
for (const {
|
|
533
|
-
collectionName,
|
|
534
|
-
repoInstanceName,
|
|
535
|
-
Repo,
|
|
536
|
-
} of autoRegisteredRepos) {
|
|
537
|
-
const repoInstance: FlinkRepo<C> = new Repo(collectionName, this.db);
|
|
538
|
-
|
|
539
|
-
this.repos[repoInstanceName] = repoInstance;
|
|
540
|
-
|
|
541
|
-
log.info(`Registered repo ${repoInstanceName}`);
|
|
542
|
-
}
|
|
543
|
-
} else if (autoRegisteredRepos.length > 0) {
|
|
544
|
-
log.warn(`No db configured but found repo(s)`);
|
|
545
|
-
}
|
|
490
|
+
this.repos[repoInstanceName] = repoInstance;
|
|
546
491
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
|
|
492
|
+
log.info(`Registered repo ${repoInstanceName}`);
|
|
493
|
+
}
|
|
494
|
+
} else if (autoRegisteredRepos.length > 0) {
|
|
495
|
+
log.warn(`No db configured but found repo(s)`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const pluginCtx = this.plugins.reduce<{ [x: string]: any }>((out, plugin) => {
|
|
499
|
+
if (out[plugin.id]) {
|
|
500
|
+
throw new Error(`Plugin ${plugin.id} is already registered`);
|
|
501
|
+
}
|
|
502
|
+
out[plugin.id] = plugin.ctx;
|
|
503
|
+
return out;
|
|
504
|
+
}, {});
|
|
505
|
+
|
|
506
|
+
this._ctx = {
|
|
507
|
+
repos: this.repos,
|
|
508
|
+
plugins: pluginCtx,
|
|
509
|
+
auth: this.auth,
|
|
510
|
+
} as C;
|
|
511
|
+
|
|
512
|
+
for (const repo of Object.values(this.repos)) {
|
|
513
|
+
repo.ctx = this.ctx;
|
|
551
514
|
}
|
|
552
|
-
out[plugin.id] = plugin.ctx;
|
|
553
|
-
return out;
|
|
554
|
-
},
|
|
555
|
-
{}
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
this._ctx = {
|
|
559
|
-
repos: this.repos,
|
|
560
|
-
plugins: pluginCtx,
|
|
561
|
-
auth: this.auth,
|
|
562
|
-
} as C;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Connects to database.
|
|
567
|
-
*/
|
|
568
|
-
private async initDb() {
|
|
569
|
-
if (this.dbOpts) {
|
|
570
|
-
try {
|
|
571
|
-
log.debug("Connecting to db");
|
|
572
|
-
const client = await mongodb.connect(this.dbOpts.uri, {
|
|
573
|
-
useUnifiedTopology: true,
|
|
574
|
-
connectTimeoutMS: 4000,
|
|
575
|
-
});
|
|
576
|
-
this.db = client.db();
|
|
577
|
-
} catch (err) {
|
|
578
|
-
log.error("Failed to connect to db: " + err);
|
|
579
|
-
process.exit(1);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (this.onDbConnection) {
|
|
583
|
-
await this.onDbConnection(this.db);
|
|
584
|
-
}
|
|
585
515
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Connects to database.
|
|
519
|
+
*/
|
|
520
|
+
private async initDb() {
|
|
521
|
+
if (this.dbOpts) {
|
|
522
|
+
try {
|
|
523
|
+
log.debug("Connecting to db");
|
|
524
|
+
const client = await mongodb.connect(this.dbOpts.uri, {
|
|
525
|
+
useUnifiedTopology: true,
|
|
526
|
+
connectTimeoutMS: 4000,
|
|
527
|
+
});
|
|
528
|
+
this.db = client.db();
|
|
529
|
+
} catch (err) {
|
|
530
|
+
log.error("Failed to connect to db: " + err);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (this.onDbConnection) {
|
|
535
|
+
await this.onDbConnection(this.db);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
594
538
|
}
|
|
595
539
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
} else {
|
|
603
|
-
return this.db;
|
|
540
|
+
/**
|
|
541
|
+
* Connects plugin to database.
|
|
542
|
+
*/
|
|
543
|
+
private async initPluginDb(plugin: FlinkPlugin) {
|
|
544
|
+
if (!plugin.db) {
|
|
545
|
+
return;
|
|
604
546
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
547
|
+
|
|
548
|
+
if (plugin.db) {
|
|
549
|
+
if (plugin.db.useHostDb) {
|
|
550
|
+
if (!this.db) {
|
|
551
|
+
log.error(`Plugin '${this.name} configured to use host app db, but no db exists in FlinkApp'`);
|
|
552
|
+
} else {
|
|
553
|
+
return this.db;
|
|
554
|
+
}
|
|
555
|
+
} else if (plugin.db.uri) {
|
|
556
|
+
try {
|
|
557
|
+
log.debug(`Connecting to '${plugin.id}' db`);
|
|
558
|
+
const client = await mongodb.connect(plugin.db.uri, {
|
|
559
|
+
useUnifiedTopology: true,
|
|
560
|
+
});
|
|
561
|
+
return client.db();
|
|
562
|
+
} catch (err) {
|
|
563
|
+
log.error(`Failed to connect to db defined in plugin '${plugin.id}': ` + err);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
616
566
|
}
|
|
617
|
-
}
|
|
618
567
|
}
|
|
619
|
-
}
|
|
620
568
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
569
|
+
private async authenticate(req: Request, permissions: string | string[]) {
|
|
570
|
+
if (!this.auth) {
|
|
571
|
+
throw new Error(`Attempting to authenticate request (${req.method} ${req.path}) but no authPlugin is set`);
|
|
572
|
+
}
|
|
573
|
+
return await this.auth.authenticateRequest(req, permissions);
|
|
626
574
|
}
|
|
627
|
-
return await this.auth.authenticateRequest(req, permissions);
|
|
628
|
-
}
|
|
629
575
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
576
|
+
public getRegisteredRoutes() {
|
|
577
|
+
return Array.from(this.handlerRouteCache.values());
|
|
578
|
+
}
|
|
633
579
|
}
|