@nymphjs/server 1.0.0-beta.2 → 1.0.0-beta.21

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.
@@ -0,0 +1,728 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createServer = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const cookie_parser_1 = __importDefault(require("cookie-parser"));
9
+ const nymph_1 = require("@nymphjs/nymph");
10
+ const nymph_2 = require("@nymphjs/nymph");
11
+ const statusDescriptions_1 = require("./statusDescriptions");
12
+ const NOT_FOUND_ERROR = 'Entity is not found.';
13
+ function createServer(nymph, { jsonOptions = {} } = {}) {
14
+ const rest = (0, express_1.default)();
15
+ rest.use((0, cookie_parser_1.default)());
16
+ rest.use(express_1.default.json(jsonOptions || {}));
17
+ function instantiateNymph(_request, response, next) {
18
+ response.locals.nymph = nymph.clone();
19
+ next();
20
+ }
21
+ function authenticateTilmeld(request, response, next) {
22
+ if (response.locals.nymph.tilmeld) {
23
+ response.locals.nymph.tilmeld.request = request;
24
+ response.locals.nymph.tilmeld.response = response;
25
+ try {
26
+ response.locals.nymph.tilmeld.authenticate();
27
+ }
28
+ catch (e) {
29
+ httpError(response, 500, e);
30
+ return;
31
+ }
32
+ }
33
+ next();
34
+ }
35
+ function unauthenticateTilmeld(_request, response, next) {
36
+ if (response.locals.nymph.tilmeld) {
37
+ response.locals.nymph.tilmeld.request = null;
38
+ response.locals.nymph.tilmeld.response = null;
39
+ try {
40
+ response.locals.nymph.tilmeld.clearSession();
41
+ }
42
+ catch (e) {
43
+ httpError(response, 500, e);
44
+ return;
45
+ }
46
+ }
47
+ next();
48
+ }
49
+ function getActionData(request) {
50
+ if (request.method === 'GET') {
51
+ if (typeof request.query?.action !== 'string' ||
52
+ typeof request.query?.data !== 'string') {
53
+ return {
54
+ action: '',
55
+ data: {},
56
+ };
57
+ }
58
+ return {
59
+ action: JSON.parse(request.query.action) ?? '',
60
+ data: JSON.parse(request.query.data),
61
+ };
62
+ }
63
+ else {
64
+ return {
65
+ action: request.body.action ?? '',
66
+ data: request.body.data,
67
+ };
68
+ }
69
+ }
70
+ rest.use(instantiateNymph);
71
+ rest.use(authenticateTilmeld);
72
+ rest.get('/', async (request, response) => {
73
+ try {
74
+ const { action, data } = getActionData(request);
75
+ if (['entity', 'entities', 'uid'].indexOf(action) === -1) {
76
+ httpError(response, 400);
77
+ return;
78
+ }
79
+ if (['entity', 'entities'].indexOf(action) !== -1) {
80
+ if (!Array.isArray(data)) {
81
+ httpError(response, 400);
82
+ return;
83
+ }
84
+ const count = data.length;
85
+ if (count < 1 || typeof data[0] !== 'object') {
86
+ httpError(response, 400);
87
+ return;
88
+ }
89
+ if (!('class' in data[0])) {
90
+ httpError(response, 400);
91
+ return;
92
+ }
93
+ let [options, ...selectors] = data;
94
+ let EntityClass;
95
+ try {
96
+ EntityClass = response.locals.nymph.getEntityClass(data[0].class);
97
+ }
98
+ catch (e) {
99
+ httpError(response, 400, e);
100
+ return;
101
+ }
102
+ options.class = EntityClass;
103
+ options.source = 'client';
104
+ options.skipAc = false;
105
+ selectors = (0, nymph_1.classNamesToEntityConstructors)(response.locals.nymph, selectors);
106
+ let result;
107
+ try {
108
+ if (action === 'entity') {
109
+ result = await response.locals.nymph.getEntity(options, ...selectors);
110
+ }
111
+ else {
112
+ result = await response.locals.nymph.getEntities(options, ...selectors);
113
+ }
114
+ }
115
+ catch (e) {
116
+ httpError(response, 500, e);
117
+ return;
118
+ }
119
+ if (result == null || (Array.isArray(result) && result.length === 0)) {
120
+ if (action === 'entity' ||
121
+ response.locals.nymph.config.emptyListError) {
122
+ httpError(response, 404);
123
+ return;
124
+ }
125
+ }
126
+ response.setHeader('Content-Type', 'application/json');
127
+ response.send(JSON.stringify(result));
128
+ }
129
+ else {
130
+ if (typeof data !== 'string') {
131
+ httpError(response, 400);
132
+ return;
133
+ }
134
+ if (response.locals.nymph.tilmeld) {
135
+ if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.READ_ACCESS)) {
136
+ httpError(response, 403);
137
+ return;
138
+ }
139
+ }
140
+ let result;
141
+ try {
142
+ result = await response.locals.nymph.getUID(data);
143
+ }
144
+ catch (e) {
145
+ httpError(response, 500, e);
146
+ return;
147
+ }
148
+ if (result === null) {
149
+ httpError(response, 404);
150
+ return;
151
+ }
152
+ else if (typeof result !== 'number') {
153
+ httpError(response, 500);
154
+ return;
155
+ }
156
+ response.setHeader('Content-Type', 'text/plain');
157
+ response.send(`${result}`);
158
+ }
159
+ }
160
+ catch (e) {
161
+ httpError(response, 500, e);
162
+ return;
163
+ }
164
+ });
165
+ rest.post('/', async (request, response) => {
166
+ try {
167
+ const { action, data: dataConst } = getActionData(request);
168
+ let data = dataConst;
169
+ if (['entity', 'entities', 'uid', 'method'].indexOf(action) === -1) {
170
+ httpError(response, 400);
171
+ return;
172
+ }
173
+ if (['entity', 'entities'].indexOf(action) !== -1) {
174
+ if (action === 'entity') {
175
+ data = [data];
176
+ }
177
+ const created = [];
178
+ let hadSuccess = false;
179
+ let invalidRequest = false;
180
+ let conflict = false;
181
+ let notfound = false;
182
+ let lastException = null;
183
+ for (let entData of data) {
184
+ if (entData.guid) {
185
+ invalidRequest = true;
186
+ created.push(null);
187
+ continue;
188
+ }
189
+ let entity;
190
+ try {
191
+ entity = await loadEntity(entData, response.locals.nymph);
192
+ }
193
+ catch (e) {
194
+ if (e instanceof nymph_1.EntityConflictError) {
195
+ conflict = true;
196
+ }
197
+ else if (e.message === NOT_FOUND_ERROR) {
198
+ notfound = true;
199
+ }
200
+ else if (e instanceof nymph_1.InvalidParametersError) {
201
+ invalidRequest = true;
202
+ lastException = e;
203
+ }
204
+ else {
205
+ lastException = e;
206
+ }
207
+ created.push(null);
208
+ continue;
209
+ }
210
+ if (!entity) {
211
+ invalidRequest = true;
212
+ created.push(null);
213
+ continue;
214
+ }
215
+ try {
216
+ if (await entity.$save()) {
217
+ created.push(entity);
218
+ hadSuccess = true;
219
+ }
220
+ else {
221
+ created.push(false);
222
+ }
223
+ }
224
+ catch (e) {
225
+ if (e instanceof nymph_2.EntityInvalidDataError) {
226
+ invalidRequest = true;
227
+ }
228
+ else {
229
+ lastException = e;
230
+ }
231
+ created.push(null);
232
+ }
233
+ }
234
+ if (!hadSuccess) {
235
+ if (invalidRequest) {
236
+ httpError(response, 400, lastException);
237
+ return;
238
+ }
239
+ else if (conflict) {
240
+ httpError(response, 409);
241
+ return;
242
+ }
243
+ else if (notfound) {
244
+ httpError(response, 404);
245
+ return;
246
+ }
247
+ else {
248
+ httpError(response, 500, lastException);
249
+ return;
250
+ }
251
+ }
252
+ response.status(201);
253
+ response.setHeader('Content-Type', 'application/json');
254
+ if (action === 'entity') {
255
+ response.send(JSON.stringify(created[0]));
256
+ }
257
+ else {
258
+ response.send(created);
259
+ }
260
+ }
261
+ else if (action === 'method') {
262
+ if (!Array.isArray(data.params)) {
263
+ httpError(response, 400);
264
+ return;
265
+ }
266
+ const params = referencesToEntities([...data.params], response.locals.nymph);
267
+ if (data.static) {
268
+ let EntityClass;
269
+ try {
270
+ EntityClass = response.locals.nymph.getEntityClass(data.class);
271
+ }
272
+ catch (e) {
273
+ httpError(response, 400);
274
+ return;
275
+ }
276
+ if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
277
+ httpError(response, 403);
278
+ return;
279
+ }
280
+ if (!(data.method in EntityClass)) {
281
+ httpError(response, 400);
282
+ return;
283
+ }
284
+ const method = EntityClass[data.method];
285
+ if (typeof method !== 'function') {
286
+ httpError(response, 400);
287
+ return;
288
+ }
289
+ try {
290
+ const result = method.call(EntityClass, ...params);
291
+ let ret = result;
292
+ if (result instanceof Promise) {
293
+ ret = await result;
294
+ }
295
+ response.status(200);
296
+ response.setHeader('Content-Type', 'application/json');
297
+ response.send({ return: ret });
298
+ }
299
+ catch (e) {
300
+ httpError(response, 500, e);
301
+ return;
302
+ }
303
+ }
304
+ else {
305
+ let entity;
306
+ try {
307
+ entity = await loadEntity(data.entity, response.locals.nymph);
308
+ }
309
+ catch (e) {
310
+ if (e instanceof nymph_1.EntityConflictError) {
311
+ httpError(response, 409);
312
+ }
313
+ else if (e.message === NOT_FOUND_ERROR) {
314
+ httpError(response, 404, e);
315
+ }
316
+ else if (e instanceof nymph_1.InvalidParametersError) {
317
+ httpError(response, 400, e);
318
+ }
319
+ else {
320
+ httpError(response, 500, e);
321
+ }
322
+ return;
323
+ }
324
+ if (data.entity.guid && !entity.guid) {
325
+ httpError(response, 400);
326
+ return;
327
+ }
328
+ if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
329
+ httpError(response, 403);
330
+ return;
331
+ }
332
+ if (!(data.method in entity) ||
333
+ typeof entity[data.method] !== 'function') {
334
+ httpError(response, 400);
335
+ return;
336
+ }
337
+ try {
338
+ const result = entity[data.method](...params);
339
+ let ret = result;
340
+ if (result instanceof Promise) {
341
+ ret = await result;
342
+ }
343
+ response.status(200);
344
+ response.setHeader('Content-Type', 'application/json');
345
+ if (data.stateless) {
346
+ response.send({ return: ret });
347
+ }
348
+ else {
349
+ response.send({ entity: entity, return: ret });
350
+ }
351
+ }
352
+ catch (e) {
353
+ httpError(response, 500, e);
354
+ return;
355
+ }
356
+ }
357
+ }
358
+ else {
359
+ if (typeof data !== 'string') {
360
+ httpError(response, 400);
361
+ return;
362
+ }
363
+ if (response.locals.nymph.tilmeld) {
364
+ if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.WRITE_ACCESS)) {
365
+ httpError(response, 403);
366
+ return;
367
+ }
368
+ }
369
+ let result;
370
+ try {
371
+ result = await response.locals.nymph.newUID(data);
372
+ }
373
+ catch (e) {
374
+ httpError(response, 500, e);
375
+ return;
376
+ }
377
+ if (typeof result !== 'number') {
378
+ httpError(response, 500);
379
+ return;
380
+ }
381
+ response.status(201);
382
+ response.setHeader('Content-Type', 'text/plain');
383
+ response.send(`${result}`);
384
+ }
385
+ }
386
+ catch (e) {
387
+ httpError(response, 500, e);
388
+ return;
389
+ }
390
+ });
391
+ rest.put('/', async (request, response) => {
392
+ try {
393
+ const { action, data } = getActionData(request);
394
+ if (['entity', 'entities', 'uid'].indexOf(action) === -1) {
395
+ httpError(response, 400);
396
+ return;
397
+ }
398
+ await doPutOrPatch(response, action, data, false);
399
+ }
400
+ catch (e) {
401
+ httpError(response, 500, e);
402
+ return;
403
+ }
404
+ });
405
+ rest.patch('/', async (request, response) => {
406
+ try {
407
+ const { action, data } = getActionData(request);
408
+ if (['entity', 'entities'].indexOf(action) === -1) {
409
+ httpError(response, 400);
410
+ return;
411
+ }
412
+ await doPutOrPatch(response, action, data, true);
413
+ }
414
+ catch (e) {
415
+ httpError(response, 500, e);
416
+ return;
417
+ }
418
+ });
419
+ async function doPutOrPatch(response, action, data, patch) {
420
+ if (action === 'uid') {
421
+ if (typeof data.name !== 'string' || typeof data.value !== 'number') {
422
+ httpError(response, 400);
423
+ return;
424
+ }
425
+ if (response.locals.nymph.tilmeld) {
426
+ if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name, nymph_1.TilmeldAccessLevels.FULL_ACCESS)) {
427
+ httpError(response, 403);
428
+ return;
429
+ }
430
+ }
431
+ let result;
432
+ try {
433
+ result = await response.locals.nymph.setUID(data.name, data.value);
434
+ }
435
+ catch (e) {
436
+ httpError(response, 500, e);
437
+ return;
438
+ }
439
+ if (!result) {
440
+ httpError(response, 500);
441
+ return;
442
+ }
443
+ response.status(200);
444
+ response.setHeader('Content-Type', 'text/plain');
445
+ response.send(`${result}`);
446
+ }
447
+ else {
448
+ if (action === 'entity') {
449
+ data = [data];
450
+ }
451
+ const saved = [];
452
+ let hadSuccess = false;
453
+ let invalidRequest = false;
454
+ let conflict = false;
455
+ let notfound = false;
456
+ let lastException = null;
457
+ for (let entData of data) {
458
+ if (entData.guid && entData.guid.length != 24) {
459
+ invalidRequest = true;
460
+ saved.push(null);
461
+ continue;
462
+ }
463
+ let entity;
464
+ try {
465
+ entity = await loadEntity(entData, response.locals.nymph, patch);
466
+ }
467
+ catch (e) {
468
+ if (e instanceof nymph_1.EntityConflictError) {
469
+ conflict = true;
470
+ }
471
+ else if (e.message === NOT_FOUND_ERROR) {
472
+ notfound = true;
473
+ }
474
+ else if (e instanceof nymph_1.InvalidParametersError) {
475
+ invalidRequest = true;
476
+ lastException = e;
477
+ }
478
+ else {
479
+ lastException = e;
480
+ }
481
+ saved.push(null);
482
+ continue;
483
+ }
484
+ if (!entity) {
485
+ invalidRequest = true;
486
+ saved.push(null);
487
+ continue;
488
+ }
489
+ try {
490
+ if (await entity.$save()) {
491
+ saved.push(entity);
492
+ hadSuccess = true;
493
+ }
494
+ else {
495
+ saved.push(false);
496
+ }
497
+ }
498
+ catch (e) {
499
+ if (e instanceof nymph_2.EntityInvalidDataError) {
500
+ invalidRequest = true;
501
+ }
502
+ else {
503
+ lastException = e;
504
+ }
505
+ saved.push(null);
506
+ }
507
+ }
508
+ if (!hadSuccess) {
509
+ if (invalidRequest) {
510
+ httpError(response, 400, lastException);
511
+ }
512
+ else if (conflict) {
513
+ httpError(response, 409);
514
+ }
515
+ else if (notfound) {
516
+ httpError(response, 404);
517
+ }
518
+ else {
519
+ httpError(response, 500, lastException);
520
+ }
521
+ return;
522
+ }
523
+ response.status(200);
524
+ response.setHeader('Content-Type', 'application/json');
525
+ if (action === 'entity') {
526
+ response.send(JSON.stringify(saved[0]));
527
+ }
528
+ else {
529
+ response.send(saved);
530
+ }
531
+ }
532
+ }
533
+ rest.delete('/', async (request, response) => {
534
+ try {
535
+ const { action, data: dataConst } = getActionData(request);
536
+ let data = dataConst;
537
+ if (['entity', 'entities', 'uid'].indexOf(action) === -1) {
538
+ httpError(response, 400);
539
+ return;
540
+ }
541
+ if (['entity', 'entities'].indexOf(action) !== -1) {
542
+ if (action === 'entity') {
543
+ data = [data];
544
+ }
545
+ const deleted = [];
546
+ let failures = false;
547
+ let invalidRequest = false;
548
+ let notfound = false;
549
+ let lastException = null;
550
+ for (let entData of data) {
551
+ if (entData.guid && entData.guid.length != 24) {
552
+ invalidRequest = true;
553
+ continue;
554
+ }
555
+ let EntityClass;
556
+ try {
557
+ EntityClass = response.locals.nymph.getEntityClass(entData.class);
558
+ }
559
+ catch (e) {
560
+ invalidRequest = true;
561
+ failures = true;
562
+ continue;
563
+ }
564
+ let entity;
565
+ try {
566
+ entity = await response.locals.nymph.getEntity({ class: EntityClass }, { type: '&', guid: entData.guid });
567
+ }
568
+ catch (e) {
569
+ lastException = e;
570
+ failures = true;
571
+ continue;
572
+ }
573
+ if (!entity) {
574
+ notfound = true;
575
+ failures = true;
576
+ continue;
577
+ }
578
+ try {
579
+ if (await entity.$delete()) {
580
+ deleted.push(entData.guid);
581
+ }
582
+ else {
583
+ failures = true;
584
+ }
585
+ }
586
+ catch (e) {
587
+ lastException = e;
588
+ failures = true;
589
+ }
590
+ }
591
+ if (deleted.length === 0) {
592
+ if (invalidRequest || !failures) {
593
+ httpError(response, 400, lastException);
594
+ }
595
+ else if (notfound) {
596
+ httpError(response, 404);
597
+ }
598
+ else {
599
+ httpError(response, 500, lastException);
600
+ }
601
+ return;
602
+ }
603
+ response.status(200);
604
+ response.setHeader('Content-Type', 'application/json');
605
+ if (action === 'entity') {
606
+ response.send(JSON.stringify(deleted[0]));
607
+ }
608
+ else {
609
+ response.send(deleted);
610
+ }
611
+ }
612
+ else {
613
+ if (typeof data !== 'string') {
614
+ httpError(response, 400);
615
+ return;
616
+ }
617
+ if (response.locals.nymph.tilmeld) {
618
+ if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data, nymph_1.TilmeldAccessLevels.FULL_ACCESS)) {
619
+ httpError(response, 403);
620
+ return;
621
+ }
622
+ }
623
+ let result;
624
+ try {
625
+ result = await response.locals.nymph.deleteUID(data);
626
+ }
627
+ catch (e) {
628
+ httpError(response, 500, e);
629
+ return;
630
+ }
631
+ if (!result) {
632
+ httpError(response, 500);
633
+ return;
634
+ }
635
+ response.status(200);
636
+ response.setHeader('Content-Type', 'application/json');
637
+ response.send(JSON.stringify(result));
638
+ }
639
+ }
640
+ catch (e) {
641
+ httpError(response, 500, e);
642
+ return;
643
+ }
644
+ });
645
+ rest.use(unauthenticateTilmeld);
646
+ async function loadEntity(entityData, nymph, patch = false, allowConflict = false) {
647
+ if (entityData.class === 'Entity') {
648
+ throw new nymph_1.InvalidParametersError("Can't use Entity class directly from the front end.");
649
+ }
650
+ let EntityClass = nymph.getEntityClass(entityData.class);
651
+ let entity;
652
+ if (entityData.guid) {
653
+ entity = await nymph.getEntity({ class: EntityClass, source: 'client' }, {
654
+ type: '&',
655
+ guid: `${entityData['guid']}`,
656
+ });
657
+ if (entity === null) {
658
+ throw new Error(NOT_FOUND_ERROR);
659
+ }
660
+ }
661
+ else {
662
+ entity = await EntityClass.factory();
663
+ }
664
+ if (patch) {
665
+ entity.$jsonAcceptPatch(entityData, allowConflict);
666
+ }
667
+ else {
668
+ entity.$jsonAcceptData(entityData, allowConflict);
669
+ }
670
+ return entity;
671
+ }
672
+ function referencesToEntities(item, nymph) {
673
+ if (Array.isArray(item)) {
674
+ if (item.length === 3 && item[0] === 'nymph_entity_reference') {
675
+ try {
676
+ const EntityClass = nymph.getEntityClass(item[1]);
677
+ return EntityClass.factoryReference(item);
678
+ }
679
+ catch (e) {
680
+ return item;
681
+ }
682
+ }
683
+ return item.map((entry) => referencesToEntities(entry, nymph));
684
+ }
685
+ else if (typeof item === 'object' && !(item instanceof nymph_1.Entity)) {
686
+ const newItem = {};
687
+ for (let curProperty in item) {
688
+ newItem[curProperty] = referencesToEntities(item[curProperty], nymph);
689
+ }
690
+ return newItem;
691
+ }
692
+ return item;
693
+ }
694
+ function httpError(res, defaultStatusCode, error) {
695
+ const status = error?.status || defaultStatusCode;
696
+ const statusText = error?.statusText ||
697
+ (error?.status == null
698
+ ? statusDescriptions_1.statusDescriptions[defaultStatusCode]
699
+ : error.status in statusDescriptions_1.statusDescriptions &&
700
+ statusDescriptions_1.statusDescriptions[error.status]) ||
701
+ 'Internal Server Error';
702
+ if (!res.headersSent) {
703
+ res.status(status);
704
+ res.setHeader('Content-Type', 'application/json');
705
+ }
706
+ if (error) {
707
+ res.send({
708
+ textStatus: `${status} ${statusText}`,
709
+ statusText,
710
+ message: error.message,
711
+ error,
712
+ ...(process.env.NODE_ENV !== 'production'
713
+ ? { stack: error.stack }
714
+ : {}),
715
+ });
716
+ }
717
+ else {
718
+ res.send({
719
+ textStatus: `${status} ${statusText}`,
720
+ statusText,
721
+ message: statusText,
722
+ });
723
+ }
724
+ }
725
+ return rest;
726
+ }
727
+ exports.createServer = createServer;
728
+ //# sourceMappingURL=createServer.js.map