@fjell/express-router 4.4.3 → 4.4.5

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/ItemRouter.ts CHANGED
@@ -3,13 +3,12 @@ import {
3
3
  cPK,
4
4
  Item,
5
5
  ItemEvent,
6
- ItemProperties,
7
6
  LocKey,
8
7
  LocKeyArray,
9
8
  PriKey,
10
9
  validatePK
11
10
  } from "@fjell/core";
12
- import { NotFoundError, Operations } from "@fjell/lib";
11
+ import { Instance, NotFoundError } from "@fjell/lib";
13
12
  import deepmerge from "deepmerge";
14
13
  import { Request, Response, Router } from "express";
15
14
  import LibLogger from "./logger";
@@ -25,14 +24,14 @@ export class ItemRouter<
25
24
  L5 extends string = never
26
25
  > {
27
26
 
28
- protected lib: Operations<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>;
27
+ protected lib: Instance<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>;
29
28
  private keyType: S;
30
29
  protected options: ItemRouterOptions;
31
30
  private childRouters: Record<string, Router> = {};
32
- private logger;
31
+ protected logger;
33
32
 
34
33
  constructor(
35
- lib: Operations<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>,
34
+ lib: Instance<Item<S, L1, L2, L3, L4, L5>, S, L1, L2, L3, L4, L5>,
36
35
  keyType: S,
37
36
  options: ItemRouterOptions = {}
38
37
  ) {
@@ -75,23 +74,74 @@ export class ItemRouter<
75
74
  throw new Error('Method not implemented in an abstract router');
76
75
  }
77
76
 
77
+ protected postAllAction = async (req: Request, res: Response) => {
78
+ const libOptions = this.lib.definition.options;
79
+ const libOperations = this.lib.operations;
80
+ this.logger.debug('Posting All Action', { query: req?.query, params: req?.params, locals: res?.locals });
81
+ const allActionKey = req.path.substring(req.path.lastIndexOf('/') + 1);
82
+ if (!libOptions.allActions) {
83
+ this.logger.error('Item Actions are not configured');
84
+ res.status(500).json({ error: 'Item Actions are not configured' });
85
+ return;
86
+ }
87
+ const allAction = libOptions.allActions[allActionKey];
88
+ if (!allAction) {
89
+ this.logger.error('All Action is not configured', { allActionKey });
90
+ res.status(500).json({ error: 'Item Action is not configured' });
91
+ return;
92
+ }
93
+ try {
94
+ res.json(await libOperations.allAction(allActionKey, req.body));
95
+ } catch (err: any) {
96
+ this.logger.error('Error in All Action', { message: err?.message, stack: err?.stack });
97
+ res.status(500).json(err);
98
+ }
99
+ }
100
+
101
+ protected getAllFacet = async (req: Request, res: Response) => {
102
+ const libOptions = this.lib.definition.options;
103
+ const libOperations = this.lib.operations;
104
+ this.logger.debug('Getting All Facet', { query: req?.query, params: req?.params, locals: res?.locals });
105
+ const facetKey = req.path.substring(req.path.lastIndexOf('/') + 1);
106
+ if (!libOptions.allFacets) {
107
+ this.logger.error('Item Facets are not configured');
108
+ res.status(500).json({ error: 'Item Facets are not configured' });
109
+ return;
110
+ }
111
+ const facet = libOptions.allFacets[facetKey];
112
+ if (!facet) {
113
+ this.logger.error('Item Facet is not configured', { facetKey });
114
+ res.status(500).json({ error: 'Item Facet is not configured' });
115
+ return;
116
+ }
117
+ try {
118
+ const combinedQueryParams = { ...req.query, ...req.params } as Record<string, string | number | boolean | Date | Array<string | number | boolean | Date>>;
119
+ res.json(await libOperations.allFacet(facetKey, combinedQueryParams));
120
+ } catch (err: any) {
121
+ this.logger.error('Error in All Facet', { message: err?.message, stack: err?.stack });
122
+ res.status(500).json(err);
123
+ }
124
+ }
125
+
78
126
  protected postItemAction = async (req: Request, res: Response) => {
79
- this.logger.default('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
127
+ const libOptions = this.lib.definition.options;
128
+ const libOperations = this.lib.operations;
129
+ this.logger.debug('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
80
130
  const ik = this.getIk(res);
81
131
  const actionKey = req.path.substring(req.path.lastIndexOf('/') + 1);
82
- if (!this.lib.actions) {
132
+ if (!libOptions.actions) {
83
133
  this.logger.error('Item Actions are not configured');
84
134
  res.status(500).json({ error: 'Item Actions are not configured' });
85
135
  return;
86
136
  }
87
- const action = this.lib.actions[actionKey];
137
+ const action = libOptions.actions[actionKey];
88
138
  if (!action) {
89
139
  this.logger.error('Item Action is not configured', { actionKey });
90
140
  res.status(500).json({ error: 'Item Action is not configured' });
91
141
  return;
92
142
  }
93
143
  try {
94
- res.json(await this.lib.action(ik, actionKey, req.body));
144
+ res.json(await libOperations.action(ik, actionKey, req.body));
95
145
  } catch (err: any) {
96
146
  this.logger.error('Error in Item Action', { message: err?.message, stack: err?.stack });
97
147
  res.status(500).json(err);
@@ -99,22 +149,25 @@ export class ItemRouter<
99
149
  }
100
150
 
101
151
  protected getItemFacet = async (req: Request, res: Response) => {
102
- this.logger.default('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
152
+ const libOptions = this.lib.definition.options;
153
+ const libOperations = this.lib.operations;
154
+ this.logger.debug('Getting Item', { query: req?.query, params: req?.params, locals: res?.locals });
103
155
  const ik = this.getIk(res);
104
156
  const facetKey = req.path.substring(req.path.lastIndexOf('/') + 1);
105
- if (!this.lib.facets) {
157
+ if (!libOptions.facets) {
106
158
  this.logger.error('Item Facets are not configured');
107
159
  res.status(500).json({ error: 'Item Facets are not configured' });
108
160
  return;
109
161
  }
110
- const facet = this.lib.facets[facetKey];
162
+ const facet = libOptions.facets[facetKey];
111
163
  if (!facet) {
112
164
  this.logger.error('Item Facet is not configured', { facetKey });
113
165
  res.status(500).json({ error: 'Item Facet is not configured' });
114
166
  return;
115
167
  }
116
168
  try {
117
- res.json(await this.lib.facet(ik, facetKey, req.params));
169
+ const combinedQueryParams = { ...req.query, ...req.params } as Record<string, string | number | boolean | Date | Array<string | number | boolean | Date>>;
170
+ res.json(await libOperations.facet(ik, facetKey, combinedQueryParams));
118
171
  } catch (err: any) {
119
172
  this.logger.error('Error in Item Facet', { message: err?.message, stack: err?.stack });
120
173
  res.status(500).json(err);
@@ -122,44 +175,53 @@ export class ItemRouter<
122
175
  }
123
176
 
124
177
  private configure = (router: Router) => {
125
- this.logger.default('Configuring Router', { pkType: this.getPkType() });
178
+ const libOptions = this.lib.definition.options;
179
+ this.logger.debug('Configuring Router', { pkType: this.getPkType() });
126
180
  router.get('/', this.findItems);
127
181
  router.post('/', this.createItem);
128
182
 
129
- // const allActions = this.configureAllActions();
130
- // this.logger.debug('All Actions supplied to Router', { allActions });
131
- // if (allActions) {
132
- // Object.keys(allActions).forEach((actionKey) => {
133
- // this.logger.default('Configuring All Action', { actionKey });
134
- // // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
135
- // router.post(`/${actionKey}`, ...allActions[actionKey]);
136
- // });
137
- // }
183
+ this.logger.default('All Actions supplied to Router', { allActions: libOptions.allActions });
184
+ if (libOptions.allActions) {
185
+ Object.keys(libOptions.allActions).forEach((actionKey) => {
186
+ this.logger.debug('Configuring All Action %s', actionKey);
187
+ // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
188
+ router.post(`/${actionKey}`, this.postAllAction);
189
+ });
190
+ }
191
+
192
+ this.logger.default('All Facets supplied to Router', { allFacets: libOptions.allFacets });
193
+ if (libOptions.allFacets) {
194
+ Object.keys(libOptions.allFacets).forEach((facetKey) => {
195
+ this.logger.debug('Configuring All Facet %s', facetKey);
196
+ // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
197
+ router.get(`/${facetKey}`, this.getAllFacet);
198
+ });
199
+ }
138
200
 
139
201
  const itemRouter = Router();
140
202
  itemRouter.get('/', this.getItem);
141
203
  itemRouter.put('/', this.updateItem);
142
204
  itemRouter.delete('/', this.deleteItem);
143
205
 
144
- this.logger.debug('Item Actions supplied to Router', { itemActions: this.lib.actions });
145
- if (this.lib.actions) {
146
- Object.keys(this.lib.actions).forEach((actionKey) => {
147
- this.logger.default('Configuring Item Action', { actionKey });
206
+ this.logger.default('Item Actions supplied to Router', { itemActions: libOptions.actions });
207
+ if (libOptions.actions) {
208
+ Object.keys(libOptions.actions).forEach((actionKey) => {
209
+ this.logger.debug('Configuring Item Action %s', actionKey);
148
210
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
149
211
  itemRouter.post(`/${actionKey}`, this.postItemAction)
150
212
  });
151
213
  }
152
214
 
153
- this.logger.debug('Item Facets supplied to Router', { itemFacets: this.lib.facets });
154
- if (this.lib.facets) {
155
- Object.keys(this.lib.facets).forEach((facetKey) => {
156
- this.logger.default('Configuring Item Facet', { facetKey });
215
+ this.logger.default('Item Facets supplied to Router', { itemFacets: libOptions.facets });
216
+ if (libOptions.facets) {
217
+ Object.keys(libOptions.facets).forEach((facetKey) => {
218
+ this.logger.debug('Configuring Item Facet %s', facetKey);
157
219
  // TODO: Ok, this is a bit of a hack, but we need to customize the types of the request handlers
158
220
  itemRouter.get(`/${facetKey}`, this.getItemFacet)
159
221
  });
160
222
  }
161
223
 
162
- this.logger.default('Configuring Item Operations under PK Param', { pkParam: this.getPkParam() });
224
+ this.logger.debug('Configuring Item Operations under PK Param %s', this.getPkParam());
163
225
  router.use(`/:${this.getPkParam()}`, this.validatePrimaryKeyValue, itemRouter);
164
226
 
165
227
  if (this.childRouters) {
@@ -174,14 +236,14 @@ export class ItemRouter<
174
236
  res.locals[this.getPkParam()] = pkParamValue;
175
237
  next();
176
238
  } else {
177
- this.logger.error('Invalid Primary Key', { pkParamValue, path: req?.path });
178
- res.status(500).json({ error: 'Invalid Primary Key', path: req?.path });
239
+ this.logger.error('Invalid Primary Key', { pkParamValue, path: req?.originalUrl });
240
+ res.status(500).json({ error: 'Invalid Primary Key', path: req?.originalUrl });
179
241
  }
180
242
  }
181
243
 
182
244
  private configureChildRouters = (router: Router, childRouters: Record<string, Router>) => {
183
245
  for (const path in childRouters) {
184
- this.logger.default('Configuring Child Router at Path', { path });
246
+ this.logger.debug('Configuring Child Router at Path %s', path);
185
247
 
186
248
  router.use(`/${path}`, childRouters[path]);
187
249
  }
@@ -208,14 +270,16 @@ export class ItemRouter<
208
270
  // TODO: Probably a better way to do this, but this postCreate hook only needs the item.
209
271
  /* istanbul ignore next */
210
272
  public postCreateItem = async (item: Item<S, L1, L2, L3, L4, L5>): Promise<Item<S, L1, L2, L3, L4, L5>> => {
211
- this.logger.default('Post Create Item', { item });
273
+ this.logger.debug('Post Create Item', { item });
212
274
  return item;
213
275
  };
214
276
 
215
277
  protected deleteItem = async (req: Request, res: Response): Promise<void> => {
216
- this.logger.default('Deleting Item', { query: req.query, params: req.params, locals: res.locals });
278
+ const libOperations = this.lib.operations;
279
+
280
+ this.logger.debug('Deleting Item', { query: req.query, params: req.params, locals: res.locals });
217
281
  const ik = this.getIk(res);
218
- const removedItem = await this.lib.remove(ik);
282
+ const removedItem = await libOperations.remove(ik);
219
283
  const item = validatePK(removedItem, this.getPkType());
220
284
  res.json(item);
221
285
  };
@@ -228,11 +292,12 @@ export class ItemRouter<
228
292
  /* eslint-enable */
229
293
 
230
294
  protected getItem = async (req: Request, res: Response) => {
231
- this.logger.default('Getting Item', { query: req.query, params: req.params, locals: res.locals });
295
+ const libOperations = this.lib.operations;
296
+ this.logger.debug('Getting Item', { query: req.query, params: req.params, locals: res.locals });
232
297
  const ik = this.getIk(res);
233
298
  try {
234
299
  // TODO: What error does validate PK throw, when can that fail?
235
- const item = validatePK(await this.lib.get(ik), this.getPkType());
300
+ const item = validatePK(await libOperations.get(ik), this.getPkType());
236
301
  res.json(item);
237
302
  } catch (err: any) {
238
303
  if (err instanceof NotFoundError) {
@@ -252,18 +317,18 @@ export class ItemRouter<
252
317
  }
253
318
 
254
319
  protected updateItem = async (req: Request, res: Response) => {
255
- this.logger.default('Updating Item',
320
+ const libOperations = this.lib.operations;
321
+ this.logger.debug('Updating Item',
256
322
  { body: req?.body, query: req?.query, params: req?.params, locals: res?.locals });
257
323
  const ik = this.getIk(res);
258
- const itemToUpdate = this.convertDates(req.body as ItemProperties<S, L1, L2, L3, L4, L5>);
259
- const retItem = validatePK(await this.lib.update(ik, itemToUpdate), this.getPkType());
324
+ const itemToUpdate = this.convertDates(req.body as Partial<Item<S, L1, L2, L3, L4, L5>>);
325
+ const retItem = validatePK(await libOperations.update(ik, itemToUpdate), this.getPkType());
260
326
  res.json(retItem);
261
327
  };
262
328
 
263
- public convertDates = (item: Item<S, L1, L2, L3, L4, L5> | ItemProperties<S, L1, L2, L3, L4, L5>):
264
- Item<S, L1, L2, L3, L4, L5> | ItemProperties<S, L1, L2, L3, L4, L5> => {
329
+ public convertDates = (item: Partial<Item<S, L1, L2, L3, L4, L5>>): Partial<Item<S, L1, L2, L3, L4, L5>> => {
265
330
  const events = item.events as Record<string, ItemEvent>;
266
- this.logger.default('Converting Dates', { item });
331
+ this.logger.debug('Converting Dates', { item });
267
332
  if (events) {
268
333
  Object.keys(events).forEach((key: string) => {
269
334
  Object.assign(events, {
@@ -2,9 +2,6 @@ import { Item, ItemQuery, paramsToQuery, PriKey, QueryParams, validatePK } from
2
2
  import { Primary } from "@fjell/lib";
3
3
  import { ItemRouter, ItemRouterOptions } from "@/ItemRouter";
4
4
  import { Request, Response } from "express";
5
- import LibLogger from "@/logger";
6
-
7
- const logger = LibLogger.get('PItemRouter');
8
5
 
9
6
  interface ParsedQuery {
10
7
  [key: string]: undefined | string | string[] | ParsedQuery | ParsedQuery[];
@@ -12,7 +9,7 @@ interface ParsedQuery {
12
9
 
13
10
  export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter<S> {
14
11
 
15
- constructor(lib: Primary.Operations<T, S>, keyType: S, options: ItemRouterOptions = {}) {
12
+ constructor(lib: Primary.Instance<T, S>, keyType: S, options: ItemRouterOptions = {}) {
16
13
  super(lib as any, keyType, options);
17
14
  }
18
15
 
@@ -22,16 +19,18 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
22
19
  }
23
20
 
24
21
  public createItem = async (req: Request, res: Response) => {
25
- logger.default('Creating Item 2', { body: req.body, query: req.query, params: req.params, locals: res.locals });
22
+ const libOperations = this.lib.operations;
23
+ this.logger.default('Creating Item', { body: req.body, query: req.query, params: req.params, locals: res.locals });
26
24
  const itemToCreate = this.convertDates(req.body as Item<S>);
27
- let item =
28
- validatePK(await this.lib.create(itemToCreate), this.getPkType()) as Item<S>;
25
+ let item = validatePK(await libOperations.create(itemToCreate), this.getPkType()) as Item<S>;
29
26
  item = await this.postCreateItem(item);
27
+ this.logger.default('Created Item %j', item);
30
28
  res.json(item);
31
29
  };
32
30
 
33
31
  protected findItems = async (req: Request, res: Response) => {
34
- logger.default('Finding Items', { query: req.query, params: req.params, locals: res.locals });
32
+ const libOperations = this.lib.operations;
33
+ this.logger.default('Finding Items', { query: req.query, params: req.params, locals: res.locals });
35
34
 
36
35
  let items: Item<S>[] = [];
37
36
 
@@ -42,19 +41,19 @@ export class PItemRouter<T extends Item<S>, S extends string> extends ItemRouter
42
41
 
43
42
  if (finder) {
44
43
  // If finder is defined? Call a finder.
45
- logger.default('Finding Items with a finder', { finder, finderParams, one });
44
+ this.logger.default('Finding Items with Finder %s %j one:%s', finder, finderParams, one);
46
45
 
47
46
  if (one === 'true') {
48
47
  const item = await (this.lib as any).findOne(finder, JSON.parse(finderParams));
49
48
  items = item ? [item] : [];
50
49
  } else {
51
- items = await this.lib.find(finder, JSON.parse(finderParams));
50
+ items = await libOperations.find(finder, JSON.parse(finderParams));
52
51
  }
53
52
  } else {
54
- logger.default('Finding Items with a query', { query: req.query });
55
53
  // TODO: This is once of the more important places to perform some validaation and feedback
56
54
  const itemQuery: ItemQuery = paramsToQuery(req.query as QueryParams);
57
- items = await this.lib.all(itemQuery);
55
+ this.logger.default('Finding Items with a query %j', itemQuery);
56
+ items = await libOperations.all(itemQuery);
58
57
  }
59
58
 
60
59
  res.json(items.map((item: Item<S>) => validatePK(item, this.getPkType())));
@@ -0,0 +1,65 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable no-undefined */
3
+ export const clean = (obj: any) => {
4
+ return Object.fromEntries(
5
+ Object.entries(obj).filter(([_, v]) => v !== undefined)
6
+ );
7
+ }
8
+
9
+ //Recursive implementation of jSON.stringify;
10
+ export const stringifyJSON = function (obj: any, visited: Set<any> = new Set()): string {
11
+ const arrOfKeyVals: string[] = [];
12
+ const arrVals: string[] = [];
13
+ let objKeys: string[] = [];
14
+
15
+ /*********CHECK FOR PRIMITIVE TYPES**********/
16
+ if (typeof obj === 'number' || typeof obj === 'boolean' || obj === null)
17
+ return '' + obj;
18
+ else if (typeof obj === 'string')
19
+ return '"' + obj + '"';
20
+
21
+ /*********DETECT CIRCULAR REFERENCES**********/
22
+ if (obj instanceof Object && visited.has(obj)) {
23
+ return '"(circular)"';
24
+ }
25
+
26
+ /*********CHECK FOR ARRAY**********/
27
+ else if (Array.isArray(obj)) {
28
+ //check for empty array
29
+ if (obj[0] === undefined)
30
+ return '[]';
31
+ else {
32
+ // Add array to visited before processing its elements
33
+ visited.add(obj);
34
+ obj.forEach(function (el) {
35
+ arrVals.push(stringifyJSON(el, visited));
36
+ });
37
+ return '[' + arrVals + ']';
38
+ }
39
+ }
40
+ /*********CHECK FOR OBJECT**********/
41
+ else if (obj instanceof Object) {
42
+ // Add object to visited before processing its properties
43
+ visited.add(obj);
44
+ //get object keys
45
+ objKeys = Object.keys(obj);
46
+ //set key output;
47
+ objKeys.forEach(function (key) {
48
+ const keyOut = '"' + key + '":';
49
+ const keyValOut = obj[key];
50
+ //skip functions and undefined properties
51
+ if (keyValOut instanceof Function || keyValOut === undefined)
52
+ return; // Skip this entry entirely instead of pushing an empty string
53
+ else if (typeof keyValOut === 'string')
54
+ arrOfKeyVals.push(keyOut + '"' + keyValOut + '"');
55
+ else if (typeof keyValOut === 'boolean' || typeof keyValOut === 'number' || keyValOut === null)
56
+ arrOfKeyVals.push(keyOut + keyValOut);
57
+ //check for nested objects, call recursively until no more objects
58
+ else if (keyValOut instanceof Object) {
59
+ arrOfKeyVals.push(keyOut + stringifyJSON(keyValOut, visited));
60
+ }
61
+ });
62
+ return '{' + arrOfKeyVals + '}';
63
+ }
64
+ return '';
65
+ };