|
1 | 1 | import { |
2 | 2 | AdminForthResource, IAdminForthDataSourceConnectorBase, |
3 | 3 | AdminForthResourceColumn, |
4 | | - IAdminForthSort, IAdminForthFilter |
| 4 | + IAdminForthSort, IAdminForthSingleFilter, IAdminForthAndOrFilter |
5 | 5 | } from "../types/Back.js"; |
6 | 6 |
|
7 | 7 |
|
@@ -32,27 +32,84 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon |
32 | 32 | } |
33 | 33 |
|
34 | 34 | async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> { |
35 | | - const data = await this.getDataWithOriginalTypes({ |
36 | | - resource, |
37 | | - limit: 1, |
| 35 | + const data = await this.getDataWithOriginalTypes({ |
| 36 | + resource, |
| 37 | + limit: 1, |
38 | 38 | offset: 0, |
39 | | - sort: [], |
40 | | - filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }], |
| 39 | + sort: [], |
| 40 | + filters: { operator: AdminForthFilterOperators.AND, subFilters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }]}, |
41 | 41 | }); |
42 | 42 | return data.length > 0 ? data[0] : null; |
43 | 43 | } |
44 | 44 |
|
| 45 | + validateAndNormalizeFilters(filters: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>, resource: AdminForthResource): { ok: boolean, error: string } { |
| 46 | + if (Array.isArray(filters)) { |
| 47 | + // go through all filters in array and call validation+normalization for each |
| 48 | + // as soon as error is encountered, there is no point in calling validation for other filters |
| 49 | + // if error is not encountered all filters will be validated and normalized |
| 50 | + return filters.reduce((result, f) => { |
| 51 | + if (!result.ok) { |
| 52 | + return result; |
| 53 | + } |
| 54 | + |
| 55 | + return this.validateAndNormalizeFilters(f, resource); |
| 56 | + }, { ok: true, error: '' }); |
| 57 | + } |
| 58 | + |
| 59 | + if (!filters.operator) { |
| 60 | + return { ok: false, error: `Field "operator" not specified in filter object: ${JSON.stringify(filters)}` }; |
| 61 | + } |
| 62 | + |
| 63 | + if ((filters as IAdminForthSingleFilter).field) { |
| 64 | + // if "field" is present, filter must be Single |
| 65 | + if (![AdminForthFilterOperators.EQ, AdminForthFilterOperators.NE, AdminForthFilterOperators.GT, |
| 66 | + AdminForthFilterOperators.LT, AdminForthFilterOperators.GTE, AdminForthFilterOperators.LTE, |
| 67 | + AdminForthFilterOperators.LIKE, AdminForthFilterOperators.ILIKE, AdminForthFilterOperators.IN, |
| 68 | + AdminForthFilterOperators.NIN].includes(filters.operator)) { |
| 69 | + return { ok: false, error: `Field "operator" has wrong value in filter object: ${JSON.stringify(filters)}` }; |
| 70 | + } |
| 71 | + const fieldObj = resource.dataSourceColumns.find((col) => col.name == (filters as IAdminForthSingleFilter).field); |
| 72 | + if (!fieldObj) { |
| 73 | + const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), (filters as IAdminForthSingleFilter).field); |
| 74 | + throw new Error(`Field '${(filters as IAdminForthSingleFilter).field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`); |
| 75 | + } |
| 76 | + if (filters.operator == AdminForthFilterOperators.IN || filters.operator == AdminForthFilterOperators.NIN) { |
| 77 | + if (!Array.isArray(filters.value)) { |
| 78 | + return { ok: false, error: `Value for operator '${filters.operator}' should be an array, in filter object: ${JSON.stringify(filters) }` }; |
| 79 | + } |
| 80 | + if (filters.value.length === 0) { |
| 81 | + // nonsense |
| 82 | + return { ok: false, error: `Filter has IN operator but empty value: ${JSON.stringify(filters)}` }; |
| 83 | + } |
| 84 | + filters.value = filters.value.map((val: any) => this.setFieldValue(fieldObj, val)); |
| 85 | + } else { |
| 86 | + (filters as IAdminForthSingleFilter).value = this.setFieldValue(fieldObj, (filters as IAdminForthSingleFilter).value); |
| 87 | + } |
| 88 | + } else if ((filters as IAdminForthAndOrFilter).subFilters) { |
| 89 | + // if "subFilters" is present, filter must be AndOr |
| 90 | + if (![AdminForthFilterOperators.AND, AdminForthFilterOperators.OR].includes(filters.operator)) { |
| 91 | + return { ok: false, error: `Field "operator" has wrong value in filter object: ${JSON.stringify(filters)}` }; |
| 92 | + } |
| 93 | + |
| 94 | + return this.validateAndNormalizeFilters((filters as IAdminForthAndOrFilter).subFilters, resource); |
| 95 | + } else { |
| 96 | + return { ok: false, error: `Fields "field" or "subFilters" are not specified in filter object: ${JSON.stringify(filters)}` }; |
| 97 | + } |
| 98 | + |
| 99 | + return { ok: true, error: '' }; |
| 100 | + } |
| 101 | + |
45 | 102 | getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: { |
46 | 103 | resource: AdminForthResource, |
47 | 104 | limit: number, |
48 | 105 | offset: number, |
49 | 106 | sort: IAdminForthSort[], |
50 | | - filters: IAdminForthFilter[], |
| 107 | + filters: IAdminForthAndOrFilter, |
51 | 108 | }): Promise<any[]> { |
52 | 109 | throw new Error('Method not implemented.'); |
53 | 110 | } |
54 | 111 |
|
55 | | - getCount({ resource, filters }: { resource: AdminForthResource; filters: { field: string; operator: AdminForthFilterOperators; value: any; }[]; }): Promise<number> { |
| 112 | + getCount({ resource, filters }: { resource: AdminForthResource; filters: IAdminForthAndOrFilter; }): Promise<number> { |
56 | 113 | throw new Error('Method not implemented.'); |
57 | 114 | } |
58 | 115 |
|
@@ -80,7 +137,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon |
80 | 137 | process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value); |
81 | 138 | const existingRecord = await this.getData({ |
82 | 139 | resource, |
83 | | - filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }], |
| 140 | + filters: { operator: AdminForthFilterOperators.AND, subFilters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }]}, |
84 | 141 | limit: 1, |
85 | 142 | sort: [], |
86 | 143 | offset: 0, |
@@ -130,7 +187,12 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon |
130 | 187 | } |
131 | 188 |
|
132 | 189 | process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record',JSON.stringify(recordWithOriginalValues)); |
133 | | - const pkValue = await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues }); |
| 190 | + let pkValue = await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues }); |
| 191 | + if (recordWithOriginalValues[this.getPrimaryKey(resource)] !== undefined) { |
| 192 | + // some data sources always return some value for pk, even if it is was not auto generated |
| 193 | + // this check prevents wrong value from being used later in get request |
| 194 | + pkValue = recordWithOriginalValues[this.getPrimaryKey(resource)]; |
| 195 | + } |
134 | 196 |
|
135 | 197 | let createdRecord = recordWithOriginalValues; |
136 | 198 | if (pkValue) { |
@@ -175,38 +237,19 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon |
175 | 237 | throw new Error('Method not implemented.'); |
176 | 238 | } |
177 | 239 |
|
178 | | - |
179 | 240 | async getData({ resource, limit, offset, sort, filters, getTotals }: { |
180 | 241 | resource: AdminForthResource, |
181 | 242 | limit: number, |
182 | 243 | offset: number, |
183 | 244 | sort: { field: string, direction: AdminForthSortDirections }[], |
184 | | - filters: { field: string, operator: AdminForthFilterOperators, value: any }[], |
| 245 | + filters: IAdminForthAndOrFilter, |
185 | 246 | getTotals: boolean, |
186 | 247 | }): Promise<{ data: any[], total: number }> { |
187 | 248 | if (filters) { |
188 | | - for (const f of filters) { |
189 | | - if (!f.field) { |
190 | | - throw new Error(`Field "field" not specified in filter object: ${JSON.stringify(f)}`); |
191 | | - } |
192 | | - if (!f.operator) { |
193 | | - throw new Error(`Field "operator" not specified in filter object: ${JSON.stringify(f)}`); |
194 | | - } |
195 | | - const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field); |
196 | | - if (!fieldObj) { |
197 | | - const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field); |
198 | | - throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`); |
199 | | - } |
200 | | - if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) { |
201 | | - f.value = f.value.map((val) => this.setFieldValue(fieldObj, val)); |
202 | | - } else { |
203 | | - f.value = this.setFieldValue(fieldObj, f.value); |
204 | | - } |
205 | | - if (f.operator === AdminForthFilterOperators.IN && f.value.length === 0) { |
206 | | - // nonsense |
207 | | - return { data: [], total: 0 }; |
208 | | - } |
209 | | - }; |
| 249 | + const filterValidation = this.validateAndNormalizeFilters(filters, resource); |
| 250 | + if (!filterValidation.ok) { |
| 251 | + throw new Error(filterValidation.error); |
| 252 | + } |
210 | 253 | } |
211 | 254 |
|
212 | 255 | const promises: Promise<any>[] = [this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters })]; |
|
0 commit comments