@technomoron/api-server-base 1.1.6 → 1.1.7
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.
|
@@ -335,7 +335,9 @@ function fillConfig(config) {
|
|
|
335
335
|
class ApiServer {
|
|
336
336
|
constructor(config = {}) {
|
|
337
337
|
this.currReq = null;
|
|
338
|
+
this.apiNotFoundHandler = null;
|
|
338
339
|
this.config = fillConfig(config);
|
|
340
|
+
this.apiBasePath = this.normalizeApiBasePath(this.config.apiBasePath);
|
|
339
341
|
this.storageAdapter = auth_storage_js_1.nullAuthStorage;
|
|
340
342
|
this.moduleAdapter = auth_module_js_1.nullAuthModule;
|
|
341
343
|
this.app = (0, express_1.default)();
|
|
@@ -345,6 +347,7 @@ class ApiServer {
|
|
|
345
347
|
}
|
|
346
348
|
this.middlewares();
|
|
347
349
|
// addSwaggerUi(this.app);
|
|
350
|
+
this.installApiNotFoundHandler();
|
|
348
351
|
}
|
|
349
352
|
authStorage(storage) {
|
|
350
353
|
this.storageAdapter = storage;
|
|
@@ -493,6 +496,56 @@ class ApiServer {
|
|
|
493
496
|
};
|
|
494
497
|
this.app.use((0, cors_1.default)(corsOptions));
|
|
495
498
|
}
|
|
499
|
+
normalizeApiBasePath(path) {
|
|
500
|
+
if (!path || typeof path !== 'string') {
|
|
501
|
+
return '/api';
|
|
502
|
+
}
|
|
503
|
+
const trimmed = path.trim();
|
|
504
|
+
if (!trimmed) {
|
|
505
|
+
return '/api';
|
|
506
|
+
}
|
|
507
|
+
const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
508
|
+
if (withLeadingSlash.length === 1) {
|
|
509
|
+
return withLeadingSlash;
|
|
510
|
+
}
|
|
511
|
+
return withLeadingSlash.replace(/\/+$/, '') || '/api';
|
|
512
|
+
}
|
|
513
|
+
installApiNotFoundHandler() {
|
|
514
|
+
if (this.apiNotFoundHandler) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
this.apiNotFoundHandler = (req, res) => {
|
|
518
|
+
const payload = {
|
|
519
|
+
code: 404,
|
|
520
|
+
message: this.describeMissingEndpoint(req),
|
|
521
|
+
data: null,
|
|
522
|
+
errors: {}
|
|
523
|
+
};
|
|
524
|
+
res.status(404).json(payload);
|
|
525
|
+
};
|
|
526
|
+
this.app.use(this.apiBasePath, this.apiNotFoundHandler);
|
|
527
|
+
}
|
|
528
|
+
ensureApiNotFoundOrdering() {
|
|
529
|
+
this.installApiNotFoundHandler();
|
|
530
|
+
if (!this.apiNotFoundHandler) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const stack = this.app?._router?.stack;
|
|
534
|
+
if (!stack) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const index = stack.findIndex((layer) => layer.handle === this.apiNotFoundHandler);
|
|
538
|
+
if (index === -1 || index === stack.length - 1) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const [layer] = stack.splice(index, 1);
|
|
542
|
+
stack.push(layer);
|
|
543
|
+
}
|
|
544
|
+
describeMissingEndpoint(req) {
|
|
545
|
+
const method = typeof req.method === 'string' ? req.method.toUpperCase() : 'GET';
|
|
546
|
+
const target = req.originalUrl || req.url || this.apiBasePath;
|
|
547
|
+
return `No such endpoint: ${method} ${target}`;
|
|
548
|
+
}
|
|
496
549
|
start() {
|
|
497
550
|
this.app
|
|
498
551
|
.listen({
|
|
@@ -705,7 +758,7 @@ class ApiServer {
|
|
|
705
758
|
this.authModule(module);
|
|
706
759
|
}
|
|
707
760
|
module.checkConfig();
|
|
708
|
-
const base = this.
|
|
761
|
+
const base = this.apiBasePath;
|
|
709
762
|
const ns = module.namespace;
|
|
710
763
|
const mountPath = `${base}${ns}`;
|
|
711
764
|
module.mountpath = mountPath;
|
|
@@ -717,6 +770,7 @@ class ApiServer {
|
|
|
717
770
|
}
|
|
718
771
|
});
|
|
719
772
|
this.app.use(mountPath, router);
|
|
773
|
+
this.ensureApiNotFoundOrdering();
|
|
720
774
|
return this;
|
|
721
775
|
}
|
|
722
776
|
dumpRequest(apiReq) {
|
|
@@ -101,8 +101,10 @@ export declare class ApiServer {
|
|
|
101
101
|
app: Application;
|
|
102
102
|
currReq: ApiRequest | null;
|
|
103
103
|
readonly config: ApiServerConf;
|
|
104
|
+
private readonly apiBasePath;
|
|
104
105
|
private storageAdapter;
|
|
105
106
|
private moduleAdapter;
|
|
107
|
+
private apiNotFoundHandler;
|
|
106
108
|
constructor(config?: Partial<ApiServerConf>);
|
|
107
109
|
authStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
108
110
|
/**
|
|
@@ -134,6 +136,10 @@ export declare class ApiServer {
|
|
|
134
136
|
guessExceptionText(error: any, defMsg?: string): string;
|
|
135
137
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
136
138
|
private middlewares;
|
|
139
|
+
private normalizeApiBasePath;
|
|
140
|
+
private installApiNotFoundHandler;
|
|
141
|
+
private ensureApiNotFoundOrdering;
|
|
142
|
+
private describeMissingEndpoint;
|
|
137
143
|
start(): this;
|
|
138
144
|
private verifyJWT;
|
|
139
145
|
private authenticate;
|
|
@@ -101,8 +101,10 @@ export declare class ApiServer {
|
|
|
101
101
|
app: Application;
|
|
102
102
|
currReq: ApiRequest | null;
|
|
103
103
|
readonly config: ApiServerConf;
|
|
104
|
+
private readonly apiBasePath;
|
|
104
105
|
private storageAdapter;
|
|
105
106
|
private moduleAdapter;
|
|
107
|
+
private apiNotFoundHandler;
|
|
106
108
|
constructor(config?: Partial<ApiServerConf>);
|
|
107
109
|
authStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
108
110
|
/**
|
|
@@ -134,6 +136,10 @@ export declare class ApiServer {
|
|
|
134
136
|
guessExceptionText(error: any, defMsg?: string): string;
|
|
135
137
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
136
138
|
private middlewares;
|
|
139
|
+
private normalizeApiBasePath;
|
|
140
|
+
private installApiNotFoundHandler;
|
|
141
|
+
private ensureApiNotFoundOrdering;
|
|
142
|
+
private describeMissingEndpoint;
|
|
137
143
|
start(): this;
|
|
138
144
|
private verifyJWT;
|
|
139
145
|
private authenticate;
|
|
@@ -327,7 +327,9 @@ function fillConfig(config) {
|
|
|
327
327
|
export class ApiServer {
|
|
328
328
|
constructor(config = {}) {
|
|
329
329
|
this.currReq = null;
|
|
330
|
+
this.apiNotFoundHandler = null;
|
|
330
331
|
this.config = fillConfig(config);
|
|
332
|
+
this.apiBasePath = this.normalizeApiBasePath(this.config.apiBasePath);
|
|
331
333
|
this.storageAdapter = nullAuthStorage;
|
|
332
334
|
this.moduleAdapter = nullAuthModule;
|
|
333
335
|
this.app = express();
|
|
@@ -337,6 +339,7 @@ export class ApiServer {
|
|
|
337
339
|
}
|
|
338
340
|
this.middlewares();
|
|
339
341
|
// addSwaggerUi(this.app);
|
|
342
|
+
this.installApiNotFoundHandler();
|
|
340
343
|
}
|
|
341
344
|
authStorage(storage) {
|
|
342
345
|
this.storageAdapter = storage;
|
|
@@ -485,6 +488,56 @@ export class ApiServer {
|
|
|
485
488
|
};
|
|
486
489
|
this.app.use(cors(corsOptions));
|
|
487
490
|
}
|
|
491
|
+
normalizeApiBasePath(path) {
|
|
492
|
+
if (!path || typeof path !== 'string') {
|
|
493
|
+
return '/api';
|
|
494
|
+
}
|
|
495
|
+
const trimmed = path.trim();
|
|
496
|
+
if (!trimmed) {
|
|
497
|
+
return '/api';
|
|
498
|
+
}
|
|
499
|
+
const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
500
|
+
if (withLeadingSlash.length === 1) {
|
|
501
|
+
return withLeadingSlash;
|
|
502
|
+
}
|
|
503
|
+
return withLeadingSlash.replace(/\/+$/, '') || '/api';
|
|
504
|
+
}
|
|
505
|
+
installApiNotFoundHandler() {
|
|
506
|
+
if (this.apiNotFoundHandler) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.apiNotFoundHandler = (req, res) => {
|
|
510
|
+
const payload = {
|
|
511
|
+
code: 404,
|
|
512
|
+
message: this.describeMissingEndpoint(req),
|
|
513
|
+
data: null,
|
|
514
|
+
errors: {}
|
|
515
|
+
};
|
|
516
|
+
res.status(404).json(payload);
|
|
517
|
+
};
|
|
518
|
+
this.app.use(this.apiBasePath, this.apiNotFoundHandler);
|
|
519
|
+
}
|
|
520
|
+
ensureApiNotFoundOrdering() {
|
|
521
|
+
this.installApiNotFoundHandler();
|
|
522
|
+
if (!this.apiNotFoundHandler) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const stack = this.app?._router?.stack;
|
|
526
|
+
if (!stack) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const index = stack.findIndex((layer) => layer.handle === this.apiNotFoundHandler);
|
|
530
|
+
if (index === -1 || index === stack.length - 1) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const [layer] = stack.splice(index, 1);
|
|
534
|
+
stack.push(layer);
|
|
535
|
+
}
|
|
536
|
+
describeMissingEndpoint(req) {
|
|
537
|
+
const method = typeof req.method === 'string' ? req.method.toUpperCase() : 'GET';
|
|
538
|
+
const target = req.originalUrl || req.url || this.apiBasePath;
|
|
539
|
+
return `No such endpoint: ${method} ${target}`;
|
|
540
|
+
}
|
|
488
541
|
start() {
|
|
489
542
|
this.app
|
|
490
543
|
.listen({
|
|
@@ -697,7 +750,7 @@ export class ApiServer {
|
|
|
697
750
|
this.authModule(module);
|
|
698
751
|
}
|
|
699
752
|
module.checkConfig();
|
|
700
|
-
const base = this.
|
|
753
|
+
const base = this.apiBasePath;
|
|
701
754
|
const ns = module.namespace;
|
|
702
755
|
const mountPath = `${base}${ns}`;
|
|
703
756
|
module.mountpath = mountPath;
|
|
@@ -709,6 +762,7 @@ export class ApiServer {
|
|
|
709
762
|
}
|
|
710
763
|
});
|
|
711
764
|
this.app.use(mountPath, router);
|
|
765
|
+
this.ensureApiNotFoundOrdering();
|
|
712
766
|
return this;
|
|
713
767
|
}
|
|
714
768
|
dumpRequest(apiReq) {
|