axe-api 0.17.3 → 0.18.1

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,32 @@
1
+ ## Description
2
+
3
+ <!--- Describe your changes in detail -->
4
+
5
+ ## Motivation and Context
6
+
7
+ <!--- Why is this change required? What problem does it solve? -->
8
+ <!--- If it fixes an open issue, please link to the issue here. -->
9
+
10
+ ## How has this been tested?
11
+
12
+ <!--- Please describe in detail how you tested your changes. -->
13
+ <!--- Include details of your testing environment, tests ran to see how -->
14
+ <!--- your change affects other areas of the code, etc. -->
15
+
16
+ ## Types of changes
17
+
18
+ <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
19
+
20
+ - [ ] Bug fix (non-breaking change which fixes an issue)
21
+ - [ ] New feature (non-breaking change which adds functionality)
22
+ - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
23
+
24
+ ## Checklist:
25
+
26
+ <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
27
+ <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
28
+
29
+ - [ ] My changes requires documentation change.
30
+ - [ ] The changes have been documented in the [axe-api/docs](https://github.com/axe-api/docs) repository. (<!--- Please insert the PR link -->)
31
+ - [ ] I added integration tests properly.
32
+ - [ ] The changes require [axe-api-template](https://github.com/axe-api/axe-api-template) changes. (<!--- Please insert the PR link -->)
@@ -0,0 +1,15 @@
1
+ name: Create Tag
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - uses: butlerlogic/action-autotag@stable
14
+ with:
15
+ GITHUB_TOKEN: "${{ secrets.AUTO_TAG_TOKEN }}"
@@ -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,37 @@
1
1
  # Release Notes
2
2
 
3
+ ## [0.18.1 (2021-12-02)](https://github.com/axe-api/axe-api/compare/0.18.1...0.18.0)
4
+
5
+ ### Fixed
6
+
7
+ - [#117](https://github.com/axe-api/axe-api/issues/117)
8
+
9
+ ## [0.18.0 (2021-11-30)](https://github.com/axe-api/axe-api/compare/0.18.0...0.17.5)
10
+
11
+ ### Fixed
12
+
13
+ - [#115](https://github.com/axe-api/axe-api/issues/115)
14
+ - [#114](https://github.com/axe-api/axe-api/issues/114)
15
+
16
+ ### Enhancements
17
+
18
+ - [#113](https://github.com/axe-api/axe-api/issues/113)
19
+ - [#107](https://github.com/axe-api/axe-api/issues/107)
20
+ - [#108](https://github.com/axe-api/axe-api/issues/108)
21
+
22
+ ## [0.17.5 (2021-11-27)](https://github.com/axe-api/axe-api/compare/0.17.5...0.17.4)
23
+
24
+ ### Fixed
25
+
26
+ - [#111](https://github.com/axe-api/axe-api/issues/111)
27
+
28
+ ## [0.17.4 (2021-10-28)](https://github.com/axe-api/axe-api/compare/0.17.4...0.17.3)
29
+
30
+ ### Fixed
31
+
32
+ - [#97](https://github.com/axe-api/axe-api/issues/97)
33
+ - [#104](https://github.com/axe-api/axe-api/issues/104)
34
+
3
35
  ## [0.17.3 (2021-10-28)](https://github.com/axe-api/axe-api/compare/0.17.3...0.17.2)
4
36
 
5
37
  ### Fixed
package/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import Server from "./src/Server.js";
2
2
  import Model from "./src/core/Model.js";
3
3
  import IoC from "./src/core/IoC.js";
4
- import { LOG_LEVEL, HOOK_FUNCTIONS, HANDLERS } from "./src/constants.js";
4
+ import {
5
+ LOG_LEVEL,
6
+ HOOK_FUNCTIONS,
7
+ HANDLERS,
8
+ DEFAULT_HANDLERS,
9
+ } from "./src/constants.js";
5
10
  import HttpResponse from "./src/core/HttpResponse.js";
6
11
 
7
12
  export {
@@ -12,4 +17,5 @@ export {
12
17
  LOG_LEVEL,
13
18
  HOOK_FUNCTIONS,
14
19
  HANDLERS,
20
+ DEFAULT_HANDLERS,
15
21
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axe-api",
3
- "version": "0.17.3",
3
+ "version": "0.18.1",
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",
@@ -22,9 +22,9 @@
22
22
  "change-case": "^4.1.2",
23
23
  "dotenv": "^10.0.0",
24
24
  "express": "^4.17.1",
25
- "knex": "^0.95.11",
26
- "knex-paginate": "^2.3.0",
27
- "knex-schema-inspector": "^1.6.2",
25
+ "knex": "^0.95.12",
26
+ "knex-paginate": "^3.0.0",
27
+ "knex-schema-inspector": "^1.6.4",
28
28
  "pluralize": "^8.0.0",
29
29
  "validatorjs": "^3.22.1"
30
30
  },
package/src/constants.js CHANGED
@@ -55,6 +55,14 @@ const HANDLERS = {
55
55
  AUTOSAVE: "autosave",
56
56
  };
57
57
 
58
+ const DEFAULT_HANDLERS = [
59
+ HANDLERS.INSERT,
60
+ HANDLERS.PAGINATE,
61
+ HANDLERS.SHOW,
62
+ HANDLERS.UPDATE,
63
+ HANDLERS.DELETE,
64
+ ];
65
+
58
66
  const HTTP_METHODS = {
59
67
  POST: "POST",
60
68
  GET: "GET",
@@ -64,31 +72,31 @@ const HTTP_METHODS = {
64
72
 
65
73
  const API_ROUTE_TEMPLATES = {
66
74
  [HANDLERS.INSERT]: {
67
- url: (parentUrl, resource) => `/api/${parentUrl}${resource}`,
75
+ url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
68
76
  method: HTTP_METHODS.POST,
69
77
  },
70
78
  [HANDLERS.PAGINATE]: {
71
- url: (parentUrl, resource) => `/api/${parentUrl}${resource}`,
79
+ url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
72
80
  method: HTTP_METHODS.GET,
73
81
  },
74
82
  [HANDLERS.SHOW]: {
75
- url: (parentUrl, resource, primaryKey) =>
76
- `/api/${parentUrl}${resource}/:${primaryKey}`,
83
+ url: (prefix, parentUrl, resource, primaryKey) =>
84
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
77
85
  method: HTTP_METHODS.GET,
78
86
  },
79
87
  [HANDLERS.UPDATE]: {
80
- url: (parentUrl, resource, primaryKey) =>
81
- `/api/${parentUrl}${resource}/:${primaryKey}`,
88
+ url: (prefix, parentUrl, resource, primaryKey) =>
89
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
82
90
  method: HTTP_METHODS.PUT,
83
91
  },
84
92
  [HANDLERS.AUTOSAVE]: {
85
- url: (parentUrl, resource, primaryKey) =>
86
- `/api/${parentUrl}${resource}/:${primaryKey}/autosave`,
93
+ url: (prefix, parentUrl, resource, primaryKey) =>
94
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}/autosave`,
87
95
  method: HTTP_METHODS.PUT,
88
96
  },
89
97
  [HANDLERS.DELETE]: {
90
- url: (parentUrl, resource, primaryKey) =>
91
- `/api/${parentUrl}${resource}/:${primaryKey}`,
98
+ url: (prefix, parentUrl, resource, primaryKey) =>
99
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
92
100
  method: HTTP_METHODS.DELETE,
93
101
  },
94
102
  };
@@ -125,5 +133,6 @@ export {
125
133
  LOG_COLORS,
126
134
  DEPENDECY_TYPES,
127
135
  HANDLERS,
136
+ DEFAULT_HANDLERS,
128
137
  TIMESTAMP_COLUMNS,
129
138
  };
@@ -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
  }
@@ -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 = serializeData(item, model.instance.serialize);
84
+ item = await serializeData(
85
+ item,
86
+ model.instance.serialize,
87
+ HANDLERS.AUTOSAVE,
88
+ request
89
+ );
82
90
 
83
91
  // Filtering hidden fields from the response data.
84
92
  filterHiddenFields([item], model.instance.hiddens);
@@ -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
 
@@ -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);
@@ -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,13 @@ const getModelMiddlewares = (model, handler) => {
179
164
  return middlewares;
180
165
  };
181
166
 
167
+ const getRootPrefix = () => {
168
+ if (Config.Application.prefix) {
169
+ return Config.Application.prefix.replace(/^\/|\/$/g, "");
170
+ }
171
+ return "api";
172
+ };
173
+
182
174
  const createRouteByModel = async (
183
175
  model,
184
176
  models,
@@ -216,6 +208,7 @@ const createRouteByModel = async (
216
208
 
217
209
  const routeTemplate = API_ROUTE_TEMPLATES[handler];
218
210
  const url = routeTemplate.url(
211
+ getRootPrefix(),
219
212
  urlPrefix,
220
213
  resource,
221
214
  model.instance.primaryKey
@@ -229,9 +222,13 @@ const createRouteByModel = async (
229
222
  docs.push(routeTemplate.method, url, model);
230
223
 
231
224
  // Adding the route to the express
232
- app[routeTemplate.method.toLowerCase()](url, middlewares, (req, res) => {
233
- requestHandler(handler, req, res, context);
234
- });
225
+ app[routeTemplate.method.toLowerCase()](
226
+ url,
227
+ middlewares,
228
+ (req, res, next) => {
229
+ requestHandler(handler, req, res, next, context);
230
+ }
231
+ );
235
232
  }
236
233
 
237
234
  await createChildRoutes(model, models, resource, urlPrefix);