adminforth 2.27.0-next.5 → 2.27.0-next.50
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/commands/callTsProxy.js +10 -5
- package/commands/createApp/templates/api.ts.hbs +12 -2
- package/commands/proxy.ts +18 -10
- package/dist/commands/proxy.js +14 -10
- package/dist/commands/proxy.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts +5 -2
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +73 -9
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +16 -9
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +515 -9
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.js +1 -1
- package/dist/modules/utils.d.ts +1 -1
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +3 -5
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts +2 -6
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +46 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/servers/openapi.d.ts +25 -0
- package/dist/servers/openapi.d.ts.map +1 -0
- package/dist/servers/openapi.js +126 -0
- package/dist/servers/openapi.js.map +1 -0
- package/dist/spa/package-lock.json +41 -0
- package/dist/spa/package.json +4 -0
- package/dist/spa/pnpm-lock.yaml +38 -0
- package/dist/spa/src/App.vue +77 -76
- package/dist/spa/src/afcl/Button.vue +2 -3
- package/dist/spa/src/afcl/Dialog.vue +1 -1
- package/dist/spa/src/afcl/Input.vue +1 -1
- package/dist/spa/src/afcl/Select.vue +8 -2
- package/dist/spa/src/afcl/Skeleton.vue +5 -0
- package/dist/spa/src/afcl/Spinner.vue +1 -1
- package/dist/spa/src/components/CallActionWrapper.vue +1 -1
- package/dist/spa/src/components/ColumnValueInput.vue +16 -3
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +25 -2
- package/dist/spa/src/components/CustomRangePicker.vue +16 -14
- package/dist/spa/src/components/Filters.vue +95 -63
- package/dist/spa/src/components/GroupsTable.vue +9 -6
- package/dist/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/src/components/ResourceForm.vue +101 -7
- package/dist/spa/src/components/ResourceListTable.vue +14 -8
- package/dist/spa/src/components/ShowTable.vue +1 -1
- package/dist/spa/src/components/Sidebar.vue +29 -8
- package/dist/spa/src/components/ThreeDotsMenu.vue +25 -10
- package/dist/spa/src/components/ValueRenderer.vue +1 -0
- package/dist/spa/src/spa_types/core.ts +32 -0
- package/dist/spa/src/stores/filters.ts +16 -12
- package/dist/spa/src/types/Back.ts +91 -24
- package/dist/spa/src/types/Common.ts +24 -5
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +27 -5
- package/dist/spa/src/types/adapters/index.ts +2 -2
- package/dist/spa/src/utils/createEditUtils.ts +65 -0
- package/dist/spa/src/utils/index.ts +2 -1
- package/dist/spa/src/utils/utils.ts +42 -7
- package/dist/spa/src/utils.ts +2 -1
- package/dist/spa/src/views/CreateEditSkeleton.vue +78 -0
- package/dist/spa/src/views/CreateView.vue +24 -50
- package/dist/spa/src/views/EditView.vue +23 -40
- package/dist/spa/src/views/ListView.vue +22 -32
- package/dist/spa/src/views/ShowView.vue +66 -24
- package/dist/types/Back.d.ts +100 -30
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +31 -5
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/adapters/CompletionAdapter.d.ts +18 -3
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -1
- package/dist/types/adapters/index.d.ts +1 -1
- package/dist/types/adapters/index.d.ts.map +1 -1
- package/package.json +9 -5
package/dist/modules/restApi.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cascadeChildrenDelete } from './utils.js';
|
|
|
3
3
|
import { afLogger } from "./logger.js";
|
|
4
4
|
import { ADMINFORTH_VERSION, listify, getLoginPromptHTML, hookResponseError } from './utils.js';
|
|
5
5
|
import AdminForthAuth from "../auth.js";
|
|
6
|
-
import { ActionCheckSource, AdminForthDataTypes, AdminForthFilterOperators, AdminForthResourcePages, AllowedActionsEnum } from "../types/Common.js";
|
|
6
|
+
import { ActionCheckSource, AdminForthDataTypes, AdminForthFilterOperators, AdminForthResourcePages, AdminForthSortDirections, AllowedActionsEnum } from "../types/Common.js";
|
|
7
7
|
import { filtersTools } from "../modules/filtersTools.js";
|
|
8
8
|
async function resolveBoolOrFn(val, ctx) {
|
|
9
9
|
if (typeof val === 'function') {
|
|
@@ -26,6 +26,406 @@ async function isFilledOnCreate(col) {
|
|
|
26
26
|
const fillOnCreate = !!col.fillOnCreate;
|
|
27
27
|
return fillOnCreate;
|
|
28
28
|
}
|
|
29
|
+
const SIMPLE_FILTER_OPERATORS = Object.values(AdminForthFilterOperators).filter((operator) => {
|
|
30
|
+
return operator !== AdminForthFilterOperators.AND && operator !== AdminForthFilterOperators.OR;
|
|
31
|
+
});
|
|
32
|
+
const genericObjectSchema = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
additionalProperties: true,
|
|
35
|
+
};
|
|
36
|
+
const errorResponseSchema = {
|
|
37
|
+
type: 'object',
|
|
38
|
+
required: ['error'],
|
|
39
|
+
properties: {
|
|
40
|
+
error: { type: 'string' },
|
|
41
|
+
},
|
|
42
|
+
additionalProperties: true,
|
|
43
|
+
};
|
|
44
|
+
const recordIdentifierSchema = {
|
|
45
|
+
anyOf: [
|
|
46
|
+
{ type: 'string' },
|
|
47
|
+
{ type: 'number' },
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
const actionIdentifierSchema = {
|
|
51
|
+
anyOf: [
|
|
52
|
+
{ type: 'string' },
|
|
53
|
+
{ type: 'number' },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
const namedColumnSchema = {
|
|
57
|
+
type: 'object',
|
|
58
|
+
required: ['name'],
|
|
59
|
+
properties: {
|
|
60
|
+
name: { type: 'string' },
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: true,
|
|
63
|
+
};
|
|
64
|
+
const validationResultSchema = {
|
|
65
|
+
type: 'object',
|
|
66
|
+
required: ['isValid'],
|
|
67
|
+
properties: {
|
|
68
|
+
isValid: { type: 'boolean' },
|
|
69
|
+
message: { type: 'string' },
|
|
70
|
+
},
|
|
71
|
+
additionalProperties: true,
|
|
72
|
+
};
|
|
73
|
+
const commonFilterSchemaDefs = {
|
|
74
|
+
singleFilter: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
field: { type: 'string' },
|
|
78
|
+
operator: { type: 'string', enum: SIMPLE_FILTER_OPERATORS },
|
|
79
|
+
value: {},
|
|
80
|
+
rightField: { type: 'string' },
|
|
81
|
+
insecureRawSQL: { type: 'string' },
|
|
82
|
+
insecureRawNoSQL: {},
|
|
83
|
+
},
|
|
84
|
+
additionalProperties: true,
|
|
85
|
+
},
|
|
86
|
+
filterNode: {
|
|
87
|
+
anyOf: [
|
|
88
|
+
{ $ref: '#/$defs/singleFilter' },
|
|
89
|
+
{
|
|
90
|
+
type: 'object',
|
|
91
|
+
required: ['operator', 'subFilters'],
|
|
92
|
+
properties: {
|
|
93
|
+
operator: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: [AdminForthFilterOperators.AND, AdminForthFilterOperators.OR],
|
|
96
|
+
},
|
|
97
|
+
subFilters: {
|
|
98
|
+
type: 'array',
|
|
99
|
+
items: { $ref: '#/$defs/filterNode' },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
additionalProperties: true,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
sortItem: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
required: ['field', 'direction'],
|
|
109
|
+
properties: {
|
|
110
|
+
field: { type: 'string' },
|
|
111
|
+
direction: { type: 'string', enum: Object.values(AdminForthSortDirections) },
|
|
112
|
+
},
|
|
113
|
+
additionalProperties: true,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const commonSortSchema = {
|
|
117
|
+
type: 'array',
|
|
118
|
+
items: { $ref: '#/$defs/sortItem' },
|
|
119
|
+
};
|
|
120
|
+
const commonFiltersSchema = {
|
|
121
|
+
oneOf: [
|
|
122
|
+
{
|
|
123
|
+
type: 'array',
|
|
124
|
+
items: { $ref: '#/$defs/filterNode' },
|
|
125
|
+
},
|
|
126
|
+
{ $ref: '#/$defs/filterNode' },
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
function createErrorOrSuccessSchema(successSchema) {
|
|
130
|
+
return {
|
|
131
|
+
anyOf: [
|
|
132
|
+
errorResponseSchema,
|
|
133
|
+
successSchema,
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const getResourceDataRequestSchema = {
|
|
138
|
+
type: 'object',
|
|
139
|
+
$defs: commonFilterSchemaDefs,
|
|
140
|
+
required: ['resourceId', 'source', 'limit', 'offset', 'filters', 'sort'],
|
|
141
|
+
properties: {
|
|
142
|
+
resourceId: { type: 'string' },
|
|
143
|
+
source: { type: 'string', enum: ['show', 'list', 'edit'] },
|
|
144
|
+
limit: { type: 'integer' },
|
|
145
|
+
offset: { type: 'integer' },
|
|
146
|
+
sort: commonSortSchema,
|
|
147
|
+
filters: commonFiltersSchema,
|
|
148
|
+
},
|
|
149
|
+
additionalProperties: true,
|
|
150
|
+
allOf: [
|
|
151
|
+
{
|
|
152
|
+
if: {
|
|
153
|
+
properties: {
|
|
154
|
+
source: { enum: ['show', 'edit'] },
|
|
155
|
+
},
|
|
156
|
+
required: ['source'],
|
|
157
|
+
},
|
|
158
|
+
then: {
|
|
159
|
+
properties: {
|
|
160
|
+
filters: {
|
|
161
|
+
type: 'array',
|
|
162
|
+
items: {
|
|
163
|
+
allOf: [
|
|
164
|
+
{ $ref: '#/$defs/singleFilter' },
|
|
165
|
+
{
|
|
166
|
+
type: 'object',
|
|
167
|
+
required: ['field'],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
const getResourceDataResponseSchema = createErrorOrSuccessSchema({
|
|
178
|
+
type: 'object',
|
|
179
|
+
required: ['data'],
|
|
180
|
+
properties: {
|
|
181
|
+
data: {
|
|
182
|
+
type: 'array',
|
|
183
|
+
items: genericObjectSchema,
|
|
184
|
+
},
|
|
185
|
+
total: { type: 'number' },
|
|
186
|
+
options: genericObjectSchema,
|
|
187
|
+
},
|
|
188
|
+
additionalProperties: true,
|
|
189
|
+
});
|
|
190
|
+
const getMenuBadgesResponseSchema = {
|
|
191
|
+
type: 'object',
|
|
192
|
+
additionalProperties: {
|
|
193
|
+
anyOf: [
|
|
194
|
+
{ type: 'string' },
|
|
195
|
+
{ type: 'number' },
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
const getResourceRequestSchema = {
|
|
200
|
+
type: 'object',
|
|
201
|
+
required: ['resourceId'],
|
|
202
|
+
properties: {
|
|
203
|
+
resourceId: { type: 'string' },
|
|
204
|
+
},
|
|
205
|
+
additionalProperties: true,
|
|
206
|
+
};
|
|
207
|
+
const getResourceResponseSchema = createErrorOrSuccessSchema({
|
|
208
|
+
type: 'object',
|
|
209
|
+
required: ['resource'],
|
|
210
|
+
properties: {
|
|
211
|
+
resource: genericObjectSchema,
|
|
212
|
+
},
|
|
213
|
+
additionalProperties: true,
|
|
214
|
+
});
|
|
215
|
+
const getResourceForeignDataRequestSchema = {
|
|
216
|
+
type: 'object',
|
|
217
|
+
$defs: commonFilterSchemaDefs,
|
|
218
|
+
required: ['resourceId', 'column', 'limit', 'offset'],
|
|
219
|
+
properties: {
|
|
220
|
+
resourceId: { type: 'string' },
|
|
221
|
+
column: { type: 'string' },
|
|
222
|
+
limit: { type: 'integer' },
|
|
223
|
+
offset: { type: 'integer' },
|
|
224
|
+
search: { type: 'string' },
|
|
225
|
+
filters: commonFiltersSchema,
|
|
226
|
+
sort: commonSortSchema,
|
|
227
|
+
},
|
|
228
|
+
additionalProperties: true,
|
|
229
|
+
};
|
|
230
|
+
const getResourceForeignDataResponseSchema = createErrorOrSuccessSchema({
|
|
231
|
+
type: 'object',
|
|
232
|
+
required: ['items'],
|
|
233
|
+
properties: {
|
|
234
|
+
items: {
|
|
235
|
+
type: 'array',
|
|
236
|
+
items: {
|
|
237
|
+
type: 'object',
|
|
238
|
+
required: ['value', 'label'],
|
|
239
|
+
properties: {
|
|
240
|
+
value: {},
|
|
241
|
+
label: { type: 'string' },
|
|
242
|
+
},
|
|
243
|
+
additionalProperties: true,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
additionalProperties: true,
|
|
248
|
+
});
|
|
249
|
+
const getMinMaxForColumnsRequestSchema = {
|
|
250
|
+
type: 'object',
|
|
251
|
+
required: ['resourceId'],
|
|
252
|
+
properties: {
|
|
253
|
+
resourceId: { type: 'string' },
|
|
254
|
+
},
|
|
255
|
+
additionalProperties: true,
|
|
256
|
+
};
|
|
257
|
+
const getMinMaxForColumnsResponseSchema = createErrorOrSuccessSchema({
|
|
258
|
+
type: 'object',
|
|
259
|
+
additionalProperties: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
required: ['min', 'max'],
|
|
262
|
+
properties: {
|
|
263
|
+
min: {},
|
|
264
|
+
max: {},
|
|
265
|
+
},
|
|
266
|
+
additionalProperties: true,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const createRecordRequestSchema = {
|
|
270
|
+
type: 'object',
|
|
271
|
+
required: ['resourceId', 'record', 'requiredColumnsToSkip'],
|
|
272
|
+
properties: {
|
|
273
|
+
resourceId: { type: 'string' },
|
|
274
|
+
record: genericObjectSchema,
|
|
275
|
+
requiredColumnsToSkip: {
|
|
276
|
+
type: 'array',
|
|
277
|
+
items: namedColumnSchema,
|
|
278
|
+
},
|
|
279
|
+
meta: genericObjectSchema,
|
|
280
|
+
},
|
|
281
|
+
additionalProperties: true,
|
|
282
|
+
};
|
|
283
|
+
const createRecordResponseSchema = createErrorOrSuccessSchema({
|
|
284
|
+
type: 'object',
|
|
285
|
+
required: ['ok', 'newRecordId', 'redirectToRecordId'],
|
|
286
|
+
properties: {
|
|
287
|
+
ok: { const: true },
|
|
288
|
+
newRecordId: recordIdentifierSchema,
|
|
289
|
+
redirectToRecordId: recordIdentifierSchema,
|
|
290
|
+
},
|
|
291
|
+
additionalProperties: true,
|
|
292
|
+
});
|
|
293
|
+
const updateRecordRequestSchema = {
|
|
294
|
+
type: 'object',
|
|
295
|
+
required: ['resourceId', 'recordId', 'record'],
|
|
296
|
+
properties: {
|
|
297
|
+
resourceId: { type: 'string' },
|
|
298
|
+
recordId: recordIdentifierSchema,
|
|
299
|
+
record: genericObjectSchema,
|
|
300
|
+
meta: genericObjectSchema,
|
|
301
|
+
},
|
|
302
|
+
additionalProperties: true,
|
|
303
|
+
};
|
|
304
|
+
const updateRecordResponseSchema = createErrorOrSuccessSchema({
|
|
305
|
+
type: 'object',
|
|
306
|
+
required: ['ok'],
|
|
307
|
+
properties: {
|
|
308
|
+
ok: { const: true },
|
|
309
|
+
recordId: recordIdentifierSchema,
|
|
310
|
+
},
|
|
311
|
+
additionalProperties: true,
|
|
312
|
+
});
|
|
313
|
+
const deleteRecordRequestSchema = {
|
|
314
|
+
type: 'object',
|
|
315
|
+
required: ['resourceId', 'primaryKey'],
|
|
316
|
+
properties: {
|
|
317
|
+
resourceId: { type: 'string' },
|
|
318
|
+
primaryKey: recordIdentifierSchema,
|
|
319
|
+
},
|
|
320
|
+
additionalProperties: true,
|
|
321
|
+
};
|
|
322
|
+
const deleteRecordResponseSchema = createErrorOrSuccessSchema({
|
|
323
|
+
type: 'object',
|
|
324
|
+
required: ['ok', 'recordId'],
|
|
325
|
+
properties: {
|
|
326
|
+
ok: { const: true },
|
|
327
|
+
recordId: recordIdentifierSchema,
|
|
328
|
+
},
|
|
329
|
+
additionalProperties: true,
|
|
330
|
+
});
|
|
331
|
+
const startCustomActionRequestSchema = {
|
|
332
|
+
type: 'object',
|
|
333
|
+
required: ['resourceId', 'actionId', 'recordId'],
|
|
334
|
+
properties: {
|
|
335
|
+
resourceId: { type: 'string' },
|
|
336
|
+
actionId: actionIdentifierSchema,
|
|
337
|
+
recordId: recordIdentifierSchema,
|
|
338
|
+
extra: genericObjectSchema,
|
|
339
|
+
},
|
|
340
|
+
additionalProperties: true,
|
|
341
|
+
};
|
|
342
|
+
const startCustomActionResponseSchema = {
|
|
343
|
+
anyOf: [
|
|
344
|
+
errorResponseSchema,
|
|
345
|
+
{
|
|
346
|
+
type: 'object',
|
|
347
|
+
required: ['actionId', 'resourceId', 'recordId', 'redirectUrl'],
|
|
348
|
+
properties: {
|
|
349
|
+
actionId: actionIdentifierSchema,
|
|
350
|
+
resourceId: { type: 'string' },
|
|
351
|
+
recordId: recordIdentifierSchema,
|
|
352
|
+
redirectUrl: { type: 'string' },
|
|
353
|
+
},
|
|
354
|
+
additionalProperties: true,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
type: 'object',
|
|
358
|
+
required: ['actionId', 'resourceId', 'recordId', 'ok'],
|
|
359
|
+
properties: {
|
|
360
|
+
actionId: actionIdentifierSchema,
|
|
361
|
+
resourceId: { type: 'string' },
|
|
362
|
+
recordId: recordIdentifierSchema,
|
|
363
|
+
ok: { const: true },
|
|
364
|
+
},
|
|
365
|
+
additionalProperties: true,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
};
|
|
369
|
+
const startCustomBulkActionRequestSchema = {
|
|
370
|
+
type: 'object',
|
|
371
|
+
required: ['resourceId', 'actionId', 'recordIds'],
|
|
372
|
+
properties: {
|
|
373
|
+
resourceId: { type: 'string' },
|
|
374
|
+
actionId: actionIdentifierSchema,
|
|
375
|
+
recordIds: {
|
|
376
|
+
type: 'array',
|
|
377
|
+
items: recordIdentifierSchema,
|
|
378
|
+
},
|
|
379
|
+
extra: genericObjectSchema,
|
|
380
|
+
},
|
|
381
|
+
additionalProperties: true,
|
|
382
|
+
};
|
|
383
|
+
const startCustomBulkActionResponseSchema = createErrorOrSuccessSchema({
|
|
384
|
+
type: 'object',
|
|
385
|
+
required: ['actionId', 'resourceId', 'recordIds', 'ok'],
|
|
386
|
+
properties: {
|
|
387
|
+
actionId: actionIdentifierSchema,
|
|
388
|
+
resourceId: { type: 'string' },
|
|
389
|
+
recordIds: {
|
|
390
|
+
type: 'array',
|
|
391
|
+
items: recordIdentifierSchema,
|
|
392
|
+
},
|
|
393
|
+
ok: { const: true },
|
|
394
|
+
},
|
|
395
|
+
additionalProperties: true,
|
|
396
|
+
});
|
|
397
|
+
const validateColumnsRequestSchema = {
|
|
398
|
+
type: 'object',
|
|
399
|
+
required: ['resourceId', 'editableColumns', 'record'],
|
|
400
|
+
properties: {
|
|
401
|
+
resourceId: { type: 'string' },
|
|
402
|
+
editableColumns: {
|
|
403
|
+
type: 'array',
|
|
404
|
+
items: {
|
|
405
|
+
type: 'object',
|
|
406
|
+
required: ['name'],
|
|
407
|
+
properties: {
|
|
408
|
+
name: { type: 'string' },
|
|
409
|
+
value: {},
|
|
410
|
+
},
|
|
411
|
+
additionalProperties: true,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
record: genericObjectSchema,
|
|
415
|
+
},
|
|
416
|
+
additionalProperties: true,
|
|
417
|
+
};
|
|
418
|
+
const validateColumnsResponseSchema = createErrorOrSuccessSchema({
|
|
419
|
+
type: 'object',
|
|
420
|
+
required: ['validationResults'],
|
|
421
|
+
properties: {
|
|
422
|
+
validationResults: {
|
|
423
|
+
type: 'object',
|
|
424
|
+
additionalProperties: validationResultSchema,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
additionalProperties: true,
|
|
428
|
+
});
|
|
29
429
|
export async function interpretResource(adminUser, resource, meta, source, adminforth) {
|
|
30
430
|
afLogger.trace(`🪲Interpreting resource, ${resource.resourceId}, ${source}, 'adminUser', ${adminUser}`);
|
|
31
431
|
const allowedActions = {};
|
|
@@ -182,7 +582,7 @@ export default class AdminForthRestAPI {
|
|
|
182
582
|
handler: async ({ tr }) => {
|
|
183
583
|
const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
|
|
184
584
|
return {
|
|
185
|
-
loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
|
|
585
|
+
loginPromptHTML: loginPromptHTML ? await tr(loginPromptHTML, 'system.loginPromptHTML') : null,
|
|
186
586
|
};
|
|
187
587
|
}
|
|
188
588
|
});
|
|
@@ -222,7 +622,7 @@ export default class AdminForthRestAPI {
|
|
|
222
622
|
server.endpoint({
|
|
223
623
|
method: 'GET',
|
|
224
624
|
path: '/get_base_config',
|
|
225
|
-
handler: async ({
|
|
625
|
+
handler: async ({ adminUser, cookies, tr, response }) => {
|
|
226
626
|
var _a, _b, _c, _d, _e;
|
|
227
627
|
let username = '';
|
|
228
628
|
let userFullName = '';
|
|
@@ -385,14 +785,17 @@ export default class AdminForthRestAPI {
|
|
|
385
785
|
server.endpoint({
|
|
386
786
|
method: 'GET',
|
|
387
787
|
path: '/get_menu_badges',
|
|
788
|
+
description: 'Computes the current menu badge values for the authenticated admin user. Static badges are returned directly, and dynamic badge callbacks are resolved for all configured menu items, including nested items.',
|
|
789
|
+
response_schema: getMenuBadgesResponseSchema,
|
|
388
790
|
handler: async ({ adminUser }) => {
|
|
389
791
|
const badges = {};
|
|
390
792
|
const badgeFunctions = [];
|
|
793
|
+
const adminforth = this.adminforth;
|
|
391
794
|
function processMenuItem(menuItem) {
|
|
392
795
|
if (menuItem.badge) {
|
|
393
796
|
if (typeof menuItem.badge === 'function') {
|
|
394
797
|
badgeFunctions.push(async () => {
|
|
395
|
-
badges[menuItem.itemId] = await menuItem.badge(adminUser);
|
|
798
|
+
badges[menuItem.itemId] = await menuItem.badge(adminUser, adminforth);
|
|
396
799
|
});
|
|
397
800
|
}
|
|
398
801
|
else {
|
|
@@ -420,8 +823,11 @@ export default class AdminForthRestAPI {
|
|
|
420
823
|
server.endpoint({
|
|
421
824
|
method: 'POST',
|
|
422
825
|
path: '/get_resource',
|
|
826
|
+
description: 'Returns the definition of a single resource. The response includes translated labels, column metadata, allowed actions, visible bulk actions, frontend action metadata, and resource options after permission checks and removal of backend-only internals.',
|
|
827
|
+
request_schema: getResourceRequestSchema,
|
|
828
|
+
response_schema: getResourceResponseSchema,
|
|
423
829
|
handler: async ({ body, adminUser, tr }) => {
|
|
424
|
-
var _a;
|
|
830
|
+
var _a, _b;
|
|
425
831
|
const { resourceId } = body;
|
|
426
832
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
427
833
|
return { error: 'Database discovery not started' };
|
|
@@ -476,8 +882,8 @@ export default class AdminForthRestAPI {
|
|
|
476
882
|
const col = JSON.parse(JSON.stringify(inCol));
|
|
477
883
|
let validation = null;
|
|
478
884
|
if (col.validation) {
|
|
479
|
-
validation = await Promise.all(col.validation.map(async (val) => {
|
|
480
|
-
return Object.assign(Object.assign({}, val), { message: await tr(val.message, `resource.${resource.resourceId}`) });
|
|
885
|
+
validation = await Promise.all(col.validation.map(async (val, index) => {
|
|
886
|
+
return Object.assign(Object.assign({}, val), { validator: inCol.validation[index].validator ? true : false, message: await tr(val.message, `resource.${resource.resourceId}`) });
|
|
481
887
|
}));
|
|
482
888
|
}
|
|
483
889
|
let enumItems = undefined;
|
|
@@ -507,14 +913,14 @@ export default class AdminForthRestAPI {
|
|
|
507
913
|
col.foreignResource.unsetLabel = await tr(col.foreignResource.unsetLabel, `resource.${resource.resourceId}.foreignResource.unsetLabel`);
|
|
508
914
|
}
|
|
509
915
|
if (inCol.suggestOnCreate && typeof inCol.suggestOnCreate === 'function') {
|
|
510
|
-
col.suggestOnCreate = await inCol.suggestOnCreate(adminUser);
|
|
916
|
+
col.suggestOnCreate = await inCol.suggestOnCreate({ adminUser });
|
|
511
917
|
}
|
|
512
918
|
return Object.assign(Object.assign({}, col), { showIn,
|
|
513
919
|
validation, label: translated[`resCol${i}`], enum: enumItems });
|
|
514
920
|
})), options: Object.assign(Object.assign({}, resource.options), { fieldGroups: (_a = resource.options.fieldGroups) === null || _a === void 0 ? void 0 : _a.map((group, i) => {
|
|
515
921
|
var _a;
|
|
516
922
|
return (Object.assign(Object.assign({}, group), { noTitle: (_a = group.noTitle) !== null && _a !== void 0 ? _a : false, groupName: translated[`fieldGroup${i}`] || group.groupName }));
|
|
517
|
-
}), bulkActions: allowedBulkActions.map((action, i) => (Object.assign(Object.assign({}, action), { label: action.label ? translated[`bulkAction${i}`] : action.label, confirm: action.confirm ? translated[`bulkActionConfirm${i}`] : action.confirm }))), allowedActions }) });
|
|
923
|
+
}), bulkActions: allowedBulkActions.map((action, i) => (Object.assign(Object.assign({}, action), { label: action.label ? translated[`bulkAction${i}`] : action.label, confirm: action.confirm ? translated[`bulkActionConfirm${i}`] : action.confirm }))), actions: (_b = resource.options.actions) === null || _b === void 0 ? void 0 : _b.map((action) => (Object.assign(Object.assign({}, action), { hasBulkHandler: !!action.bulkHandler, bulkHandler: undefined }))), allowedActions }) });
|
|
518
924
|
delete toReturn.hooks;
|
|
519
925
|
delete toReturn.plugins;
|
|
520
926
|
return {
|
|
@@ -525,6 +931,9 @@ export default class AdminForthRestAPI {
|
|
|
525
931
|
server.endpoint({
|
|
526
932
|
method: 'POST',
|
|
527
933
|
path: '/get_resource_data',
|
|
934
|
+
description: 'Loads resource rows for list, show, or edit views. The endpoint validates access, applies request hooks, filters, sorting, pagination, record labels, and row click URLs, then returns the final dataset with resource options.',
|
|
935
|
+
request_schema: getResourceDataRequestSchema,
|
|
936
|
+
response_schema: getResourceDataResponseSchema,
|
|
528
937
|
handler: async ({ body, adminUser, headers, query, cookies, requestUrl, abortSignal }) => {
|
|
529
938
|
var _a, _b, _c, _d, _e, _f;
|
|
530
939
|
const { resourceId, source } = body;
|
|
@@ -792,6 +1201,9 @@ export default class AdminForthRestAPI {
|
|
|
792
1201
|
server.endpoint({
|
|
793
1202
|
method: 'POST',
|
|
794
1203
|
path: '/get_resource_foreign_data',
|
|
1204
|
+
description: 'Loads dropdown options for a foreign-key column. It resolves the referenced resource or polymorphic resources, applies optional search text, hook-injected filters, pagination, and per-record labels, then returns sanitized option items.',
|
|
1205
|
+
request_schema: getResourceForeignDataRequestSchema,
|
|
1206
|
+
response_schema: getResourceForeignDataResponseSchema,
|
|
795
1207
|
handler: async ({ body, adminUser, headers, query, cookies, requestUrl }) => {
|
|
796
1208
|
const { resourceId, column, search } = body;
|
|
797
1209
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
@@ -952,6 +1364,9 @@ export default class AdminForthRestAPI {
|
|
|
952
1364
|
server.endpoint({
|
|
953
1365
|
method: 'POST',
|
|
954
1366
|
path: '/get_min_max_for_columns',
|
|
1367
|
+
description: 'Returns min and max values for resource columns that explicitly opt in to min/max queries. This is used to build range-based filter controls without exposing columns that do not allow the query.',
|
|
1368
|
+
request_schema: getMinMaxForColumnsRequestSchema,
|
|
1369
|
+
response_schema: getMinMaxForColumnsResponseSchema,
|
|
955
1370
|
handler: async ({ body }) => {
|
|
956
1371
|
const { resourceId } = body;
|
|
957
1372
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
@@ -981,6 +1396,9 @@ export default class AdminForthRestAPI {
|
|
|
981
1396
|
server.endpoint({
|
|
982
1397
|
method: 'POST',
|
|
983
1398
|
path: '/create_record',
|
|
1399
|
+
description: 'Creates a new record in the specified resource. The endpoint validates create permissions, required fields, hidden or backend-only field rules, polymorphic foreign keys, and resource hooks before persisting and returning the created primary key.',
|
|
1400
|
+
request_schema: createRecordRequestSchema,
|
|
1401
|
+
response_schema: createRecordResponseSchema,
|
|
984
1402
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
985
1403
|
var _a, _b, _c, _d;
|
|
986
1404
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
@@ -1110,6 +1528,9 @@ export default class AdminForthRestAPI {
|
|
|
1110
1528
|
server.endpoint({
|
|
1111
1529
|
method: 'POST',
|
|
1112
1530
|
path: '/update_record',
|
|
1531
|
+
description: 'Updates an existing record by primary key. The endpoint validates edit permissions, current record existence, hidden, backend-only, and read-only field rules, polymorphic foreign keys, and resource hooks before saving changes.',
|
|
1532
|
+
request_schema: updateRecordRequestSchema,
|
|
1533
|
+
response_schema: updateRecordResponseSchema,
|
|
1113
1534
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1114
1535
|
var _a, _b;
|
|
1115
1536
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
@@ -1228,6 +1649,9 @@ export default class AdminForthRestAPI {
|
|
|
1228
1649
|
server.endpoint({
|
|
1229
1650
|
method: 'POST',
|
|
1230
1651
|
path: '/delete_record',
|
|
1652
|
+
description: 'Deletes an existing record by primary key. The endpoint validates delete permissions, loads the current record, executes configured cascade child deletion, and then removes the record.',
|
|
1653
|
+
request_schema: deleteRecordRequestSchema,
|
|
1654
|
+
response_schema: deleteRecordResponseSchema,
|
|
1231
1655
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1232
1656
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
1233
1657
|
if (!resource) {
|
|
@@ -1288,6 +1712,9 @@ export default class AdminForthRestAPI {
|
|
|
1288
1712
|
server.endpoint({
|
|
1289
1713
|
method: 'POST',
|
|
1290
1714
|
path: '/start_custom_action',
|
|
1715
|
+
description: 'Executes a custom resource action for a single record. The endpoint validates the resource, action existence, and action permissions, then either returns a redirect URL or executes the action handler and returns its result together with action context.',
|
|
1716
|
+
request_schema: startCustomActionRequestSchema,
|
|
1717
|
+
response_schema: startCustomActionResponseSchema,
|
|
1291
1718
|
handler: async ({ body, adminUser, tr, cookies, response, headers }) => {
|
|
1292
1719
|
const { resourceId, actionId, recordId, extra } = body;
|
|
1293
1720
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
@@ -1319,6 +1746,85 @@ export default class AdminForthRestAPI {
|
|
|
1319
1746
|
resourceId }, actionResponse);
|
|
1320
1747
|
}
|
|
1321
1748
|
});
|
|
1749
|
+
server.endpoint({
|
|
1750
|
+
method: 'POST',
|
|
1751
|
+
path: '/start_custom_bulk_action',
|
|
1752
|
+
description: 'Executes a custom resource action in bulk mode for multiple records. The endpoint validates the resource, action existence, bulk handler availability, and permissions, then runs the bulk handler and returns its result together with action context.',
|
|
1753
|
+
request_schema: startCustomBulkActionRequestSchema,
|
|
1754
|
+
response_schema: startCustomBulkActionResponseSchema,
|
|
1755
|
+
handler: async ({ body, adminUser, tr, response, cookies, headers }) => {
|
|
1756
|
+
const { resourceId, actionId, recordIds, extra } = body;
|
|
1757
|
+
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
1758
|
+
if (!resource) {
|
|
1759
|
+
return { error: await tr(`Resource {resourceId} not found`, 'errors', { resourceId }) };
|
|
1760
|
+
}
|
|
1761
|
+
const { allowedActions } = await interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.CustomActionRequest, this.adminforth);
|
|
1762
|
+
const action = resource.options.actions.find((act) => act.id == actionId);
|
|
1763
|
+
if (!action) {
|
|
1764
|
+
return { error: await tr(`Action {actionId} not found`, 'errors', { actionId }) };
|
|
1765
|
+
}
|
|
1766
|
+
if (!action.bulkHandler) {
|
|
1767
|
+
return { error: await tr(`Action "{actionId}" has no bulkHandler`, 'errors', { actionId }) };
|
|
1768
|
+
}
|
|
1769
|
+
if (action.allowed) {
|
|
1770
|
+
const execAllowed = await action.allowed({ adminUser, standardAllowedActions: allowedActions });
|
|
1771
|
+
if (!execAllowed) {
|
|
1772
|
+
return { error: await tr(`Action "{actionId}" not allowed`, 'errors', { actionId: action.name }) };
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
const result = await action.bulkHandler({
|
|
1776
|
+
recordIds,
|
|
1777
|
+
adminUser,
|
|
1778
|
+
resource,
|
|
1779
|
+
tr,
|
|
1780
|
+
adminforth: this.adminforth,
|
|
1781
|
+
response,
|
|
1782
|
+
extra: Object.assign(Object.assign({}, extra), { cookies, headers }),
|
|
1783
|
+
});
|
|
1784
|
+
return Object.assign({ actionId, recordIds, resourceId }, result);
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
server.endpoint({
|
|
1788
|
+
method: 'POST',
|
|
1789
|
+
path: '/validate_columns',
|
|
1790
|
+
description: 'Runs server-side custom validators for editable columns in a resource form. Only validators defined on submitted columns are executed, and the response maps each invalid column to its validation result.',
|
|
1791
|
+
request_schema: validateColumnsRequestSchema,
|
|
1792
|
+
response_schema: validateColumnsResponseSchema,
|
|
1793
|
+
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1794
|
+
const { resourceId, editableColumns, record } = body;
|
|
1795
|
+
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
1796
|
+
if (!resource) {
|
|
1797
|
+
return { error: `Resource '${resourceId}' not found` };
|
|
1798
|
+
}
|
|
1799
|
+
const validationResults = {};
|
|
1800
|
+
const customColumnValidatorsFunctions = [];
|
|
1801
|
+
for (const col of editableColumns) {
|
|
1802
|
+
const columnConfig = resource.columns.find((c) => c.name === col.name);
|
|
1803
|
+
if (columnConfig && columnConfig.validation) {
|
|
1804
|
+
customColumnValidatorsFunctions.push(async () => {
|
|
1805
|
+
for (const val of columnConfig.validation) {
|
|
1806
|
+
if (val.validator) {
|
|
1807
|
+
const result = await val.validator(col.value, record, this.adminforth);
|
|
1808
|
+
if (typeof result === 'object' && result.isValid === false) {
|
|
1809
|
+
validationResults[col.name] = {
|
|
1810
|
+
isValid: result.isValid,
|
|
1811
|
+
message: result.message,
|
|
1812
|
+
};
|
|
1813
|
+
break;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (customColumnValidatorsFunctions.length) {
|
|
1821
|
+
await Promise.all(customColumnValidatorsFunctions.map((fn) => fn()));
|
|
1822
|
+
}
|
|
1823
|
+
return {
|
|
1824
|
+
validationResults
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1322
1828
|
// setup endpoints for all plugins
|
|
1323
1829
|
this.adminforth.activatedPlugins.forEach((plugin) => {
|
|
1324
1830
|
plugin.setupEndpoints(server);
|