@autofleet/sheilta 1.4.8-beta-1 → 1.4.8-beta-3

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,55 @@
1
+ import type { LiteralAttribute } from '../middleware';
2
+ type OrderItem = string | [string, string];
3
+ type SequelizeOrder = string | OrderItem[];
4
+ export type FormatPayloadOptions = {
5
+ includeRawPayload?: boolean;
6
+ literalAttributes?: LiteralAttribute[];
7
+ DBFormatter?: any;
8
+ skipSearchTermFormat?: boolean;
9
+ additionalAllowedAttributes?: string[];
10
+ };
11
+ type ConditionWithOperator = {
12
+ operator: string;
13
+ value: string;
14
+ };
15
+ export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
16
+ /**
17
+ * Generates replacements for the given conditions.
18
+ *
19
+ * @param conditions - The conditions to generate replacements for.
20
+ * @returns The replacements object.
21
+ */
22
+ export declare const generateFilterReplacements: (conditions: Record<string, ConditionValue>) => Record<string, string>;
23
+ /**
24
+ * Generates replacements for the given order array.
25
+ *
26
+ * @param order - The order array to generate replacements for.
27
+ * @returns The replacements object.
28
+ */
29
+ export declare const generateOrderReplacements: (order: string[]) => Record<string, string>;
30
+ declare const formatPayload: ({ order, page, perPage, include, query, attributes, searchTerm, }: {
31
+ order?: any[];
32
+ page?: number;
33
+ perPage?: number;
34
+ include?: any[];
35
+ query?: {};
36
+ attributes?: any;
37
+ searchTerm?: any;
38
+ }, model?: any, options?: FormatPayloadOptions) => {
39
+ externalQueryValues: Record<string, unknown>;
40
+ attributes: any[] | {
41
+ include: any[];
42
+ };
43
+ query: Record<string, unknown>;
44
+ order: SequelizeOrder[];
45
+ page: number;
46
+ perPage: number;
47
+ include: any;
48
+ scopes: (string | {
49
+ method: (string | {
50
+ replacementsMap: Record<string, string>;
51
+ scopeValue: any;
52
+ })[];
53
+ })[];
54
+ };
55
+ export default formatPayload;
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateOrderReplacements = exports.generateFilterReplacements = void 0;
4
+ const common_types_1 = require("@autofleet/common-types");
5
+ const utils_1 = require("../utils");
6
+ const operators_1 = require("../operators");
7
+ const DEFAULT_ORDER = 'id';
8
+ const DESCENDING_KEY = 'DESC';
9
+ const ASCENDING_KEY = 'ASC';
10
+ const CUSTOM_FIELDS_QUERY_PREFIX = 'customFields.';
11
+ const { CUSTOM_FIELDS_FILTER_SCOPE, CUSTOM_FIELDS_SORT_SCOPE } = common_types_1.customFields;
12
+ const parseCustomFieldScopeQueryValue = (value) => {
13
+ if (['string', 'number'].includes(typeof value) || Array.isArray(value)) {
14
+ return value;
15
+ }
16
+ return Object.entries(value).map(([operator, conditionValue]) => ({
17
+ operator: operators_1.OPERATORS_TO_SQL[operator],
18
+ value: conditionValue,
19
+ }));
20
+ };
21
+ const getAttributeFromOrder = (order, options = {}) => {
22
+ const { literalAttributes = [], DBFormatter = undefined } = options;
23
+ const [formattedOrder, attributes] = order.reduce((acc, o) => {
24
+ const [item, orderStyle = 'ASC'] = Array.isArray(o) ? o : [o];
25
+ const found = literalAttributes?.find((obj) => obj.attribute === item);
26
+ if (found) {
27
+ acc[1].push(found.literal);
28
+ acc[0].push([DBFormatter ? DBFormatter(`"${found.attribute}" ${orderStyle}`) : `${found.attribute} ${orderStyle}`]);
29
+ }
30
+ else {
31
+ acc[0].push(o);
32
+ }
33
+ return acc;
34
+ }, [[], []]);
35
+ return [formattedOrder, attributes];
36
+ };
37
+ /**
38
+ * Generates replacements for the given conditions.
39
+ *
40
+ * @param conditions - The conditions to generate replacements for.
41
+ * @returns The replacements object.
42
+ */
43
+ const generateFilterReplacements = (conditions) => {
44
+ const replacements = {};
45
+ Object.entries(conditions).forEach(([key, condition]) => {
46
+ const replacementKey = (0, utils_1.generateRandomString)();
47
+ // eslint-disable-next-line prefer-destructuring
48
+ replacements[replacementKey] = key.split(CUSTOM_FIELDS_QUERY_PREFIX)[1];
49
+ if (Array.isArray(condition)) {
50
+ condition.forEach((value) => {
51
+ const valueKey = (0, utils_1.generateRandomString)();
52
+ replacements[valueKey] = typeof value === 'string' ? value : value.value;
53
+ });
54
+ }
55
+ else if (typeof condition === 'string' || typeof condition === 'number' || typeof condition === 'boolean') {
56
+ const conditionKey = (0, utils_1.generateRandomString)();
57
+ replacements[conditionKey] = condition;
58
+ }
59
+ else if (condition?.operator) {
60
+ const operatorKey = (0, utils_1.generateRandomString)();
61
+ replacements[operatorKey] = condition.value;
62
+ }
63
+ });
64
+ return replacements;
65
+ };
66
+ exports.generateFilterReplacements = generateFilterReplacements;
67
+ /**
68
+ * Generates replacements for the given order array.
69
+ *
70
+ * @param order - The order array to generate replacements for.
71
+ * @returns The replacements object.
72
+ */
73
+ const generateOrderReplacements = (order) => {
74
+ const replacementMap = {};
75
+ order.forEach((o) => {
76
+ if (o.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {
77
+ const rand = (0, utils_1.generateRandomString)();
78
+ // eslint-disable-next-line prefer-destructuring
79
+ replacementMap[rand] = o.split(CUSTOM_FIELDS_QUERY_PREFIX)[1];
80
+ }
81
+ else if (o.substring(1).startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {
82
+ const rand = (0, utils_1.generateRandomString)();
83
+ // eslint-disable-next-line prefer-destructuring
84
+ replacementMap[rand] = o.substring(1).split(CUSTOM_FIELDS_QUERY_PREFIX)[1];
85
+ }
86
+ });
87
+ return replacementMap;
88
+ };
89
+ exports.generateOrderReplacements = generateOrderReplacements;
90
+ /**
91
+ * Creates a combined replacement map from order and query.
92
+ *
93
+ * @param order - The order array.
94
+ * @param query - The query object.
95
+ * @returns The combined replacements object.
96
+ */
97
+ const createReplacementMap = (order, query) => ({
98
+ ...(0, exports.generateOrderReplacements)(order),
99
+ ...(0, exports.generateFilterReplacements)(query),
100
+ });
101
+ const formatOrder = ({ order, associationModels = [], replacementsMap = {}, }) => {
102
+ const formattedOrders = [];
103
+ const orderScopesMap = new Map();
104
+ order.forEach((o) => {
105
+ if ([o, o.substring(1)].some((t) => t.startsWith(CUSTOM_FIELDS_QUERY_PREFIX))) {
106
+ if (!orderScopesMap.has(CUSTOM_FIELDS_SORT_SCOPE)) {
107
+ orderScopesMap.set(CUSTOM_FIELDS_SORT_SCOPE, {});
108
+ }
109
+ const scopeKey = o.split(CUSTOM_FIELDS_QUERY_PREFIX)[1];
110
+ orderScopesMap.get(CUSTOM_FIELDS_SORT_SCOPE)[scopeKey] = ((0, utils_1.isOrderDesc)(o) ? DESCENDING_KEY : ASCENDING_KEY);
111
+ return;
112
+ }
113
+ const formattedOrder = [(0, utils_1.extractAttributeNameFromOrder)(o, associationModels)];
114
+ const isOrderDescOrder = (0, utils_1.isOrderDesc)(o);
115
+ const isOrderAssociation = (0, utils_1.isAttributeByAssociation)(isOrderDescOrder
116
+ ? o.split(utils_1.ORDER_PREFIX)[1]
117
+ : o, associationModels);
118
+ if (isOrderAssociation) {
119
+ formattedOrder.push((0, utils_1.extractAssociatedAttributeNameFromOrder)(o));
120
+ }
121
+ if (isOrderDescOrder) {
122
+ formattedOrder.push(DESCENDING_KEY);
123
+ }
124
+ formattedOrders.push(formattedOrder);
125
+ });
126
+ return {
127
+ formattedOrders,
128
+ replacementsMap,
129
+ orderScopes: Array.from(orderScopesMap.entries()).map(([scopeName, scopeValue]) => {
130
+ if (!scopeValue) {
131
+ return scopeName;
132
+ }
133
+ return {
134
+ method: [scopeName, {
135
+ replacementsMap,
136
+ scopeValue,
137
+ }],
138
+ };
139
+ }),
140
+ };
141
+ };
142
+ const formatPage = (page) => page || utils_1.PAGE_DEFAULT;
143
+ const formatPerPage = (perPage) => perPage || utils_1.PER_PAGE_DEFAULT;
144
+ const formatInclude = (include, associationsMap = {}) => {
145
+ let formattedInclude = include.map((i) => {
146
+ const includedAssociation = associationsMap[typeof i === 'string' ? i : (i.association || i.model)];
147
+ return {
148
+ ...(typeof i !== 'string' && i),
149
+ association: includedAssociation,
150
+ required: typeof i === 'string' || i.required !== false,
151
+ ...(typeof i !== 'string' && i.include && {
152
+ include: formatInclude(i.include, includedAssociation?.target?.associations),
153
+ }),
154
+ };
155
+ });
156
+ formattedInclude = formattedInclude.map(({ model: _model, ...i }) => i);
157
+ return formattedInclude;
158
+ };
159
+ const formatQuery = (query, associationModels, replacementsMap, additionalAllowedAttributes = []) => {
160
+ const formattedQuery = {};
161
+ const externalQueryValues = {};
162
+ const formattedScopeMap = new Map();
163
+ Object.entries(query).forEach(([queryItemKey, queryItemValue]) => {
164
+ if (queryItemKey.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {
165
+ if (!formattedScopeMap.has(CUSTOM_FIELDS_FILTER_SCOPE)) {
166
+ formattedScopeMap.set(CUSTOM_FIELDS_FILTER_SCOPE, {});
167
+ }
168
+ const scopeKey = queryItemKey.split(CUSTOM_FIELDS_QUERY_PREFIX)[1];
169
+ formattedScopeMap.get(CUSTOM_FIELDS_FILTER_SCOPE)[scopeKey] = parseCustomFieldScopeQueryValue(queryItemValue);
170
+ return;
171
+ }
172
+ if (additionalAllowedAttributes.includes(queryItemKey)) {
173
+ externalQueryValues[queryItemKey] = queryItemValue;
174
+ return;
175
+ }
176
+ const key = (0, utils_1.isAttributeByAssociation)(queryItemKey, associationModels)
177
+ ? (0, utils_1.wrapAttributeWithOperator)(queryItemKey)
178
+ : queryItemKey;
179
+ formattedQuery[key] = queryItemValue;
180
+ });
181
+ const formattedScopes = Array.from(formattedScopeMap.entries()).map(([scopeName, scopeValue]) => {
182
+ if (!scopeValue) {
183
+ return scopeName;
184
+ }
185
+ return {
186
+ method: [scopeName, {
187
+ replacementsMap,
188
+ scopeValue,
189
+ }],
190
+ };
191
+ });
192
+ return {
193
+ formattedQuery,
194
+ externalQueryValues,
195
+ formattedScopes,
196
+ };
197
+ };
198
+ const formatSearchTerm = (searchTerm, attributesToSend, rawAttributes) => ({
199
+ $and: searchTerm.split(' ').map((term) => ({
200
+ $or: attributesToSend.filter((attrKey) => rawAttributes[attrKey].type.key === 'STRING').map((attr) => ({
201
+ [attr]: {
202
+ $iLike: `%${term}%`,
203
+ },
204
+ })),
205
+ })),
206
+ });
207
+ const formatPayload = ({ order = [], page = utils_1.PAGE_DEFAULT, perPage = utils_1.PER_PAGE_DEFAULT, include = [], query = {}, attributes = null, searchTerm = null, }, model, options) => {
208
+ const replacementsMap = createReplacementMap(order, query);
209
+ const associationModels = Object.keys(model?.associations || {});
210
+ const { formattedOrders, orderScopes } = formatOrder({
211
+ order: [...order, DEFAULT_ORDER],
212
+ associationModels,
213
+ replacementsMap,
214
+ });
215
+ const [filteredFormattedOrder, filteredLiteralAttributes] = getAttributeFromOrder(formattedOrders, options);
216
+ const allAttributes = [...filteredLiteralAttributes, ...(attributes || [])];
217
+ const formattedAttribute = attributes?.length ? allAttributes : { include: allAttributes };
218
+ const formattedInclude = formatInclude(include, model?.associations);
219
+ const formattedPage = formatPage(page);
220
+ const formattedPerPage = formatPerPage(perPage);
221
+ const result = formatQuery(query, associationModels, replacementsMap, options?.additionalAllowedAttributes);
222
+ const { formattedScopes: queryScopes, externalQueryValues } = result;
223
+ let { formattedQuery } = result;
224
+ if (searchTerm && !options?.skipSearchTermFormat) {
225
+ const attributesToSend = attributes?.length ? attributes : Object.keys(model.rawAttributes);
226
+ const queryWithSearchTerm = formatSearchTerm(searchTerm, attributesToSend, model.rawAttributes);
227
+ formattedQuery = !formattedQuery || Object.keys(formattedQuery).length === 0 ? queryWithSearchTerm : {
228
+ $and: [
229
+ formattedQuery,
230
+ queryWithSearchTerm,
231
+ ],
232
+ };
233
+ }
234
+ return {
235
+ query: formattedQuery,
236
+ order: filteredFormattedOrder,
237
+ page: formattedPage,
238
+ perPage: formattedPerPage,
239
+ include: formattedInclude,
240
+ scopes: [...queryScopes, ...orderScopes],
241
+ ...(formattedAttribute && { attributes: formattedAttribute }),
242
+ ...(Object.keys(externalQueryValues).length > 0 && { externalQueryValues }),
243
+ };
244
+ };
245
+ exports.default = formatPayload;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const _1 = __importDefault(require("."));
7
+ describe('formatting test', () => {
8
+ describe('query formatting', () => {
9
+ it('query json field', () => {
10
+ const { query } = (0, _1.default)({
11
+ order: [],
12
+ query: {
13
+ 'json.field': {
14
+ $gt: 4,
15
+ },
16
+ },
17
+ }, {
18
+ rawAttributes: {
19
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', json: '',
20
+ },
21
+ });
22
+ expect(JSON.stringify(query)).toBe(JSON.stringify({ 'json.field': { $gt: 4 } }));
23
+ });
24
+ it('query included model field', () => {
25
+ const { query } = (0, _1.default)({
26
+ order: [],
27
+ query: {
28
+ 'status.field': {
29
+ $gt: 4,
30
+ },
31
+ },
32
+ }, {
33
+ rawAttributes: {
34
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
35
+ },
36
+ associations: {
37
+ status: {},
38
+ },
39
+ });
40
+ expect(JSON.stringify(query)).toBe(JSON.stringify({ '$status.field$': { $gt: 4 } }));
41
+ });
42
+ it('query with searchTerm', () => {
43
+ const searchTerm = 'aviv';
44
+ const { query } = (0, _1.default)({
45
+ order: [],
46
+ query: {
47
+ 'json.field': {
48
+ $gt: 4,
49
+ },
50
+ },
51
+ searchTerm,
52
+ }, {
53
+ rawAttributes: {
54
+ startTime: {
55
+ type: {
56
+ key: 'STRING',
57
+ },
58
+ },
59
+ endTime: {
60
+ type: {
61
+ key: 'NUMBER',
62
+ },
63
+ },
64
+ },
65
+ });
66
+ expect(JSON.stringify(query)).toBe(JSON.stringify({
67
+ $and: [{ 'json.field': { $gt: 4 } }, {
68
+ $and: [{
69
+ $or: [
70
+ {
71
+ startTime: {
72
+ $iLike: `%${searchTerm}%`,
73
+ },
74
+ },
75
+ ],
76
+ }],
77
+ }],
78
+ }));
79
+ });
80
+ it('query with searchTerm with spaces', () => {
81
+ const searchTerm = 'aviv good guy';
82
+ const splitSearch = searchTerm.split(' ');
83
+ const { query } = (0, _1.default)({
84
+ order: [],
85
+ query: {
86
+ 'json.field': {
87
+ $gt: 4,
88
+ },
89
+ },
90
+ searchTerm,
91
+ }, {
92
+ rawAttributes: {
93
+ startTime: {
94
+ type: {
95
+ key: 'STRING',
96
+ },
97
+ },
98
+ endTime: {
99
+ type: {
100
+ key: 'NUMBER',
101
+ },
102
+ },
103
+ },
104
+ });
105
+ expect(JSON.stringify(query)).toBe(JSON.stringify({
106
+ $and: [{ 'json.field': { $gt: 4 } }, {
107
+ $and: [{
108
+ $or: [
109
+ {
110
+ startTime: {
111
+ $iLike: `%${splitSearch[0]}%`,
112
+ },
113
+ }
114
+ ],
115
+ }, {
116
+ $or: [{
117
+ startTime: {
118
+ $iLike: `%${splitSearch[1]}%`,
119
+ },
120
+ }],
121
+ },
122
+ {
123
+ $or: [{
124
+ startTime: {
125
+ $iLike: `%${splitSearch[2]}%`,
126
+ },
127
+ }],
128
+ }],
129
+ }],
130
+ }));
131
+ });
132
+ });
133
+ describe('order formatting', () => {
134
+ it('simple check', () => {
135
+ const { order } = (0, _1.default)({
136
+ order: ['startTime', '-endTime'],
137
+ });
138
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['startTime'], ['endTime', 'DESC'], ['id']]));
139
+ });
140
+ it('simple check asc', () => {
141
+ const { order } = (0, _1.default)({
142
+ order: ['startTime', 'endTime'],
143
+ });
144
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['startTime'], ['endTime'], ['id']]));
145
+ });
146
+ it('by included model', () => {
147
+ const { order } = (0, _1.default)({
148
+ order: ['status.name'],
149
+ }, {
150
+ rawAttributes: {
151
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
152
+ },
153
+ associations: {
154
+ status: {},
155
+ },
156
+ });
157
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['status', 'name'], ['id']]));
158
+ });
159
+ it('by included model descending', () => {
160
+ const { order } = (0, _1.default)({
161
+ order: ['-status.name'],
162
+ }, {
163
+ rawAttributes: {
164
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
165
+ },
166
+ associations: {
167
+ status: {},
168
+ },
169
+ });
170
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['status', 'name', 'DESC'], ['id']]));
171
+ });
172
+ it('by json field', () => {
173
+ const { order } = (0, _1.default)({
174
+ order: ['status.name'],
175
+ }, {
176
+ rawAttributes: {
177
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', json: '',
178
+ },
179
+ });
180
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['status.name'], ['id']]));
181
+ });
182
+ it('by json field descending', () => {
183
+ const { order } = (0, _1.default)({
184
+ order: ['-status.name'],
185
+ }, {
186
+ rawAttributes: {
187
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', json: '',
188
+ },
189
+ });
190
+ expect(JSON.stringify(order)).toBe(JSON.stringify([['status.name', 'DESC'], ['id']]));
191
+ });
192
+ it('by literal field ascending', () => {
193
+ const literalAttribute = { attribute: 'genericField', literal: [{ 0: (e) => `${e}0` }] };
194
+ const { order, attributes } = (0, _1.default)({
195
+ order: [literalAttribute.attribute],
196
+ }, {
197
+ rawAttributes: {
198
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
199
+ },
200
+ }, {
201
+ literalAttributes: [literalAttribute],
202
+ });
203
+ expect(JSON.stringify(order)).toBe(JSON.stringify([[`${literalAttribute.attribute} ASC`], ['id']]));
204
+ expect(JSON.stringify(attributes))
205
+ .toBe(JSON.stringify({ include: [literalAttribute.literal] }));
206
+ });
207
+ it('by literal field descending', () => {
208
+ const literalAttribute = { attribute: 'genericField', literal: ['void', 'field'] };
209
+ const { order, attributes } = (0, _1.default)({
210
+ order: [`-${literalAttribute.attribute}`, 'currencySymbol'],
211
+ attributes: ['endTime'],
212
+ }, {
213
+ rawAttributes: {
214
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
215
+ },
216
+ }, {
217
+ literalAttributes: [literalAttribute],
218
+ });
219
+ expect(JSON.stringify(order)).toBe(JSON.stringify([[`${literalAttribute.attribute} DESC`], ['currencySymbol'], ['id']]));
220
+ expect(JSON.stringify(attributes))
221
+ .toBe(JSON.stringify([[literalAttribute.literal[0], literalAttribute.literal[1]], 'endTime']));
222
+ });
223
+ it('by literal field - not found', () => {
224
+ const literalAttribute = { attribute: 'genericField', literal: 'void' };
225
+ const { order, attributes } = (0, _1.default)({
226
+ order: [`-O${literalAttribute.attribute}`],
227
+ }, {
228
+ rawAttributes: {
229
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
230
+ },
231
+ }, {
232
+ literalAttributes: [literalAttribute],
233
+ });
234
+ expect(JSON.stringify(order)).toBe(JSON.stringify([[`O${literalAttribute.attribute}`, 'DESC'], ['id']]));
235
+ expect(JSON.stringify(attributes)).toBe(JSON.stringify({ include: [] }));
236
+ });
237
+ });
238
+ describe('include formatting', () => {
239
+ it('include required should be true', () => {
240
+ const { include } = (0, _1.default)({
241
+ order: [],
242
+ include: [{
243
+ model: 'status',
244
+ }],
245
+ }, {
246
+ associations: {
247
+ status: {},
248
+ },
249
+ });
250
+ expect(include[0].required).toBe(true);
251
+ });
252
+ it('include required should be false', () => {
253
+ const { include } = (0, _1.default)({
254
+ order: [],
255
+ include: [{
256
+ model: 'status',
257
+ required: false,
258
+ }],
259
+ }, {
260
+ associations: {
261
+ status: {},
262
+ },
263
+ });
264
+ expect(include[0].required).toBe(false);
265
+ });
266
+ });
267
+ });
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { formatOperators } from './operators';
2
+ import { queryFormatMiddleware, queryValidationMiddleware, MiddlewareValidationOption, LiteralAttribute } from './middleware';
3
+ export { formatOperators, queryFormatMiddleware, queryValidationMiddleware, MiddlewareValidationOption, LiteralAttribute, };
package/lib/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryValidationMiddleware = exports.queryFormatMiddleware = exports.formatOperators = void 0;
4
+ const operators_1 = require("./operators");
5
+ Object.defineProperty(exports, "formatOperators", { enumerable: true, get: function () { return operators_1.formatOperators; } });
6
+ const middleware_1 = require("./middleware");
7
+ Object.defineProperty(exports, "queryFormatMiddleware", { enumerable: true, get: function () { return middleware_1.queryFormatMiddleware; } });
8
+ Object.defineProperty(exports, "queryValidationMiddleware", { enumerable: true, get: function () { return middleware_1.queryValidationMiddleware; } });
@@ -0,0 +1,17 @@
1
+ import { FormatPayloadOptions } from '../formatter';
2
+ type literal = any;
3
+ type LiteralQuery = (literal | string)[] | literal;
4
+ export type LiteralAttribute = {
5
+ attribute: string;
6
+ literal: LiteralQuery;
7
+ };
8
+ export type MiddlewareValidationOption = {
9
+ literalAttributes?: LiteralAttribute[];
10
+ enrichmentAttributes?: string[];
11
+ additionalAllowedAttributes?: string[];
12
+ };
13
+ export declare const newQueryValidationMiddleware: (inner?: string) => (model: any, options?: MiddlewareValidationOption) => (req: any, res: any, next: any) => Promise<void>;
14
+ export declare const newQueryFormatMiddleware: (inner?: string) => (model: any, options?: FormatPayloadOptions) => (req: any, res: any, next: any) => Promise<void>;
15
+ export declare const queryValidationMiddleware: (model: any, options?: MiddlewareValidationOption) => (req: any, res: any, next: any) => Promise<void>;
16
+ export declare const queryFormatMiddleware: (model: any, options?: FormatPayloadOptions) => (req: any, res: any, next: any) => Promise<void>;
17
+ export {};
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.queryFormatMiddleware = exports.queryValidationMiddleware = exports.newQueryFormatMiddleware = exports.newQueryValidationMiddleware = void 0;
7
+ const logger_1 = __importDefault(require("@autofleet/logger"));
8
+ const errors_1 = require("@autofleet/errors");
9
+ const joi_1 = __importDefault(require("joi"));
10
+ const formatter_1 = __importDefault(require("../formatter"));
11
+ const validations_1 = require("../validations");
12
+ const { object, string, number, any, array, alternatives, } = joi_1.default.types();
13
+ const logger = (0, logger_1.default)();
14
+ const querySchema = object.keys({
15
+ query: object,
16
+ attributes: array.items(string),
17
+ order: array.items(string),
18
+ page: number,
19
+ perPage: number,
20
+ include: array.items(any),
21
+ searchTerm: string,
22
+ group: array.items(string),
23
+ enrichments: alternatives.try(array.items(string), object.pattern(string, { exclude: array.items(string) })),
24
+ });
25
+ const newQueryValidationMiddleware = (inner = 'body') => (model, options = {}) => async (req, res, next) => {
26
+ const { query, attributes, order, page, perPage, include, group, enrichments, } = req[inner];
27
+ try {
28
+ const result = querySchema.validate(req[inner]);
29
+ if (result.error) {
30
+ throw new errors_1.BadRequest([result.error], null);
31
+ }
32
+ (0, validations_1.validatePayload)({
33
+ query,
34
+ attributes,
35
+ order,
36
+ page,
37
+ perPage,
38
+ include,
39
+ enrichments,
40
+ group,
41
+ }, model, options);
42
+ return next();
43
+ }
44
+ catch (error) {
45
+ return (0, errors_1.handleError)(error, res, {
46
+ logger,
47
+ message: 'error in query middleware',
48
+ payload: {
49
+ error,
50
+ query,
51
+ attributes,
52
+ order,
53
+ },
54
+ });
55
+ }
56
+ };
57
+ exports.newQueryValidationMiddleware = newQueryValidationMiddleware;
58
+ const newQueryFormatMiddleware = (inner = 'body') => (model, options = {}) => async (req, res, next) => {
59
+ const { order, page, perPage, include, query, attributes, searchTerm, } = req[inner];
60
+ const { query: formattedQuery, externalQueryValues, order: formattedOrder, page: formattedPage, perPage: formattedPerPage, include: formattedInclude, scopes: formattedScopes, attributes: formattedAttribute, } = (0, formatter_1.default)({
61
+ query,
62
+ order,
63
+ page,
64
+ perPage,
65
+ include,
66
+ attributes,
67
+ searchTerm,
68
+ }, model, options);
69
+ req[inner].query = formattedQuery;
70
+ req[inner].externalQueryValues = externalQueryValues;
71
+ req[inner].order = formattedOrder;
72
+ req[inner].attributes = formattedAttribute;
73
+ req[inner].page = formattedPage;
74
+ req[inner].perPage = formattedPerPage;
75
+ req[inner].include = formattedInclude;
76
+ req[inner].scopes = formattedScopes;
77
+ if (options.includeRawPayload) {
78
+ req[inner].rawPayload = {
79
+ order,
80
+ page,
81
+ perPage,
82
+ include,
83
+ query,
84
+ attributes,
85
+ searchTerm,
86
+ };
87
+ }
88
+ return next();
89
+ };
90
+ exports.newQueryFormatMiddleware = newQueryFormatMiddleware;
91
+ exports.queryValidationMiddleware = (0, exports.newQueryValidationMiddleware)();
92
+ exports.queryFormatMiddleware = (0, exports.newQueryFormatMiddleware)();
@@ -0,0 +1,20 @@
1
+ export declare const OPERATORS: string[];
2
+ export declare const OPERATOR_PREFIX = "$";
3
+ export declare const OPERATORS_TO_SQL: {
4
+ $eq: string;
5
+ $ne: string;
6
+ $gte: string;
7
+ $gt: string;
8
+ $lte: string;
9
+ $lt: string;
10
+ $not: string;
11
+ $in: string;
12
+ $notIn: string;
13
+ $is: string;
14
+ $like: string;
15
+ $iLike: string;
16
+ $notLike: string;
17
+ $and: string;
18
+ $or: string;
19
+ };
20
+ export declare const formatOperators: (Sequelize: any) => {};
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatOperators = exports.OPERATORS_TO_SQL = exports.OPERATOR_PREFIX = exports.OPERATORS = void 0;
4
+ exports.OPERATORS = [
5
+ 'eq',
6
+ 'ne',
7
+ 'gte',
8
+ 'gt',
9
+ 'lte',
10
+ 'lt',
11
+ 'not',
12
+ 'in',
13
+ 'notIn',
14
+ 'is',
15
+ 'like',
16
+ 'iLike',
17
+ 'notLike',
18
+ 'between',
19
+ 'and',
20
+ 'or',
21
+ 'overlap',
22
+ 'contains',
23
+ ];
24
+ exports.OPERATOR_PREFIX = '$';
25
+ exports.OPERATORS_TO_SQL = {
26
+ $eq: '=',
27
+ $ne: '!=',
28
+ $gte: '>=',
29
+ $gt: '>',
30
+ $lte: '<=',
31
+ $lt: '<',
32
+ $not: 'NOT',
33
+ $in: 'IN',
34
+ $notIn: 'NOT IN',
35
+ $is: 'IS',
36
+ $like: 'LIKE',
37
+ $iLike: 'ILIKE',
38
+ $notLike: 'NOT LIKE',
39
+ $and: 'AND',
40
+ $or: 'OR',
41
+ };
42
+ const formatOperators = (Sequelize) => {
43
+ const { Op } = Sequelize;
44
+ return exports.OPERATORS.reduce((map, o) => {
45
+ // eslint-disable-next-line no-param-reassign
46
+ map[`${exports.OPERATOR_PREFIX + o}`] = Op[o];
47
+ return map;
48
+ }, {});
49
+ };
50
+ exports.formatOperators = formatOperators;
package/lib/utils.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare const ORDER_PREFIX = "-";
2
+ export declare const ASSOCIATION_PREFIX = ".";
3
+ export declare const PER_PAGE_DEFAULT = 20;
4
+ export declare const PAGE_DEFAULT = 1;
5
+ export declare const PER_PAGE_MAX_LIMIT = 100;
6
+ export declare const PER_PAGE_MIN_LIMIT = 1;
7
+ export declare const PAGE_MIN = 1;
8
+ export declare const wrapAttributeWithOperator: (attribute: any) => string;
9
+ export declare const isAttributeByAssociation: (attributeName: any, associatedModels: any) => boolean;
10
+ export declare const extractAttributeNameFromOrder: (order: any, associationModels: any) => string;
11
+ export declare const isOrderDesc: (order: any) => boolean;
12
+ export declare const throwBadRequestError: (message: string) => never;
13
+ export declare const extractAssociatedAttributeNameFromOrder: (order: any) => string;
14
+ export declare const generateRandomString: (length?: number) => string;
package/lib/utils.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateRandomString = exports.extractAssociatedAttributeNameFromOrder = exports.throwBadRequestError = exports.isOrderDesc = exports.extractAttributeNameFromOrder = exports.isAttributeByAssociation = exports.wrapAttributeWithOperator = exports.PAGE_MIN = exports.PER_PAGE_MIN_LIMIT = exports.PER_PAGE_MAX_LIMIT = exports.PAGE_DEFAULT = exports.PER_PAGE_DEFAULT = exports.ASSOCIATION_PREFIX = exports.ORDER_PREFIX = void 0;
4
+ const errors_1 = require("@autofleet/errors");
5
+ const operators_1 = require("./operators");
6
+ const randomInt = max => Math.floor(Math.random() * Math.floor(max));
7
+ exports.ORDER_PREFIX = '-';
8
+ exports.ASSOCIATION_PREFIX = '.';
9
+ exports.PER_PAGE_DEFAULT = 20;
10
+ exports.PAGE_DEFAULT = 1;
11
+ exports.PER_PAGE_MAX_LIMIT = 100;
12
+ exports.PER_PAGE_MIN_LIMIT = 1;
13
+ exports.PAGE_MIN = 1;
14
+ const wrapAttributeWithOperator = attribute => `${operators_1.OPERATOR_PREFIX}${attribute}${operators_1.OPERATOR_PREFIX}`;
15
+ exports.wrapAttributeWithOperator = wrapAttributeWithOperator;
16
+ const isAttributeByAssociation = (attributeName, associatedModels) => attributeName.includes(exports.ASSOCIATION_PREFIX)
17
+ && associatedModels.includes(attributeName.split(exports.ASSOCIATION_PREFIX)[0]);
18
+ exports.isAttributeByAssociation = isAttributeByAssociation;
19
+ const extractAttributeNameFromOrder = (order, associationModels) => {
20
+ let formattedOrder = order;
21
+ if (order.includes(exports.ORDER_PREFIX)) {
22
+ // eslint-disable-next-line prefer-destructuring
23
+ formattedOrder = formattedOrder.split(exports.ORDER_PREFIX)[1];
24
+ }
25
+ if ((0, exports.isAttributeByAssociation)(formattedOrder, associationModels)) {
26
+ [formattedOrder] = formattedOrder.split(exports.ASSOCIATION_PREFIX);
27
+ }
28
+ return formattedOrder;
29
+ };
30
+ exports.extractAttributeNameFromOrder = extractAttributeNameFromOrder;
31
+ const isOrderDesc = (order) => order.includes(exports.ORDER_PREFIX);
32
+ exports.isOrderDesc = isOrderDesc;
33
+ const throwBadRequestError = (message) => {
34
+ throw new errors_1.BadRequest([new Error(message)], null);
35
+ };
36
+ exports.throwBadRequestError = throwBadRequestError;
37
+ const extractAssociatedAttributeNameFromOrder = (order) => order.split(exports.ASSOCIATION_PREFIX)[1];
38
+ exports.extractAssociatedAttributeNameFromOrder = extractAssociatedAttributeNameFromOrder;
39
+ const generateRandomString = (length = 5) => {
40
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
41
+ return Array.from({ length }, () => characters.charAt(randomInt(characters.length))).join('');
42
+ };
43
+ exports.generateRandomString = generateRandomString;
@@ -0,0 +1,11 @@
1
+ import type { MiddlewareValidationOption } from '../middleware';
2
+ export declare const validatePayload: ({ query, order, attributes, include, page, perPage, enrichments, group, }: {
3
+ query?: {};
4
+ order?: any[];
5
+ attributes?: any[];
6
+ include?: any[];
7
+ page?: number;
8
+ perPage?: number;
9
+ enrichments?: any[];
10
+ group?: any[];
11
+ }, model?: any, options?: MiddlewareValidationOption) => boolean;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePayload = void 0;
4
+ const utils_1 = require("../utils");
5
+ const operators_1 = require("../operators");
6
+ const validateOperator = (operator) => operators_1.OPERATORS.includes(operator.split(operators_1.OPERATOR_PREFIX)[1]);
7
+ const validateQueryAttribute = (attribute, modelAttributes = [], associationModels = [], additionalAllowedAttributes = []) => ([...modelAttributes, ...associationModels].includes(attribute.includes(utils_1.ASSOCIATION_PREFIX) ? attribute.split(utils_1.ASSOCIATION_PREFIX)[0] : attribute)
8
+ || additionalAllowedAttributes.includes(attribute));
9
+ const validateSingleOrder = (currentOrder, rawAttributes, associationModels, options = {}) => {
10
+ const isOrderDescOrder = (0, utils_1.isOrderDesc)(currentOrder);
11
+ if (isOrderDescOrder && currentOrder[0] !== utils_1.ORDER_PREFIX) {
12
+ (0, utils_1.throwBadRequestError)(`${utils_1.ORDER_PREFIX} must be only at the beginning of the word`);
13
+ }
14
+ const orderStringWithoutDesc = isOrderDescOrder ? currentOrder.split(utils_1.ORDER_PREFIX)[1] : currentOrder;
15
+ const isOrderAssociation = (0, utils_1.isAttributeByAssociation)(orderStringWithoutDesc, associationModels);
16
+ let formattedOrderString = (0, utils_1.extractAttributeNameFromOrder)(currentOrder, associationModels);
17
+ const isLiteralAttribute = options?.literalAttributes?.map((la) => la.attribute)?.includes(orderStringWithoutDesc);
18
+ if (!isOrderAssociation && formattedOrderString.includes(utils_1.ASSOCIATION_PREFIX)) {
19
+ [formattedOrderString] = formattedOrderString.split(utils_1.ASSOCIATION_PREFIX);
20
+ }
21
+ if (!(rawAttributes.includes(formattedOrderString) || isOrderAssociation || isLiteralAttribute)) {
22
+ (0, utils_1.throwBadRequestError)(`${currentOrder} is invalid. isLiteralAttribute: ${isLiteralAttribute}`);
23
+ }
24
+ };
25
+ const validateSingleAttribute = (currentAttribute, rawAttributes) => {
26
+ if (!rawAttributes.includes(currentAttribute)) {
27
+ (0, utils_1.throwBadRequestError)(`${currentAttribute} is invalid`);
28
+ }
29
+ };
30
+ const validateOrderAttributes = (order, rawAttributes, associationModels = [], options = {}) => {
31
+ order.forEach((o) => validateSingleOrder(o, rawAttributes, associationModels, options));
32
+ };
33
+ const validateAttributes = (attributes, rawAttributes) => {
34
+ attributes.forEach((a) => validateSingleAttribute(a, rawAttributes));
35
+ };
36
+ const validateEnrichments = (enrichments, options) => {
37
+ const enrichmentKeys = Array.isArray(enrichments) ? enrichments : Object.keys(enrichments);
38
+ if (!enrichmentKeys?.length) {
39
+ return;
40
+ }
41
+ const invalidEnrichment = enrichmentKeys.find((enrichment) => !options?.enrichmentAttributes?.includes(enrichment));
42
+ if (invalidEnrichment) {
43
+ (0, utils_1.throwBadRequestError)(`enrichment attribute ${invalidEnrichment} is invalid`);
44
+ }
45
+ };
46
+ const validateQueryPayload = (query, rawAttributes, associationModels = [], additionalAllowedAttributes = []) => {
47
+ Object.entries(query).forEach(([key, value]) => {
48
+ if (Array.isArray(value)) {
49
+ if (value[0] && typeof value[0] === 'object') {
50
+ value.map((v) => validateQueryPayload(v, rawAttributes, associationModels, additionalAllowedAttributes));
51
+ }
52
+ }
53
+ else if (validateOperator(key) || validateQueryAttribute(key, rawAttributes, associationModels, additionalAllowedAttributes)) {
54
+ if (value && typeof value === 'object') {
55
+ validateQueryPayload(value, rawAttributes, [], additionalAllowedAttributes);
56
+ }
57
+ }
58
+ else {
59
+ (0, utils_1.throwBadRequestError)(`invalid key: ${key}`);
60
+ }
61
+ });
62
+ };
63
+ const validatePagination = ({ page, perPage, }) => {
64
+ if (page < utils_1.PAGE_MIN) {
65
+ (0, utils_1.throwBadRequestError)('Page must be greater than 0');
66
+ }
67
+ if (perPage > utils_1.PER_PAGE_MAX_LIMIT || perPage < utils_1.PER_PAGE_MIN_LIMIT) {
68
+ (0, utils_1.throwBadRequestError)(`PerPage must be between ${utils_1.PER_PAGE_MIN_LIMIT} to ${utils_1.PER_PAGE_MAX_LIMIT}`);
69
+ }
70
+ };
71
+ const validateIncludePayload = (include, associations) => {
72
+ const associationsKeys = Object.keys(associations);
73
+ include.forEach((i) => {
74
+ validateQueryAttribute(i.model, associationsKeys);
75
+ const target = associations[i.model]?.target;
76
+ if (!target) {
77
+ (0, utils_1.throwBadRequestError)('model not found in associations');
78
+ }
79
+ const { rawAttributes } = target;
80
+ const attributeKeys = Object.keys(rawAttributes);
81
+ if (i.where) {
82
+ validateQueryPayload(i.where, attributeKeys);
83
+ }
84
+ if (i.order) {
85
+ validateOrderAttributes(i.order, attributeKeys);
86
+ }
87
+ if (i.attributes) {
88
+ validateAttributes(i.attributes, attributeKeys);
89
+ }
90
+ if (![null, undefined, true, false].includes(i.required)) {
91
+ (0, utils_1.throwBadRequestError)('include.required must be a boolean');
92
+ }
93
+ });
94
+ };
95
+ const validatePayload = ({ query = {}, order = [], attributes = [], include = [], page = utils_1.PAGE_DEFAULT, perPage = utils_1.PER_PAGE_DEFAULT, enrichments = [], group = [], }, model, options = {}) => {
96
+ const rawAttributes = Object.keys(model.rawAttributes);
97
+ const associationModels = Object.keys(model?.associations || {});
98
+ if (!attributes || attributes.length === 0) {
99
+ // eslint-disable-next-line no-param-reassign
100
+ attributes = rawAttributes;
101
+ }
102
+ else {
103
+ validateAttributes(attributes, rawAttributes);
104
+ }
105
+ validateOrderAttributes(order, rawAttributes, associationModels, options);
106
+ validateQueryPayload(query, rawAttributes, associationModels, options.additionalAllowedAttributes);
107
+ validateEnrichments(enrichments, options);
108
+ if (!Array.isArray(group)) {
109
+ (0, utils_1.throwBadRequestError)('group must be an array');
110
+ }
111
+ if (include.length && typeof include === 'object') {
112
+ validateIncludePayload(include, model?.associations);
113
+ }
114
+ else if (include && typeof include !== 'object') {
115
+ (0, utils_1.throwBadRequestError)('include must be an array');
116
+ }
117
+ validatePagination({
118
+ page,
119
+ perPage,
120
+ });
121
+ return true;
122
+ };
123
+ exports.validatePayload = validatePayload;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const _1 = require(".");
4
+ const query = {
5
+ $and: [
6
+ {
7
+ startTime: {
8
+ $gte: '2019-09-10 12:00:00',
9
+ },
10
+ },
11
+ {
12
+ endTime: {
13
+ $lt: '2019-09-07 12:00:00',
14
+ },
15
+ },
16
+ ],
17
+ $or: [
18
+ {
19
+ $and: [
20
+ {
21
+ currencySymbol: {
22
+ $eq: ['€', 'f'],
23
+ },
24
+ },
25
+ {
26
+ currencyCode: {
27
+ $eq: 'EUR',
28
+ },
29
+ },
30
+ ],
31
+ },
32
+ {
33
+ startStationId: {
34
+ $eq: '1',
35
+ },
36
+ },
37
+ ],
38
+ };
39
+ const order = ['startTime', '-endTime'];
40
+ describe('validations test', () => {
41
+ describe('query validation', () => {
42
+ it('check query validation', () => {
43
+ const payload = (0, _1.validatePayload)({ query }, {
44
+ rawAttributes: {
45
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
46
+ },
47
+ });
48
+ expect(payload).toBe(true);
49
+ });
50
+ it('check query by json property field', () => {
51
+ const payload = (0, _1.validatePayload)({
52
+ query: {
53
+ 'jsonField.exampleField': {
54
+ $eq: 'a',
55
+ },
56
+ },
57
+ }, {
58
+ rawAttributes: {
59
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', jsonField: {},
60
+ },
61
+ });
62
+ expect(payload).toBe(true);
63
+ });
64
+ it('check query by included model field', () => {
65
+ const payload = (0, _1.validatePayload)({
66
+ query: {
67
+ 'status.name': {
68
+ $eq: 'a',
69
+ },
70
+ },
71
+ }, {
72
+ rawAttributes: {
73
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
74
+ },
75
+ associations: {
76
+ status: {},
77
+ },
78
+ });
79
+ expect(payload).toBe(true);
80
+ });
81
+ it('check query by non existent field', () => {
82
+ try {
83
+ (0, _1.validatePayload)({
84
+ query: {
85
+ nonExistent: {
86
+ $eq: 'a',
87
+ },
88
+ },
89
+ }, {
90
+ rawAttributes: {
91
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
92
+ },
93
+ });
94
+ }
95
+ catch (error) {
96
+ expect(error.statusCode).toBe(400);
97
+ }
98
+ });
99
+ });
100
+ describe('order validation', () => {
101
+ it('check order validation', () => {
102
+ const payload = (0, _1.validatePayload)({ order }, {
103
+ rawAttributes: {
104
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
105
+ },
106
+ });
107
+ expect(payload).toBe(true);
108
+ });
109
+ it('order does not exist', () => {
110
+ try {
111
+ (0, _1.validatePayload)({ order: ['aviv'] }, {
112
+ rawAttributes: {
113
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
114
+ },
115
+ });
116
+ }
117
+ catch (error) {
118
+ expect(error.statusCode).toBe(400);
119
+ }
120
+ });
121
+ it('prefix in the wrong place', () => {
122
+ try {
123
+ (0, _1.validatePayload)({ order: ['startTime-', 'currencySymbol'] }, {
124
+ rawAttributes: {
125
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
126
+ },
127
+ });
128
+ }
129
+ catch (error) {
130
+ expect(error.statusCode).toBe(400);
131
+ }
132
+ });
133
+ it('order by included model', () => {
134
+ const payload = (0, _1.validatePayload)({ order: ['status.title'] }, {
135
+ rawAttributes: {
136
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
137
+ },
138
+ associations: {
139
+ status: {},
140
+ },
141
+ });
142
+ expect(payload).toBe(true);
143
+ });
144
+ it('order by included model descending', () => {
145
+ const payload = (0, _1.validatePayload)({ order: ['-status.title'] }, {
146
+ rawAttributes: {
147
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '',
148
+ },
149
+ associations: {
150
+ status: {},
151
+ },
152
+ });
153
+ expect(payload).toBe(true);
154
+ });
155
+ it('order by json field', () => {
156
+ const payload = (0, _1.validatePayload)({ order: ['status.title'] }, {
157
+ rawAttributes: {
158
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
159
+ },
160
+ });
161
+ expect(payload).toBe(true);
162
+ });
163
+ it('order by json field desc', () => {
164
+ const payload = (0, _1.validatePayload)({ order: ['-status.title'] }, {
165
+ rawAttributes: {
166
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
167
+ },
168
+ });
169
+ expect(payload).toBe(true);
170
+ });
171
+ it('try order by json field that doesnt exist', () => {
172
+ try {
173
+ (0, _1.validatePayload)({ order: ['-stas.title'] }, {
174
+ rawAttributes: {
175
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
176
+ },
177
+ });
178
+ }
179
+ catch (error) {
180
+ expect(error.statusCode).toBe(400);
181
+ }
182
+ });
183
+ it('try order by literal field that exist', () => {
184
+ const literalAttribute = { attribute: 'genericField', literal: 'void' };
185
+ const payload = (0, _1.validatePayload)({ order: [literalAttribute.attribute] }, {
186
+ rawAttributes: {
187
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
188
+ },
189
+ }, {
190
+ literalAttributes: [literalAttribute],
191
+ });
192
+ expect(payload).toBe(true);
193
+ });
194
+ it('try order by literal field that exist - desc', () => {
195
+ const literalAttribute = { attribute: 'genericField', literal: 'void' };
196
+ const payload = (0, _1.validatePayload)({ order: [`-${literalAttribute.attribute}`] }, {
197
+ rawAttributes: {
198
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
199
+ },
200
+ }, {
201
+ literalAttributes: [literalAttribute],
202
+ });
203
+ expect(payload).toBe(true);
204
+ });
205
+ it('try order by literal field that doesnt exist', () => {
206
+ const literalAttribute = { attribute: 'genericField', literal: 'void' };
207
+ try {
208
+ (0, _1.validatePayload)({ order: ['here'] }, {
209
+ rawAttributes: {
210
+ startTime: '', endTime: '', currencySymbol: '', currencyCode: '', startStationId: '', status: '',
211
+ },
212
+ }, { literalAttributes: [literalAttribute] });
213
+ }
214
+ catch (error) {
215
+ expect(error.statusCode).toBe(400);
216
+ }
217
+ });
218
+ });
219
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sheilta",
3
- "version": "1.4.8-beta-1",
3
+ "version": "1.4.8-beta-3",
4
4
  "description": "Middlewares for validation and parsing of endpoints meant for data querying.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",