axe-api 0.20.4 → 0.22.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.
- package/CHANGELOG.md +16 -0
- package/build/src/Builders/RouterBuilder.js +5 -2
- package/build/src/Enums.d.ts +5 -0
- package/build/src/Enums.js +5 -0
- package/build/src/Handlers/AllHandler.js +2 -0
- package/build/src/Handlers/DestroyHandler.js +11 -1
- package/build/src/Handlers/ForceDestroyHandler.d.ts +3 -0
- package/build/src/Handlers/ForceDestroyHandler.js +41 -0
- package/build/src/Handlers/HandlerFactory.js +3 -0
- package/build/src/Handlers/Helpers.d.ts +2 -1
- package/build/src/Handlers/Helpers.js +15 -4
- package/build/src/Handlers/PaginateHandler.js +2 -0
- package/build/src/Handlers/PatchHandler.js +3 -1
- package/build/src/Handlers/ShowHandler.js +2 -0
- package/build/src/Handlers/StoreHandler.js +1 -1
- package/build/src/Handlers/UpdateHandler.js +3 -1
- package/build/src/Interfaces.d.ts +12 -0
- package/build/src/Middlewares/acceptLanguageMiddleware.js +6 -15
- package/build/src/Model.d.ts +1 -0
- package/build/src/Model.js +3 -0
- package/build/src/Resolvers/AcceptLanguageResolver.d.ts +7 -0
- package/build/src/Resolvers/AcceptLanguageResolver.js +66 -0
- package/build/src/Resolvers/index.d.ts +2 -1
- package/build/src/Resolvers/index.js +3 -1
- package/build/src/Services/QueryService.js +3 -0
- package/build/src/Services/SchemaValidatorService.js +3 -0
- package/build/src/constants.d.ts +1 -0
- package/build/src/constants.js +2 -0
- package/package.json +1 -2
- package/readme.md +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## [0.22.0 (2023-01-29)](https://github.com/axe-api/axe-api/compare/0.22.0...0.21.0)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Added Soft-Deleting feature [#41](https://github.com/axe-api/axe-api/issues/41)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Fixed model relation route URLs [#141](https://github.com/axe-api/axe-api/issues/141)
|
|
12
|
+
|
|
13
|
+
## [0.21.0 (2022-12-28)](https://github.com/axe-api/axe-api/compare/0.21.0...0.20.4)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- Added `i18n` support. [#44](https://github.com/axe-api/axe-api/issues/44)
|
|
18
|
+
|
|
3
19
|
## [0.20.4 (2022-12-24)](https://github.com/axe-api/axe-api/compare/0.20.4...0.20.3)
|
|
4
20
|
|
|
5
21
|
### Fixed
|
|
@@ -113,13 +113,12 @@ class RouterBuilder {
|
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
// We should different parameter name for child routes
|
|
116
|
-
const primaryKey = this.getPrimaryKeyName(model);
|
|
117
116
|
const subRelations = model.relations.filter((item) => item.type === Enums_1.Relationships.HAS_MANY);
|
|
118
117
|
for (const relation of subRelations) {
|
|
119
118
|
const child = model.children.find((item) => item.name === relation.model);
|
|
120
119
|
// It should be recursive
|
|
121
120
|
if (child) {
|
|
122
|
-
yield this.createRouteByModel(child, modelList, `${urlPrefix}${resource}/:${
|
|
121
|
+
yield this.createRouteByModel(child, modelList, `${urlPrefix}${resource}/:${(0, change_case_1.camelCase)(relation.foreignKey)}/`, model, relation);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
});
|
|
@@ -140,6 +139,10 @@ class RouterBuilder {
|
|
|
140
139
|
app.delete(url, middlewares, handler);
|
|
141
140
|
docs.push(Enums_1.HttpMethods.DELETE, url, model);
|
|
142
141
|
break;
|
|
142
|
+
case Enums_1.HandlerTypes.FORCE_DELETE:
|
|
143
|
+
app.delete(url, middlewares, handler);
|
|
144
|
+
docs.push(Enums_1.HttpMethods.DELETE, url, model);
|
|
145
|
+
break;
|
|
143
146
|
case Enums_1.HandlerTypes.INSERT:
|
|
144
147
|
app.post(url, middlewares, handler);
|
|
145
148
|
docs.push(Enums_1.HttpMethods.POST, url, model);
|
package/build/src/Enums.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare enum HandlerTypes {
|
|
|
24
24
|
SHOW = "show",
|
|
25
25
|
UPDATE = "update",
|
|
26
26
|
DELETE = "destroy",
|
|
27
|
+
FORCE_DELETE = "force_delete",
|
|
27
28
|
PATCH = "patch",
|
|
28
29
|
ALL = "all"
|
|
29
30
|
}
|
|
@@ -33,6 +34,8 @@ export declare enum HookFunctionTypes {
|
|
|
33
34
|
onBeforeUpdate = "onBeforeUpdate",
|
|
34
35
|
onBeforeDeleteQuery = "onBeforeDeleteQuery",
|
|
35
36
|
onBeforeDelete = "onBeforeDelete",
|
|
37
|
+
onBeforeForceDeleteQuery = "onBeforeForceDeleteQuery",
|
|
38
|
+
onBeforeForceDelete = "onBeforeForceDelete",
|
|
36
39
|
onBeforePaginate = "onBeforePaginate",
|
|
37
40
|
onBeforeAll = "onBeforeAll",
|
|
38
41
|
onBeforeShow = "onBeforeShow",
|
|
@@ -41,6 +44,8 @@ export declare enum HookFunctionTypes {
|
|
|
41
44
|
onAfterUpdate = "onAfterUpdate",
|
|
42
45
|
onAfterDeleteQuery = "onAfterDeleteQuery",
|
|
43
46
|
onAfterDelete = "onAfterDelete",
|
|
47
|
+
onAfterForceDeleteQuery = "onAfterForceDeleteQuery",
|
|
48
|
+
onAfterForceDelete = "onAfterForceDelete",
|
|
44
49
|
onAfterPaginate = "onAfterPaginate",
|
|
45
50
|
onAfterAll = "onAfterAll",
|
|
46
51
|
onAfterShow = "onAfterShow"
|
package/build/src/Enums.js
CHANGED
|
@@ -30,6 +30,7 @@ var HandlerTypes;
|
|
|
30
30
|
HandlerTypes["SHOW"] = "show";
|
|
31
31
|
HandlerTypes["UPDATE"] = "update";
|
|
32
32
|
HandlerTypes["DELETE"] = "destroy";
|
|
33
|
+
HandlerTypes["FORCE_DELETE"] = "force_delete";
|
|
33
34
|
HandlerTypes["PATCH"] = "patch";
|
|
34
35
|
HandlerTypes["ALL"] = "all";
|
|
35
36
|
})(HandlerTypes = exports.HandlerTypes || (exports.HandlerTypes = {}));
|
|
@@ -40,6 +41,8 @@ var HookFunctionTypes;
|
|
|
40
41
|
HookFunctionTypes["onBeforeUpdate"] = "onBeforeUpdate";
|
|
41
42
|
HookFunctionTypes["onBeforeDeleteQuery"] = "onBeforeDeleteQuery";
|
|
42
43
|
HookFunctionTypes["onBeforeDelete"] = "onBeforeDelete";
|
|
44
|
+
HookFunctionTypes["onBeforeForceDeleteQuery"] = "onBeforeForceDeleteQuery";
|
|
45
|
+
HookFunctionTypes["onBeforeForceDelete"] = "onBeforeForceDelete";
|
|
43
46
|
HookFunctionTypes["onBeforePaginate"] = "onBeforePaginate";
|
|
44
47
|
HookFunctionTypes["onBeforeAll"] = "onBeforeAll";
|
|
45
48
|
HookFunctionTypes["onBeforeShow"] = "onBeforeShow";
|
|
@@ -48,6 +51,8 @@ var HookFunctionTypes;
|
|
|
48
51
|
HookFunctionTypes["onAfterUpdate"] = "onAfterUpdate";
|
|
49
52
|
HookFunctionTypes["onAfterDeleteQuery"] = "onAfterDeleteQuery";
|
|
50
53
|
HookFunctionTypes["onAfterDelete"] = "onAfterDelete";
|
|
54
|
+
HookFunctionTypes["onAfterForceDeleteQuery"] = "onAfterForceDeleteQuery";
|
|
55
|
+
HookFunctionTypes["onAfterForceDelete"] = "onAfterForceDelete";
|
|
51
56
|
HookFunctionTypes["onAfterPaginate"] = "onAfterPaginate";
|
|
52
57
|
HookFunctionTypes["onAfterAll"] = "onAfterAll";
|
|
53
58
|
HookFunctionTypes["onAfterShow"] = "onAfterShow";
|
|
@@ -20,6 +20,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
20
20
|
const conditions = queryParser.get(req.query);
|
|
21
21
|
// Creating a new database query
|
|
22
22
|
const query = database.from(model.instance.table);
|
|
23
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
24
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, conditions, query);
|
|
23
25
|
// Users should be able to select some fields to show.
|
|
24
26
|
queryParser.applyFields(query, conditions.fields);
|
|
25
27
|
// Binding parent id if there is.
|
|
@@ -20,6 +20,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
20
20
|
const query = database
|
|
21
21
|
.from(model.instance.table)
|
|
22
22
|
.where(model.instance.primaryKey, req.params[model.instance.primaryKey]);
|
|
23
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
24
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, null, query);
|
|
23
25
|
// If there is a relation, we should bind it
|
|
24
26
|
(0, Helpers_1.addForeignKeyQuery)(req, query, relation, parentModel);
|
|
25
27
|
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeDeleteQuery, Object.assign(Object.assign({}, pack), { query }));
|
|
@@ -31,7 +33,15 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
31
33
|
item }));
|
|
32
34
|
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeDelete, Object.assign(Object.assign({}, pack), { query,
|
|
33
35
|
item }));
|
|
34
|
-
|
|
36
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
37
|
+
if (model.instance.deletedAtColumn) {
|
|
38
|
+
yield query.update({
|
|
39
|
+
[model.instance.deletedAtColumn]: new Date(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
yield query.delete();
|
|
44
|
+
}
|
|
35
45
|
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onAfterDelete, Object.assign(Object.assign({}, pack), { item }));
|
|
36
46
|
return res.json();
|
|
37
47
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const Helpers_1 = require("./Helpers");
|
|
16
|
+
const Enums_1 = require("../Enums");
|
|
17
|
+
const ApiError_1 = __importDefault(require("../Exceptions/ApiError"));
|
|
18
|
+
exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
const { model, req, res, database, relation, parentModel } = pack;
|
|
20
|
+
const query = database
|
|
21
|
+
.from(model.instance.table)
|
|
22
|
+
.where(model.instance.primaryKey, req.params[model.instance.primaryKey]);
|
|
23
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
24
|
+
if (model.instance.deletedAtColumn === null) {
|
|
25
|
+
throw new ApiError_1.default("You can use force delete only soft-delete supported models.");
|
|
26
|
+
}
|
|
27
|
+
// If there is a relation, we should bind it
|
|
28
|
+
(0, Helpers_1.addForeignKeyQuery)(req, query, relation, parentModel);
|
|
29
|
+
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeForceDeleteQuery, Object.assign(Object.assign({}, pack), { query }));
|
|
30
|
+
const item = yield query.first();
|
|
31
|
+
if (!item) {
|
|
32
|
+
throw new ApiError_1.default(`The item is not found on ${model.name}.`);
|
|
33
|
+
}
|
|
34
|
+
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onAfterForceDeleteQuery, Object.assign(Object.assign({}, pack), { query,
|
|
35
|
+
item }));
|
|
36
|
+
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeForceDelete, Object.assign(Object.assign({}, pack), { query,
|
|
37
|
+
item }));
|
|
38
|
+
yield query.delete();
|
|
39
|
+
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onAfterForceDelete, Object.assign(Object.assign({}, pack), { item }));
|
|
40
|
+
return res.json();
|
|
41
|
+
});
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const AllHandler_1 = __importDefault(require("./AllHandler"));
|
|
7
7
|
const DestroyHandler_1 = __importDefault(require("./DestroyHandler"));
|
|
8
|
+
const ForceDestroyHandler_1 = __importDefault(require("./ForceDestroyHandler"));
|
|
8
9
|
const PaginateHandler_1 = __importDefault(require("./PaginateHandler"));
|
|
9
10
|
const PatchHandler_1 = __importDefault(require("./PatchHandler"));
|
|
10
11
|
const UpdateHandler_1 = __importDefault(require("./UpdateHandler"));
|
|
@@ -18,6 +19,8 @@ class HandlerFactory {
|
|
|
18
19
|
return AllHandler_1.default;
|
|
19
20
|
case Enums_1.HandlerTypes.DELETE:
|
|
20
21
|
return DestroyHandler_1.default;
|
|
22
|
+
case Enums_1.HandlerTypes.FORCE_DELETE:
|
|
23
|
+
return ForceDestroyHandler_1.default;
|
|
21
24
|
case Enums_1.HandlerTypes.INSERT:
|
|
22
25
|
return StoreHandler_1.default;
|
|
23
26
|
case Enums_1.HandlerTypes.PAGINATE:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request } from "express";
|
|
2
|
-
import { IModelService, IRelation, IHookParameter } from "../Interfaces";
|
|
2
|
+
import { IModelService, IRelation, IHookParameter, IQuery } from "../Interfaces";
|
|
3
3
|
import { Knex } from "knex";
|
|
4
4
|
import { IWith } from "../Interfaces";
|
|
5
5
|
import { HandlerTypes, HookFunctionTypes, TimestampColumns } from "../Enums";
|
|
@@ -11,4 +11,5 @@ export declare const getParentColumn: (relation: IRelation | null) => string | n
|
|
|
11
11
|
export declare const addForeignKeyQuery: (request: Request, query: Knex.QueryBuilder, relation: IRelation | null, parentModel: IModelService | null) => void;
|
|
12
12
|
export declare const serializeData: (itemArray: any[] | any, modelSerializer: (data: any, request: Request) => void, handler: HandlerTypes, request: Request) => Promise<any[]>;
|
|
13
13
|
export declare const filterHiddenFields: (itemArray: any[], hiddens: string[] | null) => void;
|
|
14
|
+
export declare const addSoftDeleteQuery: (model: IModelService, conditions: IQuery | null, query: Knex.QueryBuilder) => void;
|
|
14
15
|
export declare const getRelatedData: (data: any[], withArray: IWith[], model: IModelService, modelList: ModelListService, database: Knex | Knex.Transaction, handler: HandlerTypes, request: Request) => Promise<void>;
|
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.getRelatedData = exports.filterHiddenFields = exports.serializeData = exports.addForeignKeyQuery = exports.getParentColumn = exports.callHooks = exports.getMergedFormData = exports.bindTimestampValues = void 0;
|
|
15
|
+
exports.getRelatedData = exports.addSoftDeleteQuery = exports.filterHiddenFields = exports.serializeData = exports.addForeignKeyQuery = exports.getParentColumn = exports.callHooks = exports.getMergedFormData = exports.bindTimestampValues = void 0;
|
|
16
16
|
const change_case_1 = require("change-case");
|
|
17
17
|
const Enums_1 = require("../Enums");
|
|
18
18
|
const ApiError_1 = __importDefault(require("../Exceptions/ApiError"));
|
|
@@ -138,6 +138,14 @@ const filterHiddenFields = (itemArray, hiddens) => {
|
|
|
138
138
|
});
|
|
139
139
|
};
|
|
140
140
|
exports.filterHiddenFields = filterHiddenFields;
|
|
141
|
+
const addSoftDeleteQuery = (model, conditions, query) => {
|
|
142
|
+
// TODO: Trashed feature will be implemented later
|
|
143
|
+
// (conditions === null || conditions?.trashed === false)
|
|
144
|
+
if (model.instance.deletedAtColumn) {
|
|
145
|
+
query.whereNull(model.instance.deletedAtColumn);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
exports.addSoftDeleteQuery = addSoftDeleteQuery;
|
|
141
149
|
const getRelatedData = (data, withArray, model, modelList, database, handler, request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
150
|
if (withArray.length === 0) {
|
|
143
151
|
return;
|
|
@@ -192,10 +200,13 @@ const getRelatedData = (data, withArray, model, modelList, database, handler, re
|
|
|
192
200
|
selectColumns.push(...requiredForeignKeys);
|
|
193
201
|
}
|
|
194
202
|
selectColumns = uniqueByMap(selectColumns);
|
|
203
|
+
const foreignModelQuery = database(foreignModel.instance.table).select(selectColumns);
|
|
204
|
+
// If the model is supported soft-delete we should check the data.
|
|
205
|
+
if (foreignModel.instance.deletedAtColumn) {
|
|
206
|
+
foreignModelQuery.whereNull(foreignModel.instance.deletedAtColumn);
|
|
207
|
+
}
|
|
195
208
|
// Fetching related records by foreignKey and primary key values.
|
|
196
|
-
let relatedRecords = yield
|
|
197
|
-
.select(selectColumns)
|
|
198
|
-
.whereIn(searchFieldKey, parentPrimaryKeyValues);
|
|
209
|
+
let relatedRecords = yield foreignModelQuery.whereIn(searchFieldKey, parentPrimaryKeyValues);
|
|
199
210
|
// We should serialize related data if there is any serialization function
|
|
200
211
|
relatedRecords = yield (0, exports.serializeData)(relatedRecords, foreignModel.instance.serialize, handler, request);
|
|
201
212
|
// We should hide hidden fields if there is any
|
|
@@ -20,6 +20,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
20
20
|
const conditions = queryParser.get(req.query);
|
|
21
21
|
// Creating a new database query
|
|
22
22
|
const query = database.from(model.instance.table);
|
|
23
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
24
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, conditions, query);
|
|
23
25
|
// Users should be able to select some fields to show.
|
|
24
26
|
queryParser.applyFields(query, conditions.fields);
|
|
25
27
|
// Binding parent id if there is.
|
|
@@ -21,6 +21,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
21
21
|
const query = database.from(model.instance.table);
|
|
22
22
|
// If there is a relation, we should bind it
|
|
23
23
|
(0, Helpers_1.addForeignKeyQuery)(req, query, relation, parentModel);
|
|
24
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
25
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, null, query);
|
|
24
26
|
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeUpdateQuery, Object.assign(Object.assign({}, pack), { query }));
|
|
25
27
|
let item = yield query
|
|
26
28
|
.where(model.instance.primaryKey, req.params[model.instance.primaryKey])
|
|
@@ -36,7 +38,7 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
36
38
|
const validationRules = model.instance.getValidationRules(requestMethod);
|
|
37
39
|
if (validationRules) {
|
|
38
40
|
// The validation language should be set
|
|
39
|
-
validatorjs_1.default.useLang(req.language);
|
|
41
|
+
validatorjs_1.default.useLang(req.currentLanguage.language);
|
|
40
42
|
// Validate the data
|
|
41
43
|
const validation = new validatorjs_1.default(formData, validationRules);
|
|
42
44
|
if (validation.fails()) {
|
|
@@ -24,6 +24,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
24
24
|
const conditions = queryParser.get(req.query);
|
|
25
25
|
// Fetching item
|
|
26
26
|
const query = database.from(model.instance.table);
|
|
27
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
28
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, conditions, query);
|
|
27
29
|
// Users should be able to select some fields to show.
|
|
28
30
|
queryParser.applyFields(query, conditions.fields);
|
|
29
31
|
// If there is a relation, we should bind it
|
|
@@ -23,7 +23,7 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
23
23
|
const validationRules = model.instance.getValidationRules(requestMethod);
|
|
24
24
|
if (validationRules) {
|
|
25
25
|
// The validation language should be set
|
|
26
|
-
validatorjs_1.default.useLang(req.language);
|
|
26
|
+
validatorjs_1.default.useLang(req.currentLanguage.language);
|
|
27
27
|
// Validate the data
|
|
28
28
|
const validation = new validatorjs_1.default(formData, validationRules);
|
|
29
29
|
if (validation.fails()) {
|
|
@@ -21,6 +21,8 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
21
21
|
const query = database.from(model.instance.table);
|
|
22
22
|
// If there is a relation, we should bind it
|
|
23
23
|
(0, Helpers_1.addForeignKeyQuery)(req, query, relation, parentModel);
|
|
24
|
+
// If there is a deletedAtColumn, it means that this table support soft-delete
|
|
25
|
+
(0, Helpers_1.addSoftDeleteQuery)(model, null, query);
|
|
24
26
|
yield (0, Helpers_1.callHooks)(model, Enums_1.HookFunctionTypes.onBeforeUpdateQuery, Object.assign(Object.assign({}, pack), { query }));
|
|
25
27
|
let item = yield query
|
|
26
28
|
.where(model.instance.primaryKey, req.params[model.instance.primaryKey])
|
|
@@ -36,7 +38,7 @@ exports.default = (pack) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
36
38
|
const validationRules = model.instance.getValidationRules(requestMethod);
|
|
37
39
|
if (validationRules) {
|
|
38
40
|
// The validation language should be set
|
|
39
|
-
validatorjs_1.default.useLang(req.language);
|
|
41
|
+
validatorjs_1.default.useLang(req.currentLanguage.language);
|
|
40
42
|
// Validate the data
|
|
41
43
|
const validation = new validatorjs_1.default(formData, validationRules);
|
|
42
44
|
if (validation.fails()) {
|
|
@@ -23,8 +23,18 @@ export interface IApplicationConfig extends IConfig {
|
|
|
23
23
|
prefix: string;
|
|
24
24
|
transaction: boolean | IHandlerBasedTransactionConfig | IHandlerBasedTransactionConfig[];
|
|
25
25
|
serializers: ((data: any, request: Request) => void)[] | IHandlerBasedSerializer[];
|
|
26
|
+
supportedLanguages: string[];
|
|
26
27
|
defaultLanguage: string;
|
|
27
28
|
}
|
|
29
|
+
export interface ILanguage {
|
|
30
|
+
title: string;
|
|
31
|
+
language: string;
|
|
32
|
+
region?: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface IAcceptedLanguage {
|
|
35
|
+
language: ILanguage;
|
|
36
|
+
quality: number;
|
|
37
|
+
}
|
|
28
38
|
export declare type IDatabaseConfig = Knex.Config;
|
|
29
39
|
export interface IFolders {
|
|
30
40
|
App: string;
|
|
@@ -110,6 +120,7 @@ export interface IRawQuery {
|
|
|
110
120
|
sort: string | null;
|
|
111
121
|
fields: string | null;
|
|
112
122
|
with: string | null;
|
|
123
|
+
trashed: string | null;
|
|
113
124
|
}
|
|
114
125
|
export interface ISortField {
|
|
115
126
|
name: string;
|
|
@@ -128,6 +139,7 @@ export interface IQuery {
|
|
|
128
139
|
sort: ISortField[];
|
|
129
140
|
fields: string[];
|
|
130
141
|
with: IWith[];
|
|
142
|
+
trashed: boolean;
|
|
131
143
|
}
|
|
132
144
|
export interface IWhere {
|
|
133
145
|
prefix: string | null;
|
|
@@ -8,26 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const accept_language_parser_1 = __importDefault(require("accept-language-parser"));
|
|
16
12
|
const Services_1 = require("../Services");
|
|
13
|
+
const Resolvers_1 = require("../Resolvers");
|
|
17
14
|
exports.default = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
-
var _a;
|
|
19
15
|
// Application configuration is need for the default setting.
|
|
20
16
|
const configs = yield Services_1.IoCService.use("Config");
|
|
21
17
|
const application = configs.Application;
|
|
22
|
-
const { defaultLanguage } = application;
|
|
23
|
-
//
|
|
24
|
-
req.
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
// be carried by Express.Request
|
|
28
|
-
req.language =
|
|
29
|
-
req.acceptedLanguages.length > 0
|
|
30
|
-
? ((_a = req.acceptedLanguages.at(0)) === null || _a === void 0 ? void 0 : _a.code) || defaultLanguage
|
|
31
|
-
: defaultLanguage;
|
|
18
|
+
const { supportedLanguages, defaultLanguage } = application;
|
|
19
|
+
// Setting the current language by the supported, default and the client prefences
|
|
20
|
+
req.currentLanguage = Resolvers_1.AcceptLanguageResolver.resolve(req.get("accept-language") || "", supportedLanguages || ["en"], defaultLanguage || "en");
|
|
21
|
+
// Adding the `Content-Language` header to the response object
|
|
22
|
+
res.setHeader("Content-Language", req.currentLanguage.title);
|
|
32
23
|
next();
|
|
33
24
|
});
|
package/build/src/Model.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ declare class Model {
|
|
|
11
11
|
get hiddens(): string[];
|
|
12
12
|
get createdAtColumn(): string | null;
|
|
13
13
|
get updatedAtColumn(): string | null;
|
|
14
|
+
get deletedAtColumn(): string | null;
|
|
14
15
|
get transaction(): boolean | IHandlerBasedTransactionConfig | IHandlerBasedTransactionConfig[] | null;
|
|
15
16
|
get ignore(): boolean;
|
|
16
17
|
getFillableFields(methodType: HttpMethods): string[];
|
package/build/src/Model.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ILanguage } from "../Interfaces";
|
|
2
|
+
declare class AcceptLanguageResolver {
|
|
3
|
+
static resolve(value: string, supportedLanguages: string[], defaultLanguage: string): ILanguage;
|
|
4
|
+
static toLanguageObject(key?: string): ILanguage;
|
|
5
|
+
private static toSortedPreferences;
|
|
6
|
+
}
|
|
7
|
+
export default AcceptLanguageResolver;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class AcceptLanguageResolver {
|
|
4
|
+
static resolve(value, supportedLanguages, defaultLanguage) {
|
|
5
|
+
value = value.trim();
|
|
6
|
+
if (value === "*") {
|
|
7
|
+
return this.toLanguageObject(defaultLanguage);
|
|
8
|
+
}
|
|
9
|
+
const languages = this.toSortedPreferences(value);
|
|
10
|
+
const perfectMatch = languages.find((item) => supportedLanguages.includes(item.language.title));
|
|
11
|
+
const anyMatch = languages.find((item) => supportedLanguages.includes(item.language.language));
|
|
12
|
+
if (perfectMatch && !anyMatch) {
|
|
13
|
+
return perfectMatch.language;
|
|
14
|
+
}
|
|
15
|
+
if (!perfectMatch && anyMatch) {
|
|
16
|
+
return anyMatch.language;
|
|
17
|
+
}
|
|
18
|
+
if (perfectMatch && anyMatch) {
|
|
19
|
+
if (perfectMatch.quality >= anyMatch.quality) {
|
|
20
|
+
return perfectMatch.language;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return this.toLanguageObject(anyMatch.language.language);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return this.toLanguageObject(defaultLanguage);
|
|
27
|
+
}
|
|
28
|
+
static toLanguageObject(key = "") {
|
|
29
|
+
const [language, region] = key.split("-");
|
|
30
|
+
return {
|
|
31
|
+
title: key,
|
|
32
|
+
language,
|
|
33
|
+
region: region || null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
static toSortedPreferences(value) {
|
|
37
|
+
// Splitting by language definitons
|
|
38
|
+
const keys = value.split(",").map((key) => key.trim());
|
|
39
|
+
const languages = [];
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
// Splitting by the quality values
|
|
42
|
+
const [code, quality] = key.split(";");
|
|
43
|
+
// Parsing the language code and the quality value
|
|
44
|
+
const item = {
|
|
45
|
+
language: this.toLanguageObject(code),
|
|
46
|
+
quality: quality ? parseFloat(quality.replace("q=", "")) : 1,
|
|
47
|
+
};
|
|
48
|
+
languages.push(item);
|
|
49
|
+
}
|
|
50
|
+
// Sorting ASC
|
|
51
|
+
languages.sort((a, b) => {
|
|
52
|
+
if (a.quality === b.quality) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (a.quality === null) {
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
if (b.quality === null) {
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
return a.quality < b.quality ? 1 : -1;
|
|
62
|
+
});
|
|
63
|
+
return languages;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.default = AcceptLanguageResolver;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import AcceptLanguageResolver from "./AcceptLanguageResolver";
|
|
1
2
|
import FileResolver from "./FileResolver";
|
|
2
3
|
import FolderResolver from "./FolderResolver";
|
|
3
4
|
import GeneralHookResolver from "./GeneralHookResolver";
|
|
4
5
|
import ModelResolver from "./ModelResolver";
|
|
5
6
|
import TransactionResolver from "./TransactionResolver";
|
|
6
7
|
import WithQueryResolver from "./WithQueryResolver";
|
|
7
|
-
export { FileResolver, FolderResolver, GeneralHookResolver, ModelResolver, TransactionResolver, WithQueryResolver, };
|
|
8
|
+
export { AcceptLanguageResolver, FileResolver, FolderResolver, GeneralHookResolver, ModelResolver, TransactionResolver, WithQueryResolver, };
|
|
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WithQueryResolver = exports.TransactionResolver = exports.ModelResolver = exports.GeneralHookResolver = exports.FolderResolver = exports.FileResolver = void 0;
|
|
6
|
+
exports.WithQueryResolver = exports.TransactionResolver = exports.ModelResolver = exports.GeneralHookResolver = exports.FolderResolver = exports.FileResolver = exports.AcceptLanguageResolver = void 0;
|
|
7
|
+
const AcceptLanguageResolver_1 = __importDefault(require("./AcceptLanguageResolver"));
|
|
8
|
+
exports.AcceptLanguageResolver = AcceptLanguageResolver_1.default;
|
|
7
9
|
const FileResolver_1 = __importDefault(require("./FileResolver"));
|
|
8
10
|
exports.FileResolver = FileResolver_1.default;
|
|
9
11
|
const FolderResolver_1 = __importDefault(require("./FolderResolver"));
|
|
@@ -175,6 +175,9 @@ class QueryService {
|
|
|
175
175
|
sort: this.parseSortingOptions(sections.sort),
|
|
176
176
|
q: this.parseCondition(sections.q),
|
|
177
177
|
with: withQueryResolver.resolve((sections === null || sections === void 0 ? void 0 : sections.with) || ""),
|
|
178
|
+
trashed: (sections === null || sections === void 0 ? void 0 : sections.trashed)
|
|
179
|
+
? sections.trashed.trim() === "true" || sections.trashed.trim() === "1"
|
|
180
|
+
: false,
|
|
178
181
|
};
|
|
179
182
|
this.addRelationColumns(query.with);
|
|
180
183
|
return query;
|
|
@@ -57,6 +57,9 @@ class SchemaValidatorService {
|
|
|
57
57
|
if (model.instance.updatedAtColumn) {
|
|
58
58
|
columns.push(model.instance.updatedAtColumn);
|
|
59
59
|
}
|
|
60
|
+
if (model.instance.deletedAtColumn) {
|
|
61
|
+
columns.push(model.instance.deletedAtColumn);
|
|
62
|
+
}
|
|
60
63
|
return columns;
|
|
61
64
|
};
|
|
62
65
|
this.checkHasManyRelation = (modelList, model, relation) => {
|
package/build/src/constants.d.ts
CHANGED
|
@@ -20,4 +20,5 @@ export declare const API_ROUTE_TEMPLATES: {
|
|
|
20
20
|
update: (prefix: string, parentUrl: string, resource: string, primaryKey: string) => string;
|
|
21
21
|
patch: (prefix: string, parentUrl: string, resource: string, primaryKey: string) => string;
|
|
22
22
|
destroy: (prefix: string, parentUrl: string, resource: string, primaryKey: string) => string;
|
|
23
|
+
force_delete: (prefix: string, parentUrl: string, resource: string, primaryKey: string) => string;
|
|
23
24
|
};
|
package/build/src/constants.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.DEFAULT_METHODS_OF_MODELS = [
|
|
|
46
46
|
"hiddens",
|
|
47
47
|
"createdAtColumn",
|
|
48
48
|
"updatedAtColumn",
|
|
49
|
+
"deletedAtColumn",
|
|
49
50
|
"transaction",
|
|
50
51
|
"ignore",
|
|
51
52
|
"getFillableFields",
|
|
@@ -59,4 +60,5 @@ exports.API_ROUTE_TEMPLATES = {
|
|
|
59
60
|
[Enums_1.HandlerTypes.UPDATE]: (prefix, parentUrl, resource, primaryKey) => `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
60
61
|
[Enums_1.HandlerTypes.PATCH]: (prefix, parentUrl, resource, primaryKey) => `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
61
62
|
[Enums_1.HandlerTypes.DELETE]: (prefix, parentUrl, resource, primaryKey) => `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
63
|
+
[Enums_1.HandlerTypes.FORCE_DELETE]: (prefix, parentUrl, resource, primaryKey) => `/${prefix}/${parentUrl}${resource}/:${primaryKey}/force`,
|
|
62
64
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axe-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@types/express": "^4.17.15",
|
|
33
|
-
"accept-language-parser": "^1.5.0",
|
|
34
33
|
"change-case": "^4.1.2",
|
|
35
34
|
"dotenv": "^14.2.0",
|
|
36
35
|
"express": "^4.18.2",
|
package/readme.md
CHANGED
|
@@ -145,30 +145,6 @@ If you can see that response, it means that your project is running properly.
|
|
|
145
145
|
|
|
146
146
|
Axe API has great documentation. Please [check it out in here](https://axe-api.com/).
|
|
147
147
|
|
|
148
|
-
## How To Run Integration Tests
|
|
149
|
-
|
|
150
|
-
> You have to have **Docker** and **Docker Compose** on your local development environment to run integration tests.
|
|
151
|
-
|
|
152
|
-
Execute the following commands to prepare the integration app
|
|
153
|
-
|
|
154
|
-
```sh
|
|
155
|
-
cd tests/integrations && npm install && npm ci && npm run build --if-present
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Execute the following commands to prepare the database;
|
|
159
|
-
|
|
160
|
-
```sh
|
|
161
|
-
docker-compose -f "./tests/integrations/docker-compose.mysql8.yml" up -d --build
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
> To down the database, you can use the following command; `docker-compose -f "./tests/integrations/docker-compose.mysql8.yml" up -d --build`
|
|
165
|
-
|
|
166
|
-
You can execute the following command to execute tests;
|
|
167
|
-
|
|
168
|
-
```sh
|
|
169
|
-
npm run test:integration:mysql8
|
|
170
|
-
```
|
|
171
|
-
|
|
172
148
|
## License
|
|
173
149
|
|
|
174
150
|
[MIT License](LICENSE)
|