axe-api 0.17.4 → 0.19.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.
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- node-version: [14.x, 16.x]
14
+ node-version: [14.x, 16.x, 17.x]
15
15
  database: [mysql8, mysql57, "postgres"]
16
16
  steps:
17
17
  - uses: actions/checkout@v2
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- node-version: [14.x, 16.x]
14
+ node-version: [14.x, 16.x, 17.x]
15
15
  steps:
16
16
  - uses: actions/checkout@v2
17
17
  - name: Use Node.js ${{ matrix.node-version }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Release Notes
2
2
 
3
+ ## [0.19.0 (2021-12-05)](https://github.com/axe-api/axe-api/compare/0.19.0...0.18.1)
4
+
5
+ ### Fixed
6
+
7
+ - [#110](https://github.com/axe-api/axe-api/issues/110)
8
+
9
+ ### Enhancements
10
+
11
+ - [#106](https://github.com/axe-api/axe-api/issues/106)
12
+
13
+ ## [0.18.1 (2021-12-02)](https://github.com/axe-api/axe-api/compare/0.18.1...0.18.0)
14
+
15
+ ### Fixed
16
+
17
+ - [#117](https://github.com/axe-api/axe-api/issues/117)
18
+
19
+ ## [0.18.0 (2021-11-30)](https://github.com/axe-api/axe-api/compare/0.18.0...0.17.5)
20
+
21
+ ### Fixed
22
+
23
+ - [#115](https://github.com/axe-api/axe-api/issues/115)
24
+ - [#114](https://github.com/axe-api/axe-api/issues/114)
25
+
26
+ ### Enhancements
27
+
28
+ - [#113](https://github.com/axe-api/axe-api/issues/113)
29
+ - [#107](https://github.com/axe-api/axe-api/issues/107)
30
+ - [#108](https://github.com/axe-api/axe-api/issues/108)
31
+
32
+ ## [0.17.5 (2021-11-27)](https://github.com/axe-api/axe-api/compare/0.17.5...0.17.4)
33
+
34
+ ### Fixed
35
+
36
+ - [#111](https://github.com/axe-api/axe-api/issues/111)
37
+
3
38
  ## [0.17.4 (2021-10-28)](https://github.com/axe-api/axe-api/compare/0.17.4...0.17.3)
4
39
 
5
40
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axe-api",
3
- "version": "0.17.4",
3
+ "version": "0.19.0",
4
4
  "description": "AXE API is a simple tool which has been created based on Express and Knex.js to create Rest APIs quickly.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/constants.js CHANGED
@@ -13,6 +13,7 @@ const HOOK_FUNCTIONS = {
13
13
  onBeforeDeleteQuery: "onBeforeDeleteQuery",
14
14
  onBeforeDelete: "onBeforeDelete",
15
15
  onBeforePaginate: "onBeforePaginate",
16
+ onBeforeAll: "onBeforeAll",
16
17
  onBeforeShow: "onBeforeShow",
17
18
  onAfterInsert: "onAfterInsert",
18
19
  onAfterUpdateQuery: "onAfterUpdateQuery",
@@ -20,6 +21,7 @@ const HOOK_FUNCTIONS = {
20
21
  onAfterDeleteQuery: "onAfterDeleteQuery",
21
22
  onAfterDelete: "onAfterDelete",
22
23
  onAfterPaginate: "onAfterPaginate",
24
+ onAfterAll: "onAfterAll",
23
25
  onAfterShow: "onAfterShow",
24
26
  };
25
27
 
@@ -52,7 +54,8 @@ const HANDLERS = {
52
54
  SHOW: "show",
53
55
  UPDATE: "update",
54
56
  DELETE: "destroy",
55
- AUTOSAVE: "autosave",
57
+ PATCH: "patch",
58
+ ALL: "all",
56
59
  };
57
60
 
58
61
  const DEFAULT_HANDLERS = [
@@ -60,6 +63,7 @@ const DEFAULT_HANDLERS = [
60
63
  HANDLERS.PAGINATE,
61
64
  HANDLERS.SHOW,
62
65
  HANDLERS.UPDATE,
66
+ HANDLERS.PATCH,
63
67
  HANDLERS.DELETE,
64
68
  ];
65
69
 
@@ -68,35 +72,41 @@ const HTTP_METHODS = {
68
72
  GET: "GET",
69
73
  PUT: "PUT",
70
74
  DELETE: "DELETE",
75
+ PATCH: "PATCH",
71
76
  };
72
77
 
73
78
  const API_ROUTE_TEMPLATES = {
74
79
  [HANDLERS.INSERT]: {
75
- url: (parentUrl, resource) => `/api/${parentUrl}${resource}`,
80
+ url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
76
81
  method: HTTP_METHODS.POST,
77
82
  },
78
83
  [HANDLERS.PAGINATE]: {
79
- url: (parentUrl, resource) => `/api/${parentUrl}${resource}`,
84
+ url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
85
+ method: HTTP_METHODS.GET,
86
+ },
87
+ [HANDLERS.ALL]: {
88
+ url: (prefix, parentUrl, resource) =>
89
+ `/${prefix}/${parentUrl}${resource}/all`,
80
90
  method: HTTP_METHODS.GET,
81
91
  },
82
92
  [HANDLERS.SHOW]: {
83
- url: (parentUrl, resource, primaryKey) =>
84
- `/api/${parentUrl}${resource}/:${primaryKey}`,
93
+ url: (prefix, parentUrl, resource, primaryKey) =>
94
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
85
95
  method: HTTP_METHODS.GET,
86
96
  },
87
97
  [HANDLERS.UPDATE]: {
88
- url: (parentUrl, resource, primaryKey) =>
89
- `/api/${parentUrl}${resource}/:${primaryKey}`,
98
+ url: (prefix, parentUrl, resource, primaryKey) =>
99
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
90
100
  method: HTTP_METHODS.PUT,
91
101
  },
92
- [HANDLERS.AUTOSAVE]: {
93
- url: (parentUrl, resource, primaryKey) =>
94
- `/api/${parentUrl}${resource}/:${primaryKey}/autosave`,
95
- method: HTTP_METHODS.PUT,
102
+ [HANDLERS.PATCH]: {
103
+ url: (prefix, parentUrl, resource, primaryKey) =>
104
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
105
+ method: HTTP_METHODS.PATCH,
96
106
  },
97
107
  [HANDLERS.DELETE]: {
98
- url: (parentUrl, resource, primaryKey) =>
99
- `/api/${parentUrl}${resource}/:${primaryKey}`,
108
+ url: (prefix, parentUrl, resource, primaryKey) =>
109
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
100
110
  method: HTTP_METHODS.DELETE,
101
111
  },
102
112
  };
package/src/core/Model.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import pluralize from "pluralize";
2
2
  import { snakeCase } from "snake-case";
3
- import { RELATIONSHIPS, HANDLERS } from "./../constants.js";
4
- const { INSERT, SHOW, UPDATE, PAGINATE, DELETE } = HANDLERS;
3
+ import { RELATIONSHIPS, DEFAULT_HANDLERS } from "./../constants.js";
5
4
 
6
5
  class Model {
7
6
  constructor() {
@@ -25,7 +24,7 @@ class Model {
25
24
  }
26
25
 
27
26
  get handlers() {
28
- return [INSERT, SHOW, PAGINATE, UPDATE, DELETE];
27
+ return [...DEFAULT_HANDLERS];
29
28
  }
30
29
 
31
30
  get middlewares() {
@@ -1,31 +1,13 @@
1
1
  import HttpResponse from "./../core/HttpResponse.js";
2
2
  import { RELATIONSHIPS } from "../constants.js";
3
3
 
4
- const DEFAULT_OPTIONS = {
5
- min_per_page: 1,
6
- max_per_page: 1000,
7
- };
8
-
9
4
  class QueryParser {
10
- constructor({ model, models, options = {} }) {
5
+ constructor({ model, models }) {
11
6
  this.model = model;
12
7
  this.models = models;
13
8
  this.createdJoins = [];
14
9
  this.relationColumns = [];
15
10
  this.usedConditionColumns = new Set();
16
- this.options = { ...DEFAULT_OPTIONS, ...options };
17
-
18
- if (isNaN(this.options.min_per_page) || this.options.min_per_page < 1) {
19
- throw new Error(
20
- `You set unacceptable query parse option (min_per_page): ${this.options.min_per_page}`
21
- );
22
- }
23
-
24
- if (isNaN(this.options.max_per_page) || this.options.max_per_page > 10000) {
25
- throw new Error(
26
- `You set unacceptable query parse option (max_per_page): ${this.options.max_per_page}`
27
- );
28
- }
29
11
  }
30
12
 
31
13
  applyFields(query, fields) {
@@ -109,10 +91,9 @@ class QueryParser {
109
91
  });
110
92
 
111
93
  if (undefinedColumns.length > 0) {
112
- throw new HttpResponse(
113
- 400,
114
- `Undefined column names: ${undefinedColumns.join(",")}`
115
- );
94
+ throw new HttpResponse(400, {
95
+ message: `Undefined column names: ${undefinedColumns.join(",")}`,
96
+ });
116
97
  }
117
98
 
118
99
  return conditions;
@@ -215,7 +196,9 @@ class QueryParser {
215
196
  try {
216
197
  sections.q = JSON.parse(queryContent);
217
198
  } catch (err) {
218
- throw new Error(`Unacceptable query string: \n ${queryContent}`);
199
+ throw new HttpResponse(400, {
200
+ message: `Unacceptable query string: ${queryContent}`,
201
+ });
219
202
  }
220
203
  }
221
204
 
@@ -247,16 +230,8 @@ class QueryParser {
247
230
  _parsePerPage(content) {
248
231
  const value = parseInt(content);
249
232
 
250
- if (isNaN(value)) {
251
- return this.options.min_per_page;
252
- }
253
-
254
- if (value <= this.options.min_per_page) {
255
- return this.options.min_per_page;
256
- }
257
-
258
- if (value > this.options.max_per_page) {
259
- return this.options.max_per_page;
233
+ if (isNaN(value) || value <= 1 || value > 10000) {
234
+ return 10;
260
235
  }
261
236
 
262
237
  return value;
@@ -325,19 +300,24 @@ class QueryParser {
325
300
  return null;
326
301
  }
327
302
 
303
+ const wheres = [];
304
+ for (const key in content) {
305
+ wheres.push(this._parseConditionObject(content, key));
306
+ }
307
+
308
+ return wheres;
309
+ }
310
+
311
+ _parseConditionObject(content, key) {
328
312
  const where = {
329
313
  prefix: null,
330
314
  model: this.model,
331
315
  table: this.model.instance.table,
332
- field: null,
316
+ field: key,
333
317
  condition: "=",
334
- value: null,
318
+ value: content[key],
335
319
  };
336
320
 
337
- const key = Object.keys(content)[0];
338
- where.field = key;
339
- where.value = content[key];
340
-
341
321
  // Sometimes we can have basic OR operations for queries
342
322
  if (where.field.indexOf("$or.") === 0) {
343
323
  where.prefix = "or";
@@ -349,19 +329,30 @@ class QueryParser {
349
329
  where.field = where.field.replace("$and.", "");
350
330
  }
351
331
 
352
- this._applySpecialCondition(where, "$not", "<>");
353
- this._applySpecialCondition(where, "$gt", ">");
354
- this._applySpecialCondition(where, "$gte", ">=");
355
- this._applySpecialCondition(where, "$lt", "<");
356
- this._applySpecialCondition(where, "$lte", "<=");
357
- this._applySpecialCondition(where, "$like", "LIKE");
358
- this._applySpecialCondition(where, "$notLike", "NOT LIKE");
359
- this._applySpecialCondition(where, "$in", "In");
360
- this._applySpecialCondition(where, "$notIn", "NotIn");
361
- this._applySpecialCondition(where, "$between", "Between");
362
- this._applySpecialCondition(where, "$notBetween", "NotBetween");
363
- this._applySpecialCondition(where, "$null", "Null");
364
- this._applySpecialCondition(where, "$notNull", "NotNull");
332
+ // If there is not any value, it means that we should check nullable values
333
+ if (where.value === null) {
334
+ // If the client wants to see not nullable values
335
+ if (this._hasSpecialStructure(where.field, ".$not")) {
336
+ where.field = where.field.replace(".$not", "");
337
+ where.condition = "NotNull";
338
+ } else {
339
+ // So, it means that the clients wants to see null valus
340
+ where.condition = "Null";
341
+ }
342
+ } else {
343
+ // If there is value, we should check it
344
+ this._applySpecialCondition(where, "$not", "<>");
345
+ this._applySpecialCondition(where, "$gt", ">");
346
+ this._applySpecialCondition(where, "$gte", ">=");
347
+ this._applySpecialCondition(where, "$lt", "<");
348
+ this._applySpecialCondition(where, "$lte", "<=");
349
+ this._applySpecialCondition(where, "$like", "LIKE");
350
+ this._applySpecialCondition(where, "$notLike", "NOT LIKE");
351
+ this._applySpecialCondition(where, "$in", "In");
352
+ this._applySpecialCondition(where, "$notIn", "NotIn");
353
+ this._applySpecialCondition(where, "$between", "Between");
354
+ this._applySpecialCondition(where, "$notBetween", "NotBetween");
355
+ }
365
356
 
366
357
  if (where.condition === "In" || where.condition === "NotIn") {
367
358
  where.value = where.value.split(",");
@@ -371,10 +362,6 @@ class QueryParser {
371
362
  where.value = where.value.split(":");
372
363
  }
373
364
 
374
- if (where.condition === "Null" || where.condition === "NotNull") {
375
- where.value = null;
376
- }
377
-
378
365
  if (where.condition === "LIKE" || where.condition === "NOT LIKE") {
379
366
  where.value = where.value.replace(/\*/g, "%");
380
367
  }
@@ -389,7 +376,9 @@ class QueryParser {
389
376
  );
390
377
 
391
378
  if (!relation) {
392
- throw new Error(`Unacceptable query field: ${relationName}.${field}`);
379
+ throw new HttpResponse(400, {
380
+ message: `Unacceptable query field: ${relationName}.${field}`,
381
+ });
393
382
  }
394
383
 
395
384
  const relatedModel = this.models.find(
@@ -397,7 +386,9 @@ class QueryParser {
397
386
  );
398
387
 
399
388
  if (!relatedModel) {
400
- throw new Error(`Undefined model name: ${relation.model}`);
389
+ throw new HttpResponse(400, {
390
+ message: `Undefined model name: ${relation.model}`,
391
+ });
401
392
  }
402
393
 
403
394
  where.model = relatedModel;
@@ -504,7 +495,9 @@ class QueryParser {
504
495
  (i) => i.name === item.relationship
505
496
  );
506
497
  if (!relation) {
507
- throw new Error(`Undefined relation: ${item.relationship}`);
498
+ throw new HttpResponse(400, {
499
+ message: `Undefined relation: ${item.relationship}`,
500
+ });
508
501
  }
509
502
 
510
503
  this.relationColumns.push(
@@ -535,11 +528,15 @@ class QueryParser {
535
528
  _shouldBeAcceptableColumn(field) {
536
529
  const regex = /^[0-9,a-z,A-Z_.]+$/;
537
530
  if (!field.match(regex)) {
538
- throw new Error(`Unacceptable field name: ${field}`);
531
+ throw new HttpResponse(400, {
532
+ message: `Unacceptable field name: ${field}`,
533
+ });
539
534
  }
540
535
 
541
536
  if (field.indexOf(".") === 0 || field.indexOf(".") === field.length - 1) {
542
- throw new Error(`You have to define the column specefically: ${field}`);
537
+ throw new HttpResponse(400, {
538
+ message: `You have to define the column specefically: ${field}`,
539
+ });
543
540
  }
544
541
  }
545
542
  }
@@ -0,0 +1,73 @@
1
+ import {
2
+ callHooks,
3
+ getRelatedData,
4
+ filterHiddenFields,
5
+ serializeData,
6
+ addForeignKeyQuery,
7
+ } from "./helpers.js";
8
+ import { HOOK_FUNCTIONS, HANDLERS } from "./../constants.js";
9
+ import QueryParser from "./../core/QueryParser.js";
10
+
11
+ export default async (context) => {
12
+ const { request, response, model, models, trx, relation, parentModel } =
13
+ context;
14
+
15
+ const queryParser = new QueryParser({ model, models });
16
+
17
+ // We should parse URL query string to use as condition in Lucid query
18
+ const conditions = queryParser.get(request.query);
19
+
20
+ // Creating a new database query
21
+ const query = trx.from(model.instance.table);
22
+
23
+ // Users should be able to select some fields to show.
24
+ queryParser.applyFields(query, conditions.fields);
25
+
26
+ // Binding parent id if there is.
27
+ addForeignKeyQuery(request, query, relation, parentModel);
28
+
29
+ // Users should be able to filter records
30
+ queryParser.applyWheres(query, conditions.q);
31
+
32
+ await callHooks(model, HOOK_FUNCTIONS.onBeforeAll, {
33
+ ...context,
34
+ conditions,
35
+ query,
36
+ });
37
+
38
+ // User should be able to select sorting fields and types
39
+ queryParser.applySorting(query, conditions.sort);
40
+
41
+ const result = await query;
42
+
43
+ // We should try to get related data if there is any
44
+ await getRelatedData(
45
+ result.data,
46
+ conditions.with,
47
+ model,
48
+ models,
49
+ trx,
50
+ HANDLERS.ALL,
51
+ request
52
+ );
53
+
54
+ await callHooks(model, HOOK_FUNCTIONS.onAfterAll, {
55
+ ...context,
56
+ result,
57
+ conditions,
58
+ query,
59
+ });
60
+
61
+ // Serializing the data by the model's serialize method
62
+ result.data = await serializeData(
63
+ result.data,
64
+ model.instance.serialize,
65
+ HANDLERS.ALL,
66
+ request
67
+ );
68
+
69
+ // Filtering hidden fields from the response data.
70
+ filterHiddenFields(result.data, model.instance.hiddens);
71
+
72
+ return response.json(result);
73
+ };
@@ -22,7 +22,9 @@ export default async (context) => {
22
22
 
23
23
  let item = await query.first();
24
24
  if (!item) {
25
- throw new HttpResponse(404, `The item is not found on ${model.name}.`);
25
+ throw new HttpResponse(404, {
26
+ message: `The item is not found on ${model.name}.`,
27
+ });
26
28
  }
27
29
 
28
30
  await callHooks(model, HOOK_FUNCTIONS.onAfterDeleteQuery, {
@@ -1,7 +1,7 @@
1
1
  import { RELATIONSHIPS } from "./../constants.js";
2
2
  import { camelCase } from "change-case";
3
3
  import HttpResponse from "./../core/HttpResponse.js";
4
- import IoC from '../core/IoC.js';
4
+ import IoC from "../core/IoC.js";
5
5
 
6
6
  const getInputFromBody = (body, field) => {
7
7
  if (!body) {
@@ -83,7 +83,8 @@ export const getRelatedData = async (
83
83
  model,
84
84
  models,
85
85
  database,
86
- handler
86
+ handler,
87
+ request
87
88
  ) => {
88
89
  if (withArray.length === 0) {
89
90
  return;
@@ -96,10 +97,9 @@ export const getRelatedData = async (
96
97
  (relation) => relation.name === clientQuery.relationship
97
98
  );
98
99
  if (!definedRelation) {
99
- throw new HttpResponse(
100
- 400,
101
- `Undefined relation: ${clientQuery.relationship}`
102
- );
100
+ throw new HttpResponse(400, {
101
+ message: `Undefined relation: ${clientQuery.relationship}`,
102
+ });
103
103
  }
104
104
 
105
105
  // Find the foreign model by the relationship
@@ -164,10 +164,9 @@ export const getRelatedData = async (
164
164
  (column) => !foreignModel.instance.columnNames.includes(column)
165
165
  );
166
166
  if (undefinedColumns.length > 0) {
167
- throw new HttpResponse(
168
- 400,
169
- `Undefined columns: ${undefinedColumns.join(", ")}`
170
- );
167
+ throw new HttpResponse(400, {
168
+ message: `Undefined columns: ${undefinedColumns.join(", ")}`,
169
+ });
171
170
  }
172
171
  }
173
172
 
@@ -189,7 +188,8 @@ export const getRelatedData = async (
189
188
  relatedRecords = await serializeData(
190
189
  relatedRecords,
191
190
  foreignModel.instance.serialize,
192
- handler
191
+ handler,
192
+ request
193
193
  );
194
194
 
195
195
  // We should hide hidden fields if there is any
@@ -203,7 +203,8 @@ export const getRelatedData = async (
203
203
  foreignModel,
204
204
  models,
205
205
  database,
206
- handler
206
+ handler,
207
+ request
207
208
  );
208
209
  }
209
210
 
@@ -243,19 +244,19 @@ export const bindTimestampValues = (formData, columnTypes = [], model) => {
243
244
  }
244
245
  };
245
246
 
246
- const serialize = async (data, callback) => {
247
+ const serialize = async (data, callback, request) => {
247
248
  if (!callback) {
248
249
  return data;
249
250
  }
250
251
 
251
252
  if (Array.isArray(data)) {
252
- return data.map(callback);
253
+ return data.map((item) => callback(item, request));
253
254
  }
254
255
 
255
- return [data].map(callback)[0];
256
- }
256
+ return callback(data, request);
257
+ };
257
258
 
258
- const globalSerializer = async (itemArray, handler) => {
259
+ const globalSerializer = async (itemArray, handler, request) => {
259
260
  const { Application } = await IoC.use("Config");
260
261
 
261
262
  if (!Application.serializers) {
@@ -263,35 +264,43 @@ const globalSerializer = async (itemArray, handler) => {
263
264
  }
264
265
 
265
266
  let callbacks = [];
266
- // Push all runable serializer into callbacks.
267
- Application.serializers.map(configSerializer => {
267
+ // Push all runable serializer into callbacks.
268
+ Application.serializers.map((configSerializer) => {
268
269
  // Serialize data for all requests types.
269
270
  if (typeof configSerializer === "function") {
270
271
  callbacks.push(configSerializer);
271
- return
272
+ return;
272
273
  }
273
274
 
274
275
  // Serialize data with specific handler like "PAGINATE" or "SHOW".
275
- if (typeof configSerializer === "object" && configSerializer.handler.includes(handler)) {
276
+ if (
277
+ typeof configSerializer === "object" &&
278
+ configSerializer.handler.includes(handler)
279
+ ) {
276
280
  // Handle multiple serializer.
277
281
  if (Array.isArray(configSerializer.serializer)) {
278
- configSerializer.serializer.forEach(fn => callbacks.push(fn));
279
- return
282
+ configSerializer.serializer.forEach((fn) => callbacks.push(fn));
283
+ return;
280
284
  }
281
- callbacks.push(configSerializer.serializer)
282
- return
285
+ callbacks.push(configSerializer.serializer);
286
+ return;
283
287
  }
284
- })
288
+ });
285
289
 
286
290
  while (callbacks.length !== 0) {
287
- itemArray = await serialize(itemArray, callbacks.shift());
291
+ itemArray = serialize(itemArray, callbacks.shift(), request);
288
292
  }
289
293
  return itemArray;
290
294
  };
291
295
 
292
- export const serializeData = async (itemArray, modelSerializer, handler) => {
293
- itemArray = await serialize(itemArray, modelSerializer);
294
- itemArray = await globalSerializer(itemArray, handler);
296
+ export const serializeData = async (
297
+ itemArray,
298
+ modelSerializer,
299
+ handler,
300
+ request
301
+ ) => {
302
+ itemArray = serialize(itemArray, modelSerializer, request);
303
+ itemArray = await globalSerializer(itemArray, handler, request);
295
304
  return itemArray;
296
305
  };
297
306
 
@@ -1,8 +1,9 @@
1
- import autosave from "./autosave.js";
1
+ import all from "./all.js";
2
+ import patch from "./patch.js";
2
3
  import store from "./store.js";
3
4
  import show from "./show.js";
4
5
  import paginate from "./paginate.js";
5
6
  import update from "./update.js";
6
7
  import destroy from "./destroy.js";
7
8
 
8
- export default { autosave, store, show, paginate, update, destroy };
9
+ export default { all, patch, store, show, paginate, update, destroy };
@@ -45,7 +45,15 @@ export default async (context) => {
45
45
  });
46
46
 
47
47
  // We should try to get related data if there is any
48
- await getRelatedData(result.data, conditions.with, model, models, trx, HANDLERS.PAGINATE);
48
+ await getRelatedData(
49
+ result.data,
50
+ conditions.with,
51
+ model,
52
+ models,
53
+ trx,
54
+ HANDLERS.PAGINATE,
55
+ request
56
+ );
49
57
 
50
58
  await callHooks(model, HOOK_FUNCTIONS.onAfterPaginate, {
51
59
  ...context,
@@ -55,7 +63,12 @@ export default async (context) => {
55
63
  });
56
64
 
57
65
  // Serializing the data by the model's serialize method
58
- result.data = await serializeData(result.data, model.instance.serialize, HANDLERS.PAGINATE);
66
+ result.data = await serializeData(
67
+ result.data,
68
+ model.instance.serialize,
69
+ HANDLERS.PAGINATE,
70
+ request
71
+ );
59
72
 
60
73
  // Filtering hidden fields from the response data.
61
74
  filterHiddenFields(result.data, model.instance.hiddens);
@@ -11,6 +11,7 @@ import {
11
11
  import Validator from "validatorjs";
12
12
  import { HOOK_FUNCTIONS, TIMESTAMP_COLUMNS } from "./../constants.js";
13
13
  import HttpResponse from "./../core/HttpResponse.js";
14
+ import { HANDLERS } from "./../constants.js";
14
15
 
15
16
  export default async (context) => {
16
17
  const { request, response, model, trx, relation, parentModel } = context;
@@ -29,7 +30,9 @@ export default async (context) => {
29
30
  .where(model.instance.primaryKey, request.params[model.instance.primaryKey])
30
31
  .first();
31
32
  if (!item) {
32
- throw new HttpResponse(404, `The item is not found on ${model.name}.`);
33
+ throw new HttpResponse(404, {
34
+ message: `The item is not found on ${model.name}.`,
35
+ });
33
36
  }
34
37
 
35
38
  await callHooks(model, HOOK_FUNCTIONS.onAfterUpdateQuery, {
@@ -78,7 +81,12 @@ export default async (context) => {
78
81
  });
79
82
 
80
83
  // Serializing the data by the model's serialize method
81
- item = await serializeData(item, model.instance.serialize);
84
+ item = await serializeData(
85
+ item,
86
+ model.instance.serialize,
87
+ HANDLERS.PATCH,
88
+ request
89
+ );
82
90
 
83
91
  // Filtering hidden fields from the response data.
84
92
  filterHiddenFields([item], model.instance.hiddens);
@@ -43,11 +43,21 @@ export default async (context) => {
43
43
 
44
44
  let item = await query.first();
45
45
  if (!item) {
46
- throw new HttpResponse(404, `The item is not found on ${model.name}.`);
46
+ throw new HttpResponse(404, {
47
+ message: `The item is not found on ${model.name}.`,
48
+ });
47
49
  }
48
50
 
49
51
  // We should try to get related data if there is any
50
- await getRelatedData([item], conditions.with, model, models, trx, HANDLERS.SHOW);
52
+ await getRelatedData(
53
+ [item],
54
+ conditions.with,
55
+ model,
56
+ models,
57
+ trx,
58
+ HANDLERS.SHOW,
59
+ request
60
+ );
51
61
 
52
62
  await callHooks(model, HOOK_FUNCTIONS.onAfterShow, {
53
63
  ...context,
@@ -57,7 +67,12 @@ export default async (context) => {
57
67
  });
58
68
 
59
69
  // Serializing the data by the model's serialize method
60
- item = await serializeData(item, model.instance.serialize, HANDLERS.SHOW);
70
+ item = await serializeData(
71
+ item,
72
+ model.instance.serialize,
73
+ HANDLERS.SHOW,
74
+ request
75
+ );
61
76
 
62
77
  // Filtering hidden fields from the response data.
63
78
  filterHiddenFields([item], model.instance.hiddens);
@@ -45,7 +45,7 @@ export default async (context) => {
45
45
 
46
46
  let [insertedPrimaryKeyValue] = await trx(model.instance.table)
47
47
  .insert(formData)
48
- .returning("id");
48
+ .returning(model.instance.primaryKey);
49
49
 
50
50
  // If the user use a special primary key value, we should use that value
51
51
  if (insertedPrimaryKeyValue === 0) {
@@ -63,7 +63,12 @@ export default async (context) => {
63
63
  });
64
64
 
65
65
  // Serializing the data by the model's serialize method
66
- item = await serializeData(item, model.instance.serialize, HANDLERS.INSERT);
66
+ item = await serializeData(
67
+ item,
68
+ model.instance.serialize,
69
+ HANDLERS.INSERT,
70
+ request
71
+ );
67
72
 
68
73
  // Filtering hidden fields from the response data.
69
74
  filterHiddenFields([item], model.instance.hiddens);
@@ -28,7 +28,9 @@ export default async (context) => {
28
28
  .where(model.instance.primaryKey, request.params[model.instance.primaryKey])
29
29
  .first();
30
30
  if (!item) {
31
- throw new HttpResponse(404, `The item is not found on ${model.name}.`);
31
+ throw new HttpResponse(404, {
32
+ message: `The item is not found on ${model.name}.`,
33
+ });
32
34
  }
33
35
 
34
36
  await callHooks(model, HOOK_FUNCTIONS.onAfterUpdateQuery, {
@@ -76,7 +78,12 @@ export default async (context) => {
76
78
  });
77
79
 
78
80
  // Serializing the data by the model's serialize method
79
- item = await serializeData(item, model.instance.serialize, HANDLERS.UPDATE);
81
+ item = await serializeData(
82
+ item,
83
+ model.instance.serialize,
84
+ HANDLERS.UPDATE,
85
+ request
86
+ );
80
87
 
81
88
  // Filtering hidden fields from the response data.
82
89
  filterHiddenFields([item], model.instance.hiddens);
@@ -1,30 +1,11 @@
1
1
  import IoC from "./../core/IoC.js";
2
2
  import pluralize from "pluralize";
3
- import { paramCase } from "change-case";
3
+ import { paramCase, camelCase } from "change-case";
4
4
  import { RELATIONSHIPS, API_ROUTE_TEMPLATES } from "./../constants.js";
5
5
  import Handlers from "./../handlers/index.js";
6
6
 
7
7
  let Config = null;
8
8
 
9
- const handleErrors = (req, res, error) => {
10
- const status = error.type === "HttpResponse" ? error.status : 400;
11
- let errors = error.content ? error.content : error.message;
12
-
13
- if (Config.Application.env === "production") {
14
- errors = "An error occurred!";
15
- }
16
-
17
- const result = {
18
- errors,
19
- };
20
-
21
- if (Config.Application.env === "development") {
22
- result.stack = error.stack;
23
- }
24
-
25
- res.status(status).json(result);
26
- };
27
-
28
9
  const setTransactionOption = (option, handler, defaultValue) => {
29
10
  if (Array.isArray(option)) {
30
11
  if (option.some((i) => i.handler === handler)) {
@@ -53,7 +34,7 @@ const hasTransaction = (config, model, handler) => {
53
34
  return privilegedOption;
54
35
  };
55
36
 
56
- const requestHandler = async (handler, req, res, context) => {
37
+ const requestHandler = async (handler, req, res, next, context) => {
57
38
  try {
58
39
  context.trx = context.database;
59
40
  if (hasTransaction(Config, context.model, handler)) {
@@ -73,7 +54,11 @@ const requestHandler = async (handler, req, res, context) => {
73
54
  await context.trx.rollback();
74
55
  }
75
56
 
76
- handleErrors(req, res, error);
57
+ if (error.type === "HttpResponse") {
58
+ return res.status(error.status).json(error.content);
59
+ }
60
+
61
+ next(error);
77
62
  }
78
63
  };
79
64
 
@@ -137,7 +122,7 @@ const createNestedRoutes = async (
137
122
  await createRouteByModel(
138
123
  model,
139
124
  models,
140
- `${urlPrefix}${resource}/:${getPrimaryKeyName(model)}/`,
125
+ `${urlPrefix}${resource}/:${camelCase(relation.foreignKey)}/`,
141
126
  model,
142
127
  relation,
143
128
  false
@@ -179,6 +164,14 @@ const getModelMiddlewares = (model, handler) => {
179
164
  return middlewares;
180
165
  };
181
166
 
167
+ const getRootPrefix = () => {
168
+ if (Config.Application.prefix) {
169
+ // NOSONAR
170
+ return Config.Application.prefix.replace(/^\/|\/$/g, "");
171
+ }
172
+ return "api";
173
+ };
174
+
182
175
  const createRouteByModel = async (
183
176
  model,
184
177
  models,
@@ -216,6 +209,7 @@ const createRouteByModel = async (
216
209
 
217
210
  const routeTemplate = API_ROUTE_TEMPLATES[handler];
218
211
  const url = routeTemplate.url(
212
+ getRootPrefix(),
219
213
  urlPrefix,
220
214
  resource,
221
215
  model.instance.primaryKey
@@ -229,9 +223,13 @@ const createRouteByModel = async (
229
223
  docs.push(routeTemplate.method, url, model);
230
224
 
231
225
  // Adding the route to the express
232
- app[routeTemplate.method.toLowerCase()](url, middlewares, (req, res) => {
233
- requestHandler(handler, req, res, context);
234
- });
226
+ app[routeTemplate.method.toLowerCase()](
227
+ url,
228
+ middlewares,
229
+ (req, res, next) => {
230
+ requestHandler(handler, req, res, next, context);
231
+ }
232
+ );
235
233
  }
236
234
 
237
235
  await createChildRoutes(model, models, resource, urlPrefix);