@radatek/microserver 2.0.0 → 2.1.0
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/microserver.d.ts +52 -16
- package/dist/microserver.js +148 -82
- package/package.json +1 -7
- package/readme.md +50 -47
package/dist/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.1.0
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import http from 'http';
|
|
9
9
|
import net from 'net';
|
|
10
10
|
import { Readable } from 'stream';
|
|
11
|
+
import fs from 'fs';
|
|
11
12
|
import { EventEmitter } from 'events';
|
|
12
13
|
export declare class Warning extends Error {
|
|
13
14
|
constructor(text: string);
|
|
@@ -31,9 +32,16 @@ export declare class WebSocketError extends Error {
|
|
|
31
32
|
statusCode: number;
|
|
32
33
|
constructor(text?: string, code?: number);
|
|
33
34
|
}
|
|
35
|
+
export type Routes = () => {
|
|
36
|
+
[key: string]: Array<any>;
|
|
37
|
+
} | {
|
|
38
|
+
[key: string]: Array<any>;
|
|
39
|
+
};
|
|
34
40
|
export declare abstract class Plugin {
|
|
41
|
+
name?: string;
|
|
35
42
|
priority?: number;
|
|
36
43
|
handler?(req: ServerRequest, res: ServerResponse, next: Function): void;
|
|
44
|
+
routes?: () => Routes | Routes;
|
|
37
45
|
constructor(router: Router, ...args: any);
|
|
38
46
|
}
|
|
39
47
|
interface PluginClass {
|
|
@@ -57,8 +65,8 @@ export declare class ServerRequest extends http.IncomingMessage {
|
|
|
57
65
|
baseUrl: string;
|
|
58
66
|
/** Original url */
|
|
59
67
|
originalUrl?: string;
|
|
60
|
-
/**
|
|
61
|
-
|
|
68
|
+
/** Query parameters */
|
|
69
|
+
query: {
|
|
62
70
|
[key: string]: string;
|
|
63
71
|
};
|
|
64
72
|
/** Router named parameters */
|
|
@@ -109,17 +117,20 @@ export declare class ServerResponse extends http.ServerResponse {
|
|
|
109
117
|
headersOnly: boolean;
|
|
110
118
|
private constructor();
|
|
111
119
|
/** Send error reponse */
|
|
112
|
-
error(error: string | number | Error
|
|
120
|
+
error(error: string | number | Error): void;
|
|
113
121
|
/** Sets Content-Type acording to data and sends response */
|
|
114
122
|
send(data?: string | Buffer | Error | Readable | object): void;
|
|
115
123
|
/** Send json response */
|
|
116
124
|
json(data: any): void;
|
|
117
125
|
/** Send json response in form { success: false, error: err } */
|
|
118
|
-
jsonError(error: string | number | object | Error
|
|
126
|
+
jsonError(error: string | number | object | Error): void;
|
|
119
127
|
/** Send json response in form { success: true, ... } */
|
|
120
|
-
jsonSuccess(data?: object | string
|
|
128
|
+
jsonSuccess(data?: object | string): void;
|
|
121
129
|
/** Send redirect response to specified URL with optional status code (default: 302) */
|
|
122
130
|
redirect(code: number | string, url?: string): void;
|
|
131
|
+
/** Set status code */
|
|
132
|
+
status(code: number): this;
|
|
133
|
+
download(path: string, filename?: string): void;
|
|
123
134
|
}
|
|
124
135
|
/** WebSocket options */
|
|
125
136
|
export interface WebSocketOptions {
|
|
@@ -215,6 +226,9 @@ export interface Middleware {
|
|
|
215
226
|
export declare class Router extends EventEmitter {
|
|
216
227
|
server: MicroServer;
|
|
217
228
|
auth?: Auth;
|
|
229
|
+
plugins: {
|
|
230
|
+
[key: string]: Plugin;
|
|
231
|
+
};
|
|
218
232
|
/** @param {MicroServer} server */
|
|
219
233
|
constructor(server: MicroServer);
|
|
220
234
|
/** Handler */
|
|
@@ -272,7 +286,7 @@ export declare class Router extends EventEmitter {
|
|
|
272
286
|
* @param { {[key: string]: Array<any>} } routes list with subroutes: 'METHOD /suburl': [...middlewares]
|
|
273
287
|
* @return {Router} current router
|
|
274
288
|
*/
|
|
275
|
-
|
|
289
|
+
use(...args: any): Router;
|
|
276
290
|
/** Add hook */
|
|
277
291
|
hook(url: string, ...mid: Middleware[]): Router;
|
|
278
292
|
/** Check if middleware allready added */
|
|
@@ -353,7 +367,7 @@ export declare class MicroServer extends EventEmitter {
|
|
|
353
367
|
static plugins: {
|
|
354
368
|
[key: string]: PluginClass;
|
|
355
369
|
};
|
|
356
|
-
plugins: {
|
|
370
|
+
get plugins(): {
|
|
357
371
|
[key: string]: Plugin;
|
|
358
372
|
};
|
|
359
373
|
constructor(config: MicroServerConfig);
|
|
@@ -365,7 +379,7 @@ export declare class MicroServer extends EventEmitter {
|
|
|
365
379
|
listen(config?: ListenConfig): Promise<unknown>;
|
|
366
380
|
/** bind middleware or create one from string like: 'redirect:302,https://redirect.to', 'error:422', 'param:name=value', 'acl:users/get', 'model:User', 'group:Users', 'user:admin' */
|
|
367
381
|
bind(fn: string | Function | object): Function;
|
|
368
|
-
/** Add middleware, routes, etc.. see {
|
|
382
|
+
/** Add middleware, routes, etc.. see {router.use} */
|
|
369
383
|
use(...args: any): MicroServer;
|
|
370
384
|
/** Default server handler */
|
|
371
385
|
handler(req: ServerRequest, res: ServerResponse): void;
|
|
@@ -378,17 +392,19 @@ export declare class MicroServer extends EventEmitter {
|
|
|
378
392
|
handlerUpgrade(req: ServerRequest, socket: net.Socket, head: any): void;
|
|
379
393
|
/** Close server instance */
|
|
380
394
|
close(): Promise<void>;
|
|
381
|
-
/** Add route, alias to `server.router.
|
|
395
|
+
/** Add route, alias to `server.router.use(url, ...args)` */
|
|
396
|
+
all(url: string, ...args: any): MicroServer;
|
|
397
|
+
/** Add route, alias to `server.router.use('GET ' + url, ...args)` */
|
|
382
398
|
get(url: string, ...args: any): MicroServer;
|
|
383
|
-
/** Add route, alias to `server.router.
|
|
399
|
+
/** Add route, alias to `server.router.use('POST ' + url, ...args)` */
|
|
384
400
|
post(url: string, ...args: any): MicroServer;
|
|
385
|
-
/** Add route, alias to `server.router.
|
|
401
|
+
/** Add route, alias to `server.router.use('PUT ' + url, ...args)` */
|
|
386
402
|
put(url: string, ...args: any): MicroServer;
|
|
387
|
-
/** Add route, alias to `server.router.
|
|
403
|
+
/** Add route, alias to `server.router.use('PATCH ' + url, ...args)` */
|
|
388
404
|
patch(url: string, ...args: any): MicroServer;
|
|
389
|
-
/** Add route, alias to `server.router.
|
|
405
|
+
/** Add route, alias to `server.router.use('DELETE ' + url, ...args)` */
|
|
390
406
|
delete(url: string, ...args: any): MicroServer;
|
|
391
|
-
/** Add websocket handler, alias to `server.router.
|
|
407
|
+
/** Add websocket handler, alias to `server.router.use('WEBSOCKET ' + url, ...args)` */
|
|
392
408
|
websocket(url: string, ...args: any): MicroServer;
|
|
393
409
|
/** Add router hook, alias to `server.router.hook(url, ...args)` */
|
|
394
410
|
hook(url: string, ...args: any): MicroServer;
|
|
@@ -418,6 +434,26 @@ export interface StaticOptions {
|
|
|
418
434
|
/** Max file age in seconds */
|
|
419
435
|
maxAge?: number;
|
|
420
436
|
}
|
|
437
|
+
export interface ServeFileOptions {
|
|
438
|
+
/** path */
|
|
439
|
+
path: string;
|
|
440
|
+
/** root */
|
|
441
|
+
root?: string;
|
|
442
|
+
/** file name */
|
|
443
|
+
filename?: string;
|
|
444
|
+
/** file mime type */
|
|
445
|
+
mimeType?: string;
|
|
446
|
+
/** last modified date */
|
|
447
|
+
lastModified?: boolean;
|
|
448
|
+
/** etag */
|
|
449
|
+
etag?: boolean;
|
|
450
|
+
/** max age */
|
|
451
|
+
maxAge?: number;
|
|
452
|
+
/** range */
|
|
453
|
+
range?: boolean;
|
|
454
|
+
/** stat */
|
|
455
|
+
stats?: fs.Stats;
|
|
456
|
+
}
|
|
421
457
|
/** Proxy plugin options */
|
|
422
458
|
export interface ProxyPluginOptions {
|
|
423
459
|
/** Base path */
|
|
@@ -576,7 +612,7 @@ export declare class FileStore {
|
|
|
576
612
|
/** load json file data */
|
|
577
613
|
load(name: string, autosave?: boolean): Promise<any>;
|
|
578
614
|
/** save data */
|
|
579
|
-
save(name: string, data: any): Promise<
|
|
615
|
+
save(name: string, data: any): Promise<any>;
|
|
580
616
|
/** load all files in directory */
|
|
581
617
|
all(name: string, autosave?: boolean): Promise<{
|
|
582
618
|
[key: string]: any;
|
package/dist/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.1.0
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -12,7 +12,7 @@ import tls from 'tls';
|
|
|
12
12
|
import querystring from 'querystring';
|
|
13
13
|
import { Readable } from 'stream';
|
|
14
14
|
import fs from 'fs';
|
|
15
|
-
import path from 'path';
|
|
15
|
+
import path, { basename, extname } from 'path';
|
|
16
16
|
import crypto from 'crypto';
|
|
17
17
|
import zlib from 'zlib';
|
|
18
18
|
import { EventEmitter } from 'events';
|
|
@@ -92,8 +92,8 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
92
92
|
this.pathname = pathname;
|
|
93
93
|
this.path = pathname.slice(pathname.lastIndexOf('/'));
|
|
94
94
|
this.baseUrl = pathname.slice(0, pathname.length - this.path.length);
|
|
95
|
-
this.
|
|
96
|
-
parsedUrl.searchParams.forEach((v, k) => this.
|
|
95
|
+
this.query = {};
|
|
96
|
+
parsedUrl.searchParams.forEach((v, k) => this.query[k] = v);
|
|
97
97
|
}
|
|
98
98
|
/** Rewrite request url */
|
|
99
99
|
rewrite(url) {
|
|
@@ -313,8 +313,9 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
313
313
|
this.statusCode = 200;
|
|
314
314
|
}
|
|
315
315
|
/** Send error reponse */
|
|
316
|
-
error(error
|
|
316
|
+
error(error) {
|
|
317
317
|
let code = 0;
|
|
318
|
+
let text;
|
|
318
319
|
if (error instanceof Error) {
|
|
319
320
|
if ('statusCode' in error)
|
|
320
321
|
code = error.statusCode;
|
|
@@ -322,7 +323,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
322
323
|
}
|
|
323
324
|
else if (typeof error === 'number') {
|
|
324
325
|
code = error;
|
|
325
|
-
text =
|
|
326
|
+
text = commonCodes[code] || 'Error';
|
|
326
327
|
}
|
|
327
328
|
else
|
|
328
329
|
text = error.toString();
|
|
@@ -399,19 +400,17 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
399
400
|
this.send(data);
|
|
400
401
|
}
|
|
401
402
|
/** Send json response in form { success: false, error: err } */
|
|
402
|
-
jsonError(error
|
|
403
|
+
jsonError(error) {
|
|
403
404
|
this.isJson = true;
|
|
404
|
-
this.statusCode = code || 200;
|
|
405
405
|
if (typeof error === 'number')
|
|
406
|
-
|
|
406
|
+
error = http.STATUS_CODES[error] || 'Error';
|
|
407
407
|
if (error instanceof Error)
|
|
408
408
|
return this.json(error);
|
|
409
409
|
this.json(typeof error === 'string' ? { success: false, error } : { success: false, ...error });
|
|
410
410
|
}
|
|
411
411
|
/** Send json response in form { success: true, ... } */
|
|
412
|
-
jsonSuccess(data
|
|
412
|
+
jsonSuccess(data) {
|
|
413
413
|
this.isJson = true;
|
|
414
|
-
this.statusCode = code || 200;
|
|
415
414
|
if (data instanceof Error)
|
|
416
415
|
return this.json(data);
|
|
417
416
|
this.json(typeof data === 'string' ? { success: true, message: data } : { success: true, ...data });
|
|
@@ -427,6 +426,18 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
427
426
|
this.statusCode = code || 302;
|
|
428
427
|
this.end();
|
|
429
428
|
}
|
|
429
|
+
/** Set status code */
|
|
430
|
+
status(code) {
|
|
431
|
+
this.statusCode = code;
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
download(path, filename) {
|
|
435
|
+
StaticPlugin.serveFile(this.req, this, {
|
|
436
|
+
path: path,
|
|
437
|
+
filename: filename || basename(path),
|
|
438
|
+
mimeType: StaticPlugin.mimeTypes[extname(path)] || 'application/octet-stream'
|
|
439
|
+
});
|
|
440
|
+
}
|
|
430
441
|
}
|
|
431
442
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
432
443
|
const DEFLATE_TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
|
@@ -897,6 +908,7 @@ export class Router extends EventEmitter {
|
|
|
897
908
|
/** @param {MicroServer} server */
|
|
898
909
|
constructor(server) {
|
|
899
910
|
super();
|
|
911
|
+
this.plugins = {};
|
|
900
912
|
this._stack = [];
|
|
901
913
|
this._stackAfter = [];
|
|
902
914
|
this._tree = {};
|
|
@@ -1096,32 +1108,26 @@ export class Router extends EventEmitter {
|
|
|
1096
1108
|
* @param { {[key: string]: Array<any>} } routes list with subroutes: 'METHOD /suburl': [...middlewares]
|
|
1097
1109
|
* @return {Router} current router
|
|
1098
1110
|
*/
|
|
1099
|
-
|
|
1111
|
+
use(...args) {
|
|
1100
1112
|
if (!args[0])
|
|
1101
1113
|
return this;
|
|
1102
|
-
//
|
|
1114
|
+
// use(plugin)
|
|
1103
1115
|
if (args[0] instanceof Plugin)
|
|
1104
1116
|
return this._plugin(args[0]);
|
|
1105
|
-
//
|
|
1117
|
+
// use(pluginid, ...args)
|
|
1106
1118
|
if (typeof args[0] === 'string' && MicroServer.plugins[args[0]]) {
|
|
1107
1119
|
const constructor = MicroServer.plugins[args[0]];
|
|
1108
1120
|
const plugin = new constructor(this, ...args.slice(1));
|
|
1109
1121
|
return this._plugin(plugin);
|
|
1110
1122
|
}
|
|
1123
|
+
// use(PluginClass, ...args)
|
|
1111
1124
|
if (args[0].prototype instanceof Plugin) {
|
|
1112
1125
|
const plugin = new args[0](this, ...args.slice(1));
|
|
1113
1126
|
return this._plugin(plugin);
|
|
1114
1127
|
}
|
|
1128
|
+
// use(middleware)
|
|
1115
1129
|
if (typeof args[0] === 'function') {
|
|
1116
|
-
|
|
1117
|
-
constructor() {
|
|
1118
|
-
super(...arguments);
|
|
1119
|
-
this.priority = args[0].priority;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
const middleware = new Middleware(this);
|
|
1123
|
-
middleware.handler = args[0];
|
|
1124
|
-
return this._plugin(middleware);
|
|
1130
|
+
return this._middleware(args[0]);
|
|
1125
1131
|
}
|
|
1126
1132
|
let method = '*', url = '/';
|
|
1127
1133
|
if (typeof args[0] === 'string') {
|
|
@@ -1134,13 +1140,13 @@ export class Router extends EventEmitter {
|
|
|
1134
1140
|
throw new Error(`Invalid url ${url}`);
|
|
1135
1141
|
args = args.slice(1);
|
|
1136
1142
|
}
|
|
1137
|
-
//
|
|
1143
|
+
// use('/url', ControllerClass)
|
|
1138
1144
|
if (typeof args[0] === 'function' && args[0].prototype instanceof Controller) {
|
|
1139
1145
|
const routes = args[0].routes();
|
|
1140
1146
|
if (routes)
|
|
1141
1147
|
args[0] = routes;
|
|
1142
1148
|
}
|
|
1143
|
-
//
|
|
1149
|
+
// use('/url', [ ['METHOD /url', ...], {'METHOD } ])
|
|
1144
1150
|
if (Array.isArray(args[0])) {
|
|
1145
1151
|
if (method !== '*')
|
|
1146
1152
|
throw new Error('Invalid router usage');
|
|
@@ -1149,25 +1155,25 @@ export class Router extends EventEmitter {
|
|
|
1149
1155
|
// [methodUrl, ...middlewares]
|
|
1150
1156
|
if (typeof item[0] !== 'string' || !item[0].match(/^(\w+ )?\//))
|
|
1151
1157
|
throw new Error('Url expected');
|
|
1152
|
-
return this.
|
|
1158
|
+
return this.use(item[0].replace(/\//, (url === '/' ? '' : url) + '/'), ...item.slice(1));
|
|
1153
1159
|
}
|
|
1154
1160
|
else
|
|
1155
1161
|
throw new Error('Invalid param');
|
|
1156
1162
|
});
|
|
1157
1163
|
return this;
|
|
1158
1164
|
}
|
|
1159
|
-
//
|
|
1165
|
+
// use('/url', {'METHOD /url': [...middlewares], ... } ])
|
|
1160
1166
|
if (typeof args[0] === 'object' && args[0].constructor === Object) {
|
|
1161
1167
|
if (method !== '*')
|
|
1162
1168
|
throw new Error('Invalid router usage');
|
|
1163
1169
|
for (const [subUrl, subArgs] of Object.entries(args[0])) {
|
|
1164
1170
|
if (!subUrl.match(/^(\w+ )?\//))
|
|
1165
1171
|
throw new Error('Url expected');
|
|
1166
|
-
this.
|
|
1172
|
+
this.use(subUrl.replace(/\//, (url === '/' ? '' : url) + '/'), ...(Array.isArray(subArgs) ? subArgs : [subArgs]));
|
|
1167
1173
|
}
|
|
1168
1174
|
return this;
|
|
1169
1175
|
}
|
|
1170
|
-
//
|
|
1176
|
+
// use('/url', ...middleware)
|
|
1171
1177
|
return this._add(method, url, 'next', args.filter((o) => o));
|
|
1172
1178
|
}
|
|
1173
1179
|
_middleware(middleware) {
|
|
@@ -1176,17 +1182,28 @@ export class Router extends EventEmitter {
|
|
|
1176
1182
|
const priority = (middleware?.priority || 0) - 1;
|
|
1177
1183
|
const stack = priority < -1 ? this._stackAfter : this._stack;
|
|
1178
1184
|
const idx = stack.findIndex(f => 'priority' in f
|
|
1179
|
-
&& priority
|
|
1185
|
+
&& priority >= (f.priority || 0));
|
|
1180
1186
|
stack.splice(idx < 0 ? stack.length : idx, 0, middleware);
|
|
1181
1187
|
return this;
|
|
1182
1188
|
}
|
|
1183
1189
|
_plugin(plugin) {
|
|
1190
|
+
if (plugin.name) {
|
|
1191
|
+
if (this.plugins[plugin.name])
|
|
1192
|
+
throw new Error(`Plugin ${plugin.name} already added`);
|
|
1193
|
+
this.plugins[plugin.name] = plugin;
|
|
1194
|
+
}
|
|
1184
1195
|
if (plugin.handler) {
|
|
1185
1196
|
const middleware = plugin.handler.bind(plugin);
|
|
1186
1197
|
middleware.plugin = plugin;
|
|
1187
1198
|
middleware.priority = plugin.priority;
|
|
1188
1199
|
return this._middleware(middleware);
|
|
1189
1200
|
}
|
|
1201
|
+
if (plugin.routes) {
|
|
1202
|
+
if (typeof plugin.routes === 'function')
|
|
1203
|
+
this.use(plugin.routes());
|
|
1204
|
+
else
|
|
1205
|
+
this.use(plugin.routes);
|
|
1206
|
+
}
|
|
1190
1207
|
return this;
|
|
1191
1208
|
}
|
|
1192
1209
|
/** Add hook */
|
|
@@ -1203,10 +1220,10 @@ export class Router extends EventEmitter {
|
|
|
1203
1220
|
}
|
|
1204
1221
|
}
|
|
1205
1222
|
export class MicroServer extends EventEmitter {
|
|
1223
|
+
get plugins() { return this.router.plugins; }
|
|
1206
1224
|
constructor(config) {
|
|
1207
1225
|
super();
|
|
1208
1226
|
this._ready = false;
|
|
1209
|
-
this.plugins = {};
|
|
1210
1227
|
this._methods = {};
|
|
1211
1228
|
let promise = Promise.resolve();
|
|
1212
1229
|
this._init = (f, ...args) => {
|
|
@@ -1226,7 +1243,7 @@ export class MicroServer extends EventEmitter {
|
|
|
1226
1243
|
this.use(config.routes);
|
|
1227
1244
|
for (const key in MicroServer.plugins) {
|
|
1228
1245
|
if (config[key])
|
|
1229
|
-
this.router.
|
|
1246
|
+
this.router.use(MicroServer.plugins[key], config[key]);
|
|
1230
1247
|
}
|
|
1231
1248
|
if (config.listen)
|
|
1232
1249
|
this._init(() => {
|
|
@@ -1430,9 +1447,9 @@ export class MicroServer extends EventEmitter {
|
|
|
1430
1447
|
throw new Error('Invalid middleware: ' + String.toString.call(fn));
|
|
1431
1448
|
return fn.bind(this);
|
|
1432
1449
|
}
|
|
1433
|
-
/** Add middleware, routes, etc.. see {
|
|
1450
|
+
/** Add middleware, routes, etc.. see {router.use} */
|
|
1434
1451
|
use(...args) {
|
|
1435
|
-
this.router.
|
|
1452
|
+
this.router.use(...args);
|
|
1436
1453
|
return this;
|
|
1437
1454
|
}
|
|
1438
1455
|
/** Default server handler */
|
|
@@ -1579,34 +1596,39 @@ export class MicroServer extends EventEmitter {
|
|
|
1579
1596
|
this.emit('close');
|
|
1580
1597
|
});
|
|
1581
1598
|
}
|
|
1582
|
-
/** Add route, alias to `server.router.
|
|
1599
|
+
/** Add route, alias to `server.router.use(url, ...args)` */
|
|
1600
|
+
all(url, ...args) {
|
|
1601
|
+
this.router.use(url, ...args);
|
|
1602
|
+
return this;
|
|
1603
|
+
}
|
|
1604
|
+
/** Add route, alias to `server.router.use('GET ' + url, ...args)` */
|
|
1583
1605
|
get(url, ...args) {
|
|
1584
|
-
this.router.
|
|
1606
|
+
this.router.use('GET ' + url, ...args);
|
|
1585
1607
|
return this;
|
|
1586
1608
|
}
|
|
1587
|
-
/** Add route, alias to `server.router.
|
|
1609
|
+
/** Add route, alias to `server.router.use('POST ' + url, ...args)` */
|
|
1588
1610
|
post(url, ...args) {
|
|
1589
|
-
this.router.
|
|
1611
|
+
this.router.use('POST ' + url, ...args);
|
|
1590
1612
|
return this;
|
|
1591
1613
|
}
|
|
1592
|
-
/** Add route, alias to `server.router.
|
|
1614
|
+
/** Add route, alias to `server.router.use('PUT ' + url, ...args)` */
|
|
1593
1615
|
put(url, ...args) {
|
|
1594
|
-
this.router.
|
|
1616
|
+
this.router.use('PUT ' + url, ...args);
|
|
1595
1617
|
return this;
|
|
1596
1618
|
}
|
|
1597
|
-
/** Add route, alias to `server.router.
|
|
1619
|
+
/** Add route, alias to `server.router.use('PATCH ' + url, ...args)` */
|
|
1598
1620
|
patch(url, ...args) {
|
|
1599
|
-
this.router.
|
|
1621
|
+
this.router.use('PATCH ' + url, ...args);
|
|
1600
1622
|
return this;
|
|
1601
1623
|
}
|
|
1602
|
-
/** Add route, alias to `server.router.
|
|
1624
|
+
/** Add route, alias to `server.router.use('DELETE ' + url, ...args)` */
|
|
1603
1625
|
delete(url, ...args) {
|
|
1604
|
-
this.router.
|
|
1626
|
+
this.router.use('DELETE ' + url, ...args);
|
|
1605
1627
|
return this;
|
|
1606
1628
|
}
|
|
1607
|
-
/** Add websocket handler, alias to `server.router.
|
|
1629
|
+
/** Add websocket handler, alias to `server.router.use('WEBSOCKET ' + url, ...args)` */
|
|
1608
1630
|
websocket(url, ...args) {
|
|
1609
|
-
this.router.
|
|
1631
|
+
this.router.use('WEBSOCKET ' + url, ...args);
|
|
1610
1632
|
return this;
|
|
1611
1633
|
}
|
|
1612
1634
|
/** Add router hook, alias to `server.router.hook(url, ...args)` */
|
|
@@ -1621,6 +1643,7 @@ class TrustProxyPlugin extends Plugin {
|
|
|
1621
1643
|
constructor(router, options) {
|
|
1622
1644
|
super(router);
|
|
1623
1645
|
this.priority = 110;
|
|
1646
|
+
this.name = 'trustProxy';
|
|
1624
1647
|
this.trustProxy = [];
|
|
1625
1648
|
this.trustProxy = options || [];
|
|
1626
1649
|
}
|
|
@@ -1651,14 +1674,16 @@ class VHostPlugin extends Plugin {
|
|
|
1651
1674
|
super(router);
|
|
1652
1675
|
this.priority = 100;
|
|
1653
1676
|
const server = router.server;
|
|
1654
|
-
if (!server.vhosts)
|
|
1677
|
+
if (!server.vhosts) {
|
|
1655
1678
|
server.vhosts = {};
|
|
1679
|
+
this.name = 'vhost';
|
|
1680
|
+
}
|
|
1656
1681
|
else
|
|
1657
1682
|
this.handler = undefined;
|
|
1658
1683
|
for (const host in options) {
|
|
1659
1684
|
if (!server.vhosts[host])
|
|
1660
1685
|
server.vhosts[host] = new Router(server);
|
|
1661
|
-
server.vhosts[host].
|
|
1686
|
+
server.vhosts[host].use(options[host]);
|
|
1662
1687
|
}
|
|
1663
1688
|
}
|
|
1664
1689
|
handler(req, res, next) {
|
|
@@ -1686,7 +1711,7 @@ class StaticPlugin extends Plugin {
|
|
|
1686
1711
|
options = {};
|
|
1687
1712
|
if (typeof options === 'string')
|
|
1688
1713
|
options = { path: options };
|
|
1689
|
-
this.mimeTypes = { ...StaticPlugin.mimeTypes, ...options.mimeTypes };
|
|
1714
|
+
this.mimeTypes = options.mimeTypes ? { ...StaticPlugin.mimeTypes, ...options.mimeTypes } : Object.freeze(StaticPlugin.mimeTypes);
|
|
1690
1715
|
this.root = path.resolve((options.root || options?.path || 'public').replace(/^\//, '')) + path.sep;
|
|
1691
1716
|
this.ignore = (options.ignore || []).map((p) => path.normalize(path.join(this.root, p)) + path.sep);
|
|
1692
1717
|
this.index = options.index || 'index.html';
|
|
@@ -1694,7 +1719,7 @@ class StaticPlugin extends Plugin {
|
|
|
1694
1719
|
this.lastModified = options.lastModified !== false;
|
|
1695
1720
|
this.etag = options.etag !== false;
|
|
1696
1721
|
this.maxAge = options.maxAge;
|
|
1697
|
-
router.
|
|
1722
|
+
router.use('GET /' + (options.path?.replace(/^[.\/]*/, '') || '').replace(/\/$/, '') + '/:path*', this.staticHandler.bind(this));
|
|
1698
1723
|
}
|
|
1699
1724
|
/** Default static files handler */
|
|
1700
1725
|
staticHandler(req, res, next) {
|
|
@@ -1703,7 +1728,7 @@ class StaticPlugin extends Plugin {
|
|
|
1703
1728
|
let filename = path.normalize(path.join(this.root, (req.params && req.params.path) || req.pathname));
|
|
1704
1729
|
if (!filename.startsWith(this.root)) // check root access
|
|
1705
1730
|
return next();
|
|
1706
|
-
const firstch =
|
|
1731
|
+
const firstch = basename(filename)[0];
|
|
1707
1732
|
if (firstch === '.' || firstch === '_') // hidden file
|
|
1708
1733
|
return next();
|
|
1709
1734
|
if (filename.endsWith(path.sep))
|
|
@@ -1725,27 +1750,61 @@ class StaticPlugin extends Plugin {
|
|
|
1725
1750
|
req.filename = filename;
|
|
1726
1751
|
return handler.call(this, req, res, next);
|
|
1727
1752
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1753
|
+
StaticPlugin.serveFile(req, res, {
|
|
1754
|
+
path: filename,
|
|
1755
|
+
mimeType,
|
|
1756
|
+
stats
|
|
1757
|
+
});
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
static serveFile(req, res, options) {
|
|
1761
|
+
const filePath = options.root ? path.join(options.root, options.path) : options.path;
|
|
1762
|
+
const statRes = (err, stats) => {
|
|
1763
|
+
if (err)
|
|
1764
|
+
return res.error(err);
|
|
1765
|
+
if (!stats.isFile())
|
|
1766
|
+
return res.error(404);
|
|
1767
|
+
if (!res.getHeader('Content-Type')) {
|
|
1768
|
+
if (options.mimeType)
|
|
1769
|
+
res.setHeader('Content-Type', options.mimeType);
|
|
1770
|
+
else
|
|
1771
|
+
res.setHeader('Content-Type', this.mimeTypes[path.extname(options.path)] || 'application/octet-stream');
|
|
1772
|
+
}
|
|
1773
|
+
if (options.filename)
|
|
1774
|
+
res.setHeader('Content-Disposition', 'attachment; filename="' + options.filename + '"');
|
|
1775
|
+
if (options.lastModified !== false)
|
|
1733
1776
|
res.setHeader('Last-Modified', stats.mtime.toUTCString());
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1777
|
+
res.setHeader('Content-Length', stats.size);
|
|
1778
|
+
if (options.etag !== false) {
|
|
1779
|
+
const etag = '"' + etagPrefix + stats.mtime.getTime().toString(32) + '"';
|
|
1780
|
+
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === stats.mtime.toUTCString()) {
|
|
1781
|
+
res.statusCode = 304;
|
|
1782
|
+
res.headersOnly = true;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (options.maxAge)
|
|
1786
|
+
res.setHeader('Cache-Control', 'max-age=' + options.maxAge);
|
|
1738
1787
|
if (res.headersOnly) {
|
|
1739
|
-
res.
|
|
1740
|
-
return
|
|
1788
|
+
res.end();
|
|
1789
|
+
return;
|
|
1741
1790
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1791
|
+
const streamOptions = { start: 0, end: stats.size - 1 };
|
|
1792
|
+
if (options.range !== false) {
|
|
1793
|
+
const range = req.headers['range'];
|
|
1794
|
+
if (range && range.startsWith('bytes=')) {
|
|
1795
|
+
const parts = range.slice(6).split('-');
|
|
1796
|
+
streamOptions.start = parseInt(parts[0]) || 0;
|
|
1797
|
+
streamOptions.end = parts[1] ? parseInt(parts[1]) : stats.size - 1;
|
|
1798
|
+
res.setHeader('Content-Range', `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
|
|
1799
|
+
res.setHeader('Content-Length', streamOptions.end - streamOptions.start + 1);
|
|
1800
|
+
}
|
|
1745
1801
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1802
|
+
fs.createReadStream(filePath, streamOptions).pipe(res);
|
|
1803
|
+
};
|
|
1804
|
+
if (!options.stats)
|
|
1805
|
+
fs.stat(filePath, statRes);
|
|
1806
|
+
else
|
|
1807
|
+
statRes(null, options.stats);
|
|
1749
1808
|
}
|
|
1750
1809
|
}
|
|
1751
1810
|
/** Default mime types */
|
|
@@ -1759,12 +1818,17 @@ StaticPlugin.mimeTypes = {
|
|
|
1759
1818
|
'.css': 'text/css',
|
|
1760
1819
|
'.png': 'image/png',
|
|
1761
1820
|
'.jpg': 'image/jpeg',
|
|
1762
|
-
'.mp3': 'audio/mpeg',
|
|
1763
1821
|
'.svg': 'image/svg+xml',
|
|
1822
|
+
'.mp3': 'audio/mpeg',
|
|
1823
|
+
'.ogg': 'audio/ogg',
|
|
1824
|
+
'.mp4': 'video/mp4',
|
|
1764
1825
|
'.pdf': 'application/pdf',
|
|
1765
1826
|
'.woff': 'application/x-font-woff',
|
|
1766
1827
|
'.woff2': 'application/x-font-woff2',
|
|
1767
|
-
'.ttf': 'application/x-font-ttf'
|
|
1828
|
+
'.ttf': 'application/x-font-ttf',
|
|
1829
|
+
'.gz': 'application/gzip',
|
|
1830
|
+
'.zip': 'application/zip',
|
|
1831
|
+
'.tgz': 'application/gzip',
|
|
1768
1832
|
};
|
|
1769
1833
|
MicroServer.plugins.static = StaticPlugin;
|
|
1770
1834
|
export class ProxyPlugin extends Plugin {
|
|
@@ -1780,7 +1844,7 @@ export class ProxyPlugin extends Plugin {
|
|
|
1780
1844
|
this.validHeaders = { ...ProxyPlugin.validHeaders, ...options?.validHeaders };
|
|
1781
1845
|
if (options.path && options.path !== '/') {
|
|
1782
1846
|
this.handler = undefined;
|
|
1783
|
-
router.
|
|
1847
|
+
router.use(options.path + '/:path*', this.proxyHandler.bind(this));
|
|
1784
1848
|
}
|
|
1785
1849
|
}
|
|
1786
1850
|
/** Default proxy handler */
|
|
@@ -1816,8 +1880,7 @@ export class ProxyPlugin extends Plugin {
|
|
|
1816
1880
|
}
|
|
1817
1881
|
if (this.headers)
|
|
1818
1882
|
Object.assign(reqOptions.headers, this.headers);
|
|
1819
|
-
|
|
1820
|
-
reqOptions.headers.Host = reqOptions.host;
|
|
1883
|
+
reqOptions.setHost = true;
|
|
1821
1884
|
const conn = this.remoteUrl.protocol === 'https:' ? https.request(reqOptions) : http.request(reqOptions);
|
|
1822
1885
|
conn.on('response', (response) => {
|
|
1823
1886
|
res.statusCode = response.statusCode || 502;
|
|
@@ -2144,6 +2207,7 @@ async function login (username, password, salt) {
|
|
|
2144
2207
|
class AuthPlugin extends Plugin {
|
|
2145
2208
|
constructor(router, options) {
|
|
2146
2209
|
super(router);
|
|
2210
|
+
this.name = 'auth';
|
|
2147
2211
|
if (router.auth)
|
|
2148
2212
|
throw new Error('Auth plugin already initialized');
|
|
2149
2213
|
this.options = {
|
|
@@ -2190,7 +2254,7 @@ class AuthPlugin extends Plugin {
|
|
|
2190
2254
|
if (sid)
|
|
2191
2255
|
token = sid.slice(sid.indexOf('=') + 1);
|
|
2192
2256
|
if (!token)
|
|
2193
|
-
token = req.
|
|
2257
|
+
token = req.query.token;
|
|
2194
2258
|
if (token) {
|
|
2195
2259
|
const now = new Date().getTime();
|
|
2196
2260
|
let usr, expire;
|
|
@@ -2331,7 +2395,7 @@ export class FileStore {
|
|
|
2331
2395
|
data: data
|
|
2332
2396
|
};
|
|
2333
2397
|
this._cache[name] = item;
|
|
2334
|
-
|
|
2398
|
+
this._sync(async () => {
|
|
2335
2399
|
if (this._cache[name] === item) {
|
|
2336
2400
|
this.cleanup();
|
|
2337
2401
|
try {
|
|
@@ -2341,6 +2405,7 @@ export class FileStore {
|
|
|
2341
2405
|
}
|
|
2342
2406
|
}
|
|
2343
2407
|
});
|
|
2408
|
+
return data;
|
|
2344
2409
|
}
|
|
2345
2410
|
/** load all files in directory */
|
|
2346
2411
|
async all(name, autosave = false) {
|
|
@@ -2782,7 +2847,7 @@ export class Model {
|
|
|
2782
2847
|
/** Microserver middleware */
|
|
2783
2848
|
handler(req, res) {
|
|
2784
2849
|
res.isJson = true;
|
|
2785
|
-
let filter, filterStr = req.
|
|
2850
|
+
let filter, filterStr = req.query.filter;
|
|
2786
2851
|
if (filterStr) {
|
|
2787
2852
|
try {
|
|
2788
2853
|
if (!filterStr.startsWith('{'))
|
|
@@ -2924,11 +2989,12 @@ export class MicroCollection {
|
|
|
2924
2989
|
});
|
|
2925
2990
|
return count;
|
|
2926
2991
|
}
|
|
2927
|
-
let
|
|
2928
|
-
if (!
|
|
2992
|
+
let doc = this.queryDocument(options.query, this.data[id]);
|
|
2993
|
+
if (!doc) {
|
|
2929
2994
|
if (!options.upsert && !options.new)
|
|
2930
2995
|
throw new InvalidData(`Document not found`);
|
|
2931
|
-
|
|
2996
|
+
doc = { _id: id };
|
|
2997
|
+
this.data[id] = doc;
|
|
2932
2998
|
}
|
|
2933
2999
|
else {
|
|
2934
3000
|
if (options.new)
|
|
@@ -2937,15 +3003,15 @@ export class MicroCollection {
|
|
|
2937
3003
|
if (options.update) {
|
|
2938
3004
|
for (const n in options.update) {
|
|
2939
3005
|
if (!n.startsWith('$'))
|
|
2940
|
-
|
|
3006
|
+
doc[n] = options.update[n];
|
|
2941
3007
|
}
|
|
2942
3008
|
if (options.update.$unset) {
|
|
2943
3009
|
for (const n in options.update.$unset)
|
|
2944
|
-
delete
|
|
3010
|
+
delete doc[n];
|
|
2945
3011
|
}
|
|
2946
3012
|
}
|
|
2947
3013
|
if (this._save)
|
|
2948
|
-
this.data[id] = await this._save(id,
|
|
3014
|
+
this.data[id] = await this._save(id, doc, this) || doc;
|
|
2949
3015
|
return 1;
|
|
2950
3016
|
}
|
|
2951
3017
|
/** Insert one document */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radatek/microserver",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "HTTP MicroServer",
|
|
5
5
|
"author": "Darius Kisonas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,11 +20,5 @@
|
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/radateklt/microserver.git"
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@types/node": "^22.14.1"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "pnpm --package typescript dlx tsc -d -m nodenext -t es2021 microserver.ts --outDir dist && sed -Ei 's/\\\"use strict\\\";\\n\\n//g' dist/microserver.js && sed -Ei ':a;N;$!ba;s/\\n +private _[^\\n]+//g;s/export \\{\\};//g' dist/microserver.d.ts"
|
|
29
23
|
}
|
|
30
24
|
}
|
package/readme.md
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
## HTTP MicroServer
|
|
2
2
|
|
|
3
|
-
Lightweight all-in-one http web server
|
|
3
|
+
Lightweight all-in-one http web server without dependencies
|
|
4
4
|
|
|
5
5
|
Features:
|
|
6
6
|
- fast REST API router
|
|
7
7
|
- form/json body decoder
|
|
8
8
|
- file upload
|
|
9
|
+
- websockets
|
|
9
10
|
- authentication
|
|
10
11
|
- plain/hashed passwords
|
|
11
12
|
- virtual hosts
|
|
12
13
|
- static files
|
|
14
|
+
- rewrite
|
|
15
|
+
- redirect
|
|
13
16
|
- reverse proxy routes
|
|
14
17
|
- trust ip for reverse proxy
|
|
15
18
|
- json file storage with autosave
|
|
16
|
-
- websockets (single file module from ws package)
|
|
17
19
|
- tls with automatic certificate reload
|
|
18
20
|
- data model with validation and mongodb interface
|
|
19
|
-
- simple file/memory storage
|
|
21
|
+
- simple file/memory storage
|
|
20
22
|
- promises as middleware
|
|
21
23
|
- controller class
|
|
22
24
|
- access rights per route
|
|
23
25
|
- access rights per model field
|
|
24
|
-
- rewrite
|
|
25
|
-
- redirect
|
|
26
|
-
- express like interface
|
|
27
26
|
|
|
28
27
|
### Usage examples:
|
|
29
28
|
|
|
30
29
|
Simple router:
|
|
31
30
|
|
|
32
31
|
```ts
|
|
32
|
+
import { MicroServer, AccessDenied } from '@radatek/microserver'
|
|
33
|
+
|
|
33
34
|
const server = new MicroServer({
|
|
34
35
|
listen: 8080,
|
|
35
36
|
auth: {
|
|
@@ -47,7 +48,7 @@ server.use('POST /api/login',
|
|
|
47
48
|
(req: ServerRequset, res: ServerResponse) =>
|
|
48
49
|
{
|
|
49
50
|
const user = await req.auth.login(req.body.user, req.body.password)
|
|
50
|
-
return user ? {user} :
|
|
51
|
+
return user ? {user} : new AccessDenied()
|
|
51
52
|
})
|
|
52
53
|
server.use('GET /api/protected', 'acl:auth',
|
|
53
54
|
(req: ServerRequset, res: ServerResponse) =>
|
|
@@ -55,49 +56,14 @@ server.use('GET /api/protected', 'acl:auth',
|
|
|
55
56
|
server.use('static', {root:'public'})
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
Using
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
class RestApi extends Controller {
|
|
62
|
-
static acl = '' // default acl
|
|
63
|
-
|
|
64
|
-
gethello(id) {
|
|
65
|
-
return {message:'Hello ' + id + '!'}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async postlogin() {
|
|
69
|
-
const user = await this.auth.login(this.body.user, this.body.password)
|
|
70
|
-
return user ? {user} : 403
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
static 'acl:protected' = 'user'
|
|
74
|
-
static 'url:protected' = 'GET /protected'
|
|
75
|
-
protected() {
|
|
76
|
-
return {message:'Protected'}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const server = new MicroServer({
|
|
81
|
-
listen: 8080,
|
|
82
|
-
auth: {
|
|
83
|
-
users: {
|
|
84
|
-
usr: {
|
|
85
|
-
password: 'secret',
|
|
86
|
-
acl: {user: true}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
server.use('/api', RestApi)
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`Model.handler` automatically detects usage for standard functions: get,insert,update,delete
|
|
59
|
+
Using data schema:
|
|
95
60
|
|
|
96
61
|
```js
|
|
97
|
-
import { MicroServer, Model, MicroCollection, FileStore } from '@
|
|
62
|
+
import { MicroServer, Model, MicroCollection, FileStore } from '@radatek/microserver'
|
|
98
63
|
|
|
99
64
|
const usersCollection = new MicroCollection({ store: new FileStore({ dir: 'data' }), name: 'users' })
|
|
100
|
-
//
|
|
65
|
+
// or using MicroDB collection
|
|
66
|
+
const usersCollection = await db.collection('users')
|
|
101
67
|
|
|
102
68
|
const userProfile = new Model({
|
|
103
69
|
_id: 'string',
|
|
@@ -115,7 +81,7 @@ const server = new MicroServer({
|
|
|
115
81
|
}
|
|
116
82
|
})
|
|
117
83
|
|
|
118
|
-
userProfile.insert({name: 'admin', password: 'secret', role: 'admin', acl: {'user/*': true}})
|
|
84
|
+
await userProfile.insert({name: 'admin', password: 'secret', role: 'admin', acl: {'user/*': true}})
|
|
119
85
|
|
|
120
86
|
server.use('POST /login', async (req) => {
|
|
121
87
|
const user = await req.auth.login(req.body.user, req.body.password)
|
|
@@ -134,3 +100,40 @@ server.use('PUT /admin/user/:id', 'acl:user/update', userProfile)
|
|
|
134
100
|
// delete user if has acl 'user/update'
|
|
135
101
|
server.use('DELETE /admin/user/:id', 'acl:user/delete', userProfile)
|
|
136
102
|
```
|
|
103
|
+
|
|
104
|
+
Using controller:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const server = new MicroServer({
|
|
108
|
+
listen: 8080,
|
|
109
|
+
auth: {
|
|
110
|
+
users: {
|
|
111
|
+
usr: {
|
|
112
|
+
password: 'secret',
|
|
113
|
+
acl: {user: true}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
class RestApi extends Controller {
|
|
120
|
+
static acl = '' // default acl
|
|
121
|
+
|
|
122
|
+
gethello(id) {
|
|
123
|
+
return {message:'Hello ' + id + '!'}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async postlogin() {
|
|
127
|
+
const user = await this.auth.login(this.body.user, this.body.password)
|
|
128
|
+
return user ? {user} : 403
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static 'acl:protected' = 'user'
|
|
132
|
+
static 'url:protected' = 'GET /protected'
|
|
133
|
+
protected() {
|
|
134
|
+
return {message:'Protected'}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
server.use('/api', RestApi)
|
|
139
|
+
```
|