@nymphjs/server 1.0.0-alpha.2 → 1.0.0-alpha.23

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/src/index.ts CHANGED
@@ -10,24 +10,47 @@ import {
10
10
  EntityPatch,
11
11
  EntityReference,
12
12
  InvalidParametersError,
13
+ TilmeldAccessLevels,
13
14
  } from '@nymphjs/nymph';
14
15
  import { EntityInvalidDataError } from '@nymphjs/nymph';
15
16
 
17
+ type NymphResponse = Response<any, { nymph: Nymph }>;
18
+
19
+ const NOT_FOUND_ERROR = 'Entity is not found.';
20
+
21
+ /**
22
+ * A REST server middleware creator for Nymph.
23
+ *
24
+ * Written by Hunter Perrin for SciActive.
25
+ *
26
+ * @author Hunter Perrin <hperrin@gmail.com>
27
+ * @copyright SciActive Inc
28
+ * @see http://nymph.io/
29
+ */
16
30
  export default function createServer(nymph: Nymph) {
17
31
  const rest = express();
18
32
  rest.use(cookieParser());
19
33
  rest.use(express.json());
20
34
 
35
+ function instantiateNymph(
36
+ _request: Request,
37
+ response: NymphResponse,
38
+ next: NextFunction
39
+ ) {
40
+ response.locals.nymph = nymph.clone();
41
+ next();
42
+ }
43
+
21
44
  function authenticateTilmeld(
22
45
  request: Request,
23
- response: Response,
46
+ response: NymphResponse,
24
47
  next: NextFunction
25
48
  ) {
26
- if (nymph.tilmeld) {
27
- nymph.tilmeld.request = request;
28
- nymph.tilmeld.response = response;
49
+ if (response.locals.nymph.tilmeld) {
50
+ response.locals.nymph.tilmeld.request = request;
51
+ response.locals.nymph.tilmeld.response = response;
29
52
  try {
30
- nymph.tilmeld.authenticate();
53
+ response.locals.nymph.tilmeld.authenticate();
31
54
  } catch (e: any) {
32
55
  httpError(response, 500, 'Internal Server Error', e);
33
56
  return;
@@ -38,14 +61,14 @@ export default function createServer(nymph: Nymph) {
38
61
 
39
62
  function unauthenticateTilmeld(
40
63
  _request: Request,
41
- response: Response,
64
+ response: NymphResponse,
42
65
  next: NextFunction
43
66
  ) {
44
- if (nymph.tilmeld) {
45
- nymph.tilmeld.request = null;
46
- nymph.tilmeld.response = null;
67
+ if (response.locals.nymph.tilmeld) {
68
+ response.locals.nymph.tilmeld.request = null;
69
+ response.locals.nymph.tilmeld.response = null;
47
70
  try {
48
- nymph.tilmeld.clearSession();
71
+ response.locals.nymph.tilmeld.clearSession();
49
72
  } catch (e: any) {
50
73
  httpError(response, 500, 'Internal Server Error', e);
51
74
  return;
@@ -77,10 +100,13 @@ export default function createServer(nymph: Nymph) {
77
100
  }
78
101
  }
79
102
 
103
+ // Create a new instance of Nymph for the request/response.
104
+ rest.use(instantiateNymph);
105
+
80
106
  // Authenticate before the request.
81
107
  rest.use(authenticateTilmeld);
82
108
 
83
- rest.get('/', async (request, response) => {
109
+ rest.get('/', async (request, response: NymphResponse) => {
84
110
  try {
85
111
  const { action, data } = getActionData(request);
86
112
  if (['entity', 'entities', 'uid'].indexOf(action) === -1) {
@@ -103,7 +129,7 @@ export default function createServer(nymph: Nymph) {
103
129
  }
104
130
  let EntityClass;
105
131
  try {
106
- EntityClass = nymph.getEntityClass(data[0].class);
132
+ EntityClass = response.locals.nymph.getEntityClass(data[0].class);
107
133
  } catch (e: any) {
108
134
  httpError(response, 400, 'Bad Request', e);
109
135
  return;
@@ -119,16 +145,22 @@ export default function createServer(nymph: Nymph) {
119
145
  | null;
120
146
  try {
121
147
  if (action === 'entity') {
122
- result = await nymph.getEntity(data[0], ...data.slice(1));
148
+ result = await response.locals.nymph.getEntity(
149
+ data[0],
150
+ ...data.slice(1)
151
+ );
123
152
  } else {
124
- result = await nymph.getEntities(...data);
153
+ result = await response.locals.nymph.getEntities(...data);
125
154
  }
126
155
  } catch (e: any) {
127
156
  httpError(response, 500, 'Internal Server Error', e);
128
157
  return;
129
158
  }
130
159
  if (result === [] || result == null) {
131
- if (action === 'entity' || nymph.config.emptyListError) {
160
+ if (
161
+ action === 'entity' ||
162
+ response.locals.nymph.config.emptyListError
163
+ ) {
132
164
  httpError(response, 404, 'Not Found');
133
165
  return;
134
166
  }
@@ -136,9 +168,24 @@ export default function createServer(nymph: Nymph) {
136
168
  response.setHeader('Content-Type', 'application/json');
137
169
  response.send(JSON.stringify(result));
138
170
  } else {
171
+ if (typeof data !== 'string') {
172
+ httpError(response, 400, 'Bad Request');
173
+ return;
174
+ }
175
+ if (response.locals.nymph.tilmeld) {
176
+ if (
177
+ !response.locals.nymph.tilmeld.checkClientUIDPermissions(
178
+ data,
179
+ TilmeldAccessLevels.READ_ACCESS
180
+ )
181
+ ) {
182
+ httpError(response, 403, 'Forbidden');
183
+ return;
184
+ }
185
+ }
139
186
  let result: number | null;
140
187
  try {
141
- result = await nymph.getUID(`${data}`);
188
+ result = await response.locals.nymph.getUID(data);
142
189
  } catch (e: any) {
143
190
  httpError(response, 500, 'Internal Server Error', e);
144
191
  return;
@@ -159,7 +206,7 @@ export default function createServer(nymph: Nymph) {
159
206
  }
160
207
  });
161
208
 
162
- rest.post('/', async (request, response) => {
209
+ rest.post('/', async (request, response: NymphResponse) => {
163
210
  try {
164
211
  const { action, data: dataConst } = getActionData(request);
165
212
  let data = dataConst;
@@ -175,6 +222,7 @@ export default function createServer(nymph: Nymph) {
175
222
  let hadSuccess = false;
176
223
  let invalidRequest = false;
177
224
  let conflict = false;
225
+ let notfound = false;
178
226
  let lastException = null;
179
227
  for (let entData of data) {
180
228
  if (entData.guid) {
@@ -184,10 +232,12 @@ export default function createServer(nymph: Nymph) {
184
232
  }
185
233
  let entity: EntityInterface;
186
234
  try {
187
- entity = await loadEntity(entData);
235
+ entity = await loadEntity(entData, response.locals.nymph);
188
236
  } catch (e: any) {
189
237
  if (e instanceof EntityConflictError) {
190
238
  conflict = true;
239
+ } else if (e.message === NOT_FOUND_ERROR) {
240
+ notfound = true;
191
241
  } else if (e instanceof InvalidParametersError) {
192
242
  invalidRequest = true;
193
243
  lastException = e;
@@ -225,6 +275,9 @@ export default function createServer(nymph: Nymph) {
225
275
  } else if (conflict) {
226
276
  httpError(response, 409, 'Conflict');
227
277
  return;
278
+ } else if (notfound) {
279
+ httpError(response, 404, 'Not Found');
280
+ return;
228
281
  } else {
229
282
  httpError(response, 500, 'Internal Server Error', lastException);
230
283
  return;
@@ -242,11 +295,14 @@ export default function createServer(nymph: Nymph) {
242
295
  httpError(response, 400, 'Bad Request');
243
296
  return;
244
297
  }
245
- const params = referencesToEntities([...data.params]);
298
+ const params = referencesToEntities(
299
+ [...data.params],
300
+ response.locals.nymph
301
+ );
246
302
  if (data.static) {
247
303
  let EntityClass: EntityConstructor;
248
304
  try {
249
- EntityClass = nymph.getEntityClass(data.class);
305
+ EntityClass = response.locals.nymph.getEntityClass(data.class);
250
306
  } catch (e: any) {
251
307
  httpError(response, 400, 'Bad Request');
252
308
  return;
@@ -283,10 +339,12 @@ export default function createServer(nymph: Nymph) {
283
339
  } else {
284
340
  let entity: EntityInterface;
285
341
  try {
286
- entity = await loadEntity(data.entity);
342
+ entity = await loadEntity(data.entity, response.locals.nymph);
287
343
  } catch (e: any) {
288
344
  if (e instanceof EntityConflictError) {
289
345
  httpError(response, 409, 'Conflict');
346
+ } else if (e.message === NOT_FOUND_ERROR) {
347
+ httpError(response, 404, 'Not Found', e);
290
348
  } else if (e instanceof InvalidParametersError) {
291
349
  httpError(response, 400, 'Bad Request', e);
292
350
  } else {
@@ -328,9 +386,24 @@ export default function createServer(nymph: Nymph) {
328
386
  }
329
387
  }
330
388
  } else {
389
+ if (typeof data !== 'string') {
390
+ httpError(response, 400, 'Bad Request');
391
+ return;
392
+ }
393
+ if (response.locals.nymph.tilmeld) {
394
+ if (
395
+ !response.locals.nymph.tilmeld.checkClientUIDPermissions(
396
+ data,
397
+ TilmeldAccessLevels.WRITE_ACCESS
398
+ )
399
+ ) {
400
+ httpError(response, 403, 'Forbidden');
401
+ return;
402
+ }
403
+ }
331
404
  let result: number | null;
332
405
  try {
333
- result = await nymph.newUID(`${data}`);
406
+ result = await response.locals.nymph.newUID(data);
334
407
  } catch (e: any) {
335
408
  httpError(response, 500, 'Internal Server Error', e);
336
409
  return;
@@ -349,7 +422,7 @@ export default function createServer(nymph: Nymph) {
349
422
  }
350
423
  });
351
424
 
352
- rest.put('/', async (request, response) => {
425
+ rest.put('/', async (request, response: NymphResponse) => {
353
426
  try {
354
427
  const { action, data } = getActionData(request);
355
428
  if (['entity', 'entities', 'uid'].indexOf(action) === -1) {
@@ -363,7 +436,7 @@ export default function createServer(nymph: Nymph) {
363
436
  }
364
437
  });
365
438
 
366
- rest.patch('/', async (request, response) => {
439
+ rest.patch('/', async (request, response: NymphResponse) => {
367
440
  try {
368
441
  const { action, data } = getActionData(request);
369
442
  if (['entity', 'entities'].indexOf(action) === -1) {
@@ -378,7 +451,7 @@ export default function createServer(nymph: Nymph) {
378
451
  });
379
452
 
380
453
  async function doPutOrPatch(
381
- response: Response,
454
+ response: NymphResponse,
382
455
  action: string,
383
456
  data: any,
384
457
  patch: boolean
@@ -388,9 +461,20 @@ export default function createServer(nymph: Nymph) {
388
461
  httpError(response, 400, 'Bad Request');
389
462
  return;
390
463
  }
464
+ if (response.locals.nymph.tilmeld) {
465
+ if (
466
+ !response.locals.nymph.tilmeld.checkClientUIDPermissions(
467
+ data.name,
468
+ TilmeldAccessLevels.FULL_ACCESS
469
+ )
470
+ ) {
471
+ httpError(response, 403, 'Forbidden');
472
+ return;
473
+ }
474
+ }
391
475
  let result: boolean;
392
476
  try {
393
- result = await nymph.setUID(data.name, data.value);
477
+ result = await response.locals.nymph.setUID(data.name, data.value);
394
478
  } catch (e: any) {
395
479
  httpError(response, 500, 'Internal Server Error', e);
396
480
  return;
@@ -420,14 +504,17 @@ export default function createServer(nymph: Nymph) {
420
504
  }
421
505
  let entity: EntityInterface;
422
506
  try {
423
- entity = await loadEntity(entData, patch);
507
+ entity = await loadEntity(entData, response.locals.nymph, patch);
424
508
  } catch (e: any) {
425
509
  if (e instanceof EntityConflictError) {
426
510
  conflict = true;
427
- }
428
- if (e instanceof InvalidParametersError) {
511
+ } else if (e.message === NOT_FOUND_ERROR) {
512
+ notfound = true;
513
+ } else if (e instanceof InvalidParametersError) {
429
514
  invalidRequest = true;
430
515
  lastException = e;
516
+ } else {
517
+ lastException = e;
431
518
  }
432
519
  saved.push(null);
433
520
  continue;
@@ -475,7 +562,7 @@ export default function createServer(nymph: Nymph) {
475
562
  }
476
563
  }
477
564
 
478
- rest.delete('/', async (request, response) => {
565
+ rest.delete('/', async (request, response: NymphResponse) => {
479
566
  try {
480
567
  const { action, data: dataConst } = getActionData(request);
481
568
  let data = dataConst;
@@ -489,28 +576,56 @@ export default function createServer(nymph: Nymph) {
489
576
  }
490
577
  const deleted = [];
491
578
  let failures = false;
579
+ let hadSuccess = false;
492
580
  let invalidRequest = false;
581
+ let notfound = false;
493
582
  let lastException = null;
494
- for (let delEnt of data) {
583
+ for (let entData of data) {
584
+ if (entData.guid && entData.guid.length != 24) {
585
+ invalidRequest = true;
586
+ continue;
587
+ }
588
+ let EntityClass: EntityConstructor;
495
589
  try {
496
- const guid = delEnt.guid;
497
- if (!delEnt.guid) {
498
- invalidRequest = true;
499
- continue;
500
- }
501
- if (await nymph.deleteEntityByID(guid, delEnt.class)) {
502
- deleted.push(guid);
590
+ EntityClass = response.locals.nymph.getEntityClass(entData.class);
591
+ } catch (e: any) {
592
+ invalidRequest = true;
593
+ failures = true;
594
+ continue;
595
+ }
596
+ let entity: EntityInterface | null;
597
+ try {
598
+ entity = await response.locals.nymph.getEntity(
599
+ { class: EntityClass },
600
+ { type: '&', guid: entData.guid }
601
+ );
602
+ } catch (e: any) {
603
+ lastException = e;
604
+ failures = true;
605
+ continue;
606
+ }
607
+ if (!entity) {
608
+ notfound = true;
609
+ failures = true;
610
+ continue;
611
+ }
612
+ try {
613
+ if (await entity.$delete()) {
614
+ deleted.push(entData.guid);
615
+ hadSuccess = true;
503
616
  } else {
504
617
  failures = true;
505
618
  }
506
619
  } catch (e: any) {
507
- failures = true;
508
620
  lastException = e;
621
+ failures = true;
509
622
  }
510
623
  }
511
624
  if (deleted.length === 0) {
512
625
  if (invalidRequest || !failures) {
513
- httpError(response, 400, 'Bad Request');
626
+ httpError(response, 400, 'Bad Request', lastException);
627
+ } else if (notfound) {
628
+ httpError(response, 404, 'Not Found');
514
629
  } else {
515
630
  httpError(response, 500, 'Internal Server Error', lastException);
516
631
  }
@@ -528,9 +643,20 @@ export default function createServer(nymph: Nymph) {
528
643
  httpError(response, 400, 'Bad Request');
529
644
  return;
530
645
  }
646
+ if (response.locals.nymph.tilmeld) {
647
+ if (
648
+ !response.locals.nymph.tilmeld.checkClientUIDPermissions(
649
+ data,
650
+ TilmeldAccessLevels.FULL_ACCESS
651
+ )
652
+ ) {
653
+ httpError(response, 403, 'Forbidden');
654
+ return;
655
+ }
656
+ }
531
657
  let result: boolean;
532
658
  try {
533
- result = await nymph.deleteUID(data);
659
+ result = await response.locals.nymph.deleteUID(data);
534
660
  } catch (e: any) {
535
661
  httpError(response, 500, 'Internal Server Error', e);
536
662
  return;
@@ -554,6 +680,7 @@ export default function createServer(nymph: Nymph) {
554
680
 
555
681
  async function loadEntity(
556
682
  entityData: EntityJson | EntityPatch,
683
+ nymph: Nymph,
557
684
  patch = false,
558
685
  allowConflict = false
559
686
  ): Promise<EntityInterface> {
@@ -574,7 +701,7 @@ export default function createServer(nymph: Nymph) {
574
701
  }
575
702
  );
576
703
  if (entity === null) {
577
- throw new Error('Entity is not found.');
704
+ throw new Error(NOT_FOUND_ERROR);
578
705
  }
579
706
  } else {
580
707
  entity = await EntityClass.factory();
@@ -595,7 +722,7 @@ export default function createServer(nymph: Nymph) {
595
722
  * @param item The item to check.
596
723
  * @returns The item, converted.
597
724
  */
598
- function referencesToEntities(item: any): any {
725
+ function referencesToEntities(item: any, nymph: Nymph): any {
599
726
  if (Array.isArray(item)) {
600
727
  if (item.length === 3 && item[0] === 'nymph_entity_reference') {
601
728
  try {
@@ -605,12 +732,12 @@ export default function createServer(nymph: Nymph) {
605
732
  return item;
606
733
  }
607
734
  }
608
- return item.map((entry) => referencesToEntities(entry));
735
+ return item.map((entry) => referencesToEntities(entry, nymph));
609
736
  } else if (typeof item === 'object' && !(item instanceof Entity)) {
610
737
  // Only do this for non-entity objects.
611
738
  const newItem: { [k: string]: any } = {};
612
739
  for (let curProperty in item) {
613
- newItem[curProperty] = referencesToEntities(item[curProperty]);
740
+ newItem[curProperty] = referencesToEntities(item[curProperty], nymph);
614
741
  }
615
742
  return newItem;
616
743
  }
@@ -625,7 +752,7 @@ export default function createServer(nymph: Nymph) {
625
752
  * @param error An optional exception object to report.
626
753
  */
627
754
  function httpError(
628
- res: Response,
755
+ res: NymphResponse,
629
756
  errorCode: number,
630
757
  message: string,
631
758
  error?: Error
@@ -1,8 +1,4 @@
1
- import nymphServer, {
2
- Entity as EntityServer,
3
- EntityInvalidDataError,
4
- } from '@nymphjs/nymph';
5
- import { Nymph } from '@nymphjs/client-node';
1
+ import { Entity as EntityServer, EntityInvalidDataError } from '@nymphjs/nymph';
6
2
  import { Entity } from '@nymphjs/client';
7
3
 
8
4
  export type EmployeeBaseData<T> = {
@@ -98,7 +94,7 @@ export class EmployeeModel extends EntityServer<EmployeeModelData> {
98
94
  }
99
95
  // Generate employee ID.
100
96
  if (this.$data.id == null) {
101
- this.$data.id = (await nymphServer.newUID('employee')) ?? undefined;
97
+ this.$data.id = (await this.$nymph.newUID('employee')) ?? undefined;
102
98
  }
103
99
  return await super.$save();
104
100
  }
@@ -130,8 +126,6 @@ export class EmployeeModel extends EntityServer<EmployeeModelData> {
130
126
  }
131
127
  }
132
128
 
133
- nymphServer.setEntityClass(EmployeeModel.class, EmployeeModel);
134
-
135
129
  export class BadFunctionCallError extends Error {
136
130
  constructor(message: string) {
137
131
  super(message);
@@ -188,5 +182,4 @@ export class Employee extends Entity<EmployeeData> {
188
182
  }
189
183
  }
190
184
 
191
- Nymph.setEntityClass(Employee.class, Employee);
192
185
  export default Employee;