adminforth 2.34.0 → 2.35.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/commands/createApp/templates/api.ts.hbs +25 -16
- package/commands/createApp/templates/package.json.hbs +2 -1
- package/dist/basePlugin.js +1 -1
- package/dist/basePlugin.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +495 -4
- package/dist/modules/restApi.js.map +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 +18 -7
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +137 -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 +92 -0
- package/dist/servers/openapi.js.map +1 -0
- package/dist/servers/openapiDocument.d.ts +12 -0
- package/dist/servers/openapiDocument.d.ts.map +1 -0
- package/dist/servers/openapiDocument.js +313 -0
- package/dist/servers/openapiDocument.js.map +1 -0
- package/dist/spa/package.json +1 -0
- package/dist/spa/pnpm-lock.yaml +346 -310
- package/dist/spa/pnpm-workspace.yaml +4 -0
- package/dist/spa/src/App.vue +1 -0
- package/dist/spa/src/stores/core.ts +15 -1
- package/dist/spa/src/types/Back.ts +109 -23
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +10 -0
- package/dist/types/Back.d.ts +113 -18
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/adapters/CompletionAdapter.d.ts +8 -1
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -1
- package/package.json +5 -2
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,465 @@ 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
|
+
title: 'AdminForthErrorResponse',
|
|
38
|
+
description: 'Standard error response returned by AdminForth endpoints.',
|
|
39
|
+
type: 'object',
|
|
40
|
+
required: ['error'],
|
|
41
|
+
properties: {
|
|
42
|
+
error: { type: 'string' },
|
|
43
|
+
},
|
|
44
|
+
additionalProperties: true,
|
|
45
|
+
};
|
|
46
|
+
const recordIdentifierSchema = {
|
|
47
|
+
title: 'AdminForthRecordIdentifier',
|
|
48
|
+
description: 'Record identifier accepted by AdminForth. Depending on the resource it can be a string or a number.',
|
|
49
|
+
anyOf: [
|
|
50
|
+
{ type: 'string' },
|
|
51
|
+
{ type: 'number' },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
const actionIdentifierSchema = {
|
|
55
|
+
title: 'AdminForthActionIdentifier',
|
|
56
|
+
description: 'Action identifier accepted by AdminForth. Depending on configuration it can be a string or a number.',
|
|
57
|
+
anyOf: [
|
|
58
|
+
{ type: 'string' },
|
|
59
|
+
{ type: 'number' },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
const namedColumnSchema = {
|
|
63
|
+
title: 'AdminForthNamedColumn',
|
|
64
|
+
type: 'object',
|
|
65
|
+
required: ['name'],
|
|
66
|
+
properties: {
|
|
67
|
+
name: { type: 'string' },
|
|
68
|
+
},
|
|
69
|
+
additionalProperties: true,
|
|
70
|
+
};
|
|
71
|
+
const validationResultSchema = {
|
|
72
|
+
title: 'AdminForthValidationResult',
|
|
73
|
+
type: 'object',
|
|
74
|
+
required: ['isValid'],
|
|
75
|
+
properties: {
|
|
76
|
+
isValid: { type: 'boolean' },
|
|
77
|
+
message: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: true,
|
|
80
|
+
};
|
|
81
|
+
const filterConditionExample = {
|
|
82
|
+
field: 'status',
|
|
83
|
+
operator: AdminForthFilterOperators.EQ,
|
|
84
|
+
value: 'active',
|
|
85
|
+
};
|
|
86
|
+
const filterGroupExample = {
|
|
87
|
+
operator: AdminForthFilterOperators.AND,
|
|
88
|
+
subFilters: [filterConditionExample],
|
|
89
|
+
};
|
|
90
|
+
const sortItemExample = {
|
|
91
|
+
field: 'createdAt',
|
|
92
|
+
direction: AdminForthSortDirections.desc,
|
|
93
|
+
};
|
|
94
|
+
const filterConditionSchema = {
|
|
95
|
+
title: 'AdminForthFilterCondition',
|
|
96
|
+
description: 'Single field comparison used in AdminForth filtering.',
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
field: { type: 'string' },
|
|
100
|
+
operator: { type: 'string', enum: SIMPLE_FILTER_OPERATORS },
|
|
101
|
+
value: {},
|
|
102
|
+
rightField: { type: 'string' },
|
|
103
|
+
insecureRawSQL: { type: 'string' },
|
|
104
|
+
insecureRawNoSQL: {},
|
|
105
|
+
},
|
|
106
|
+
additionalProperties: true,
|
|
107
|
+
examples: [filterConditionExample],
|
|
108
|
+
};
|
|
109
|
+
const filterGroupSchema = {
|
|
110
|
+
title: 'AdminForthFilterGroup',
|
|
111
|
+
description: 'Nested boolean filter group. Use this for AND or OR combinations of filter nodes.',
|
|
112
|
+
type: 'object',
|
|
113
|
+
required: ['operator', 'subFilters'],
|
|
114
|
+
properties: {
|
|
115
|
+
operator: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
enum: [AdminForthFilterOperators.AND, AdminForthFilterOperators.OR],
|
|
118
|
+
},
|
|
119
|
+
subFilters: {
|
|
120
|
+
type: 'array',
|
|
121
|
+
items: { $ref: '#/$defs/filterNode' },
|
|
122
|
+
description: 'Nested filters evaluated with the selected operator.',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
additionalProperties: true,
|
|
126
|
+
examples: [filterGroupExample],
|
|
127
|
+
};
|
|
128
|
+
const sortItemSchema = {
|
|
129
|
+
title: 'AdminForthSortItem',
|
|
130
|
+
description: 'Single sort instruction applied in order with the rest of the list.',
|
|
131
|
+
type: 'object',
|
|
132
|
+
required: ['field', 'direction'],
|
|
133
|
+
properties: {
|
|
134
|
+
field: { type: 'string' },
|
|
135
|
+
direction: { type: 'string', enum: Object.values(AdminForthSortDirections) },
|
|
136
|
+
},
|
|
137
|
+
additionalProperties: true,
|
|
138
|
+
examples: [sortItemExample],
|
|
139
|
+
};
|
|
140
|
+
const commonFilterSchemaDefs = {
|
|
141
|
+
singleFilter: filterConditionSchema,
|
|
142
|
+
filterGroup: filterGroupSchema,
|
|
143
|
+
filterNode: {
|
|
144
|
+
title: 'AdminForthFilterNode',
|
|
145
|
+
description: 'Either a single filter condition or a nested filter group.',
|
|
146
|
+
anyOf: [
|
|
147
|
+
{ $ref: '#/$defs/singleFilter' },
|
|
148
|
+
{ $ref: '#/$defs/filterGroup' },
|
|
149
|
+
],
|
|
150
|
+
examples: [filterConditionExample, filterGroupExample],
|
|
151
|
+
},
|
|
152
|
+
sortItem: sortItemSchema,
|
|
153
|
+
};
|
|
154
|
+
const commonSortSchema = {
|
|
155
|
+
title: 'AdminForthSortList',
|
|
156
|
+
description: 'Ordered list of sort instructions.',
|
|
157
|
+
type: 'array',
|
|
158
|
+
items: { $ref: '#/$defs/sortItem' },
|
|
159
|
+
examples: [[sortItemExample]],
|
|
160
|
+
};
|
|
161
|
+
const commonFiltersSchema = {
|
|
162
|
+
title: 'AdminForthFilterInput',
|
|
163
|
+
description: 'Runtime accepts either a single filter node or an array of filter nodes. The OpenAPI document normalizes this to the array form for readability.',
|
|
164
|
+
oneOf: [
|
|
165
|
+
{
|
|
166
|
+
type: 'array',
|
|
167
|
+
items: { $ref: '#/$defs/filterNode' },
|
|
168
|
+
},
|
|
169
|
+
{ $ref: '#/$defs/filterNode' },
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
function createErrorOrSuccessSchema(successSchema) {
|
|
173
|
+
return {
|
|
174
|
+
anyOf: [
|
|
175
|
+
errorResponseSchema,
|
|
176
|
+
successSchema,
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const getResourceDataRequestSchema = {
|
|
181
|
+
type: 'object',
|
|
182
|
+
$defs: commonFilterSchemaDefs,
|
|
183
|
+
required: ['resourceId', 'source', 'limit', 'offset', 'filters', 'sort'],
|
|
184
|
+
properties: {
|
|
185
|
+
resourceId: { type: 'string' },
|
|
186
|
+
source: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
enum: ['show', 'list', 'edit'],
|
|
189
|
+
description: 'Target UI context. Show and edit requests should use direct field filters that identify a single record.',
|
|
190
|
+
},
|
|
191
|
+
limit: {
|
|
192
|
+
type: 'integer',
|
|
193
|
+
description: 'Maximum number of rows to return for the current page.',
|
|
194
|
+
},
|
|
195
|
+
offset: {
|
|
196
|
+
type: 'integer',
|
|
197
|
+
description: 'Zero-based row offset used for pagination.',
|
|
198
|
+
},
|
|
199
|
+
sort: commonSortSchema,
|
|
200
|
+
filters: commonFiltersSchema,
|
|
201
|
+
},
|
|
202
|
+
additionalProperties: true,
|
|
203
|
+
allOf: [
|
|
204
|
+
{
|
|
205
|
+
if: {
|
|
206
|
+
properties: {
|
|
207
|
+
source: { enum: ['show', 'edit'] },
|
|
208
|
+
},
|
|
209
|
+
required: ['source'],
|
|
210
|
+
},
|
|
211
|
+
then: {
|
|
212
|
+
properties: {
|
|
213
|
+
filters: {
|
|
214
|
+
type: 'array',
|
|
215
|
+
items: {
|
|
216
|
+
allOf: [
|
|
217
|
+
{ $ref: '#/$defs/singleFilter' },
|
|
218
|
+
{
|
|
219
|
+
type: 'object',
|
|
220
|
+
required: ['field'],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
const getResourceDataResponseSchema = createErrorOrSuccessSchema({
|
|
231
|
+
type: 'object',
|
|
232
|
+
required: ['data'],
|
|
233
|
+
properties: {
|
|
234
|
+
data: {
|
|
235
|
+
type: 'array',
|
|
236
|
+
items: genericObjectSchema,
|
|
237
|
+
},
|
|
238
|
+
total: { type: 'number' },
|
|
239
|
+
options: genericObjectSchema,
|
|
240
|
+
},
|
|
241
|
+
additionalProperties: true,
|
|
242
|
+
});
|
|
243
|
+
const getMenuBadgesResponseSchema = {
|
|
244
|
+
type: 'object',
|
|
245
|
+
additionalProperties: {
|
|
246
|
+
anyOf: [
|
|
247
|
+
{ type: 'string' },
|
|
248
|
+
{ type: 'number' },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
const getResourceRequestSchema = {
|
|
253
|
+
type: 'object',
|
|
254
|
+
required: ['resourceId'],
|
|
255
|
+
properties: {
|
|
256
|
+
resourceId: { type: 'string' },
|
|
257
|
+
},
|
|
258
|
+
additionalProperties: true,
|
|
259
|
+
};
|
|
260
|
+
const getResourceResponseSchema = createErrorOrSuccessSchema({
|
|
261
|
+
type: 'object',
|
|
262
|
+
required: ['resource'],
|
|
263
|
+
properties: {
|
|
264
|
+
resource: genericObjectSchema,
|
|
265
|
+
},
|
|
266
|
+
additionalProperties: true,
|
|
267
|
+
});
|
|
268
|
+
const getResourceForeignDataRequestSchema = {
|
|
269
|
+
type: 'object',
|
|
270
|
+
$defs: commonFilterSchemaDefs,
|
|
271
|
+
required: ['resourceId', 'column', 'limit', 'offset'],
|
|
272
|
+
properties: {
|
|
273
|
+
resourceId: { type: 'string' },
|
|
274
|
+
column: { type: 'string' },
|
|
275
|
+
limit: {
|
|
276
|
+
type: 'integer',
|
|
277
|
+
description: 'Maximum number of dropdown options to return.',
|
|
278
|
+
},
|
|
279
|
+
offset: {
|
|
280
|
+
type: 'integer',
|
|
281
|
+
description: 'Zero-based offset used to fetch the next option page.',
|
|
282
|
+
},
|
|
283
|
+
search: { type: 'string' },
|
|
284
|
+
filters: commonFiltersSchema,
|
|
285
|
+
sort: commonSortSchema,
|
|
286
|
+
},
|
|
287
|
+
additionalProperties: true,
|
|
288
|
+
};
|
|
289
|
+
const getResourceForeignDataResponseSchema = createErrorOrSuccessSchema({
|
|
290
|
+
type: 'object',
|
|
291
|
+
required: ['items'],
|
|
292
|
+
properties: {
|
|
293
|
+
items: {
|
|
294
|
+
type: 'array',
|
|
295
|
+
items: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
required: ['value', 'label'],
|
|
298
|
+
properties: {
|
|
299
|
+
value: {},
|
|
300
|
+
label: { type: 'string' },
|
|
301
|
+
},
|
|
302
|
+
additionalProperties: true,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
additionalProperties: true,
|
|
307
|
+
});
|
|
308
|
+
const getMinMaxForColumnsRequestSchema = {
|
|
309
|
+
type: 'object',
|
|
310
|
+
required: ['resourceId'],
|
|
311
|
+
properties: {
|
|
312
|
+
resourceId: { type: 'string' },
|
|
313
|
+
},
|
|
314
|
+
additionalProperties: true,
|
|
315
|
+
};
|
|
316
|
+
const getMinMaxForColumnsResponseSchema = createErrorOrSuccessSchema({
|
|
317
|
+
type: 'object',
|
|
318
|
+
additionalProperties: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
required: ['min', 'max'],
|
|
321
|
+
properties: {
|
|
322
|
+
min: {},
|
|
323
|
+
max: {},
|
|
324
|
+
},
|
|
325
|
+
additionalProperties: true,
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
const createRecordRequestSchema = {
|
|
329
|
+
type: 'object',
|
|
330
|
+
required: ['resourceId', 'record', 'requiredColumnsToSkip'],
|
|
331
|
+
properties: {
|
|
332
|
+
resourceId: { type: 'string' },
|
|
333
|
+
record: genericObjectSchema,
|
|
334
|
+
requiredColumnsToSkip: {
|
|
335
|
+
type: 'array',
|
|
336
|
+
items: namedColumnSchema,
|
|
337
|
+
},
|
|
338
|
+
meta: genericObjectSchema,
|
|
339
|
+
},
|
|
340
|
+
additionalProperties: true,
|
|
341
|
+
};
|
|
342
|
+
const createRecordResponseSchema = createErrorOrSuccessSchema({
|
|
343
|
+
type: 'object',
|
|
344
|
+
required: ['ok', 'newRecordId', 'redirectToRecordId'],
|
|
345
|
+
properties: {
|
|
346
|
+
ok: { const: true },
|
|
347
|
+
newRecordId: recordIdentifierSchema,
|
|
348
|
+
redirectToRecordId: recordIdentifierSchema,
|
|
349
|
+
},
|
|
350
|
+
additionalProperties: true,
|
|
351
|
+
});
|
|
352
|
+
const updateRecordRequestSchema = {
|
|
353
|
+
type: 'object',
|
|
354
|
+
required: ['resourceId', 'recordId', 'record'],
|
|
355
|
+
properties: {
|
|
356
|
+
resourceId: { type: 'string' },
|
|
357
|
+
recordId: recordIdentifierSchema,
|
|
358
|
+
record: genericObjectSchema,
|
|
359
|
+
meta: genericObjectSchema,
|
|
360
|
+
},
|
|
361
|
+
additionalProperties: true,
|
|
362
|
+
};
|
|
363
|
+
const updateRecordResponseSchema = createErrorOrSuccessSchema({
|
|
364
|
+
type: 'object',
|
|
365
|
+
required: ['ok'],
|
|
366
|
+
properties: {
|
|
367
|
+
ok: { const: true },
|
|
368
|
+
recordId: recordIdentifierSchema,
|
|
369
|
+
},
|
|
370
|
+
additionalProperties: true,
|
|
371
|
+
});
|
|
372
|
+
const deleteRecordRequestSchema = {
|
|
373
|
+
type: 'object',
|
|
374
|
+
required: ['resourceId', 'primaryKey'],
|
|
375
|
+
properties: {
|
|
376
|
+
resourceId: { type: 'string' },
|
|
377
|
+
primaryKey: recordIdentifierSchema,
|
|
378
|
+
},
|
|
379
|
+
additionalProperties: true,
|
|
380
|
+
};
|
|
381
|
+
const deleteRecordResponseSchema = createErrorOrSuccessSchema({
|
|
382
|
+
type: 'object',
|
|
383
|
+
required: ['ok', 'recordId'],
|
|
384
|
+
properties: {
|
|
385
|
+
ok: { const: true },
|
|
386
|
+
recordId: recordIdentifierSchema,
|
|
387
|
+
},
|
|
388
|
+
additionalProperties: true,
|
|
389
|
+
});
|
|
390
|
+
const startCustomActionRequestSchema = {
|
|
391
|
+
type: 'object',
|
|
392
|
+
required: ['resourceId', 'actionId', 'recordId'],
|
|
393
|
+
properties: {
|
|
394
|
+
resourceId: { type: 'string' },
|
|
395
|
+
actionId: actionIdentifierSchema,
|
|
396
|
+
recordId: recordIdentifierSchema,
|
|
397
|
+
extra: genericObjectSchema,
|
|
398
|
+
},
|
|
399
|
+
additionalProperties: true,
|
|
400
|
+
};
|
|
401
|
+
const startCustomActionResponseSchema = {
|
|
402
|
+
anyOf: [
|
|
403
|
+
errorResponseSchema,
|
|
404
|
+
{
|
|
405
|
+
type: 'object',
|
|
406
|
+
required: ['actionId', 'resourceId', 'recordId', 'redirectUrl'],
|
|
407
|
+
properties: {
|
|
408
|
+
actionId: actionIdentifierSchema,
|
|
409
|
+
resourceId: { type: 'string' },
|
|
410
|
+
recordId: recordIdentifierSchema,
|
|
411
|
+
redirectUrl: { type: 'string' },
|
|
412
|
+
},
|
|
413
|
+
additionalProperties: true,
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
type: 'object',
|
|
417
|
+
required: ['actionId', 'resourceId', 'recordId', 'ok'],
|
|
418
|
+
properties: {
|
|
419
|
+
actionId: actionIdentifierSchema,
|
|
420
|
+
resourceId: { type: 'string' },
|
|
421
|
+
recordId: recordIdentifierSchema,
|
|
422
|
+
ok: { const: true },
|
|
423
|
+
},
|
|
424
|
+
additionalProperties: true,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
const startCustomBulkActionRequestSchema = {
|
|
429
|
+
type: 'object',
|
|
430
|
+
required: ['resourceId', 'actionId', 'recordIds'],
|
|
431
|
+
properties: {
|
|
432
|
+
resourceId: { type: 'string' },
|
|
433
|
+
actionId: actionIdentifierSchema,
|
|
434
|
+
recordIds: {
|
|
435
|
+
type: 'array',
|
|
436
|
+
items: recordIdentifierSchema,
|
|
437
|
+
},
|
|
438
|
+
extra: genericObjectSchema,
|
|
439
|
+
},
|
|
440
|
+
additionalProperties: true,
|
|
441
|
+
};
|
|
442
|
+
const startCustomBulkActionResponseSchema = createErrorOrSuccessSchema({
|
|
443
|
+
type: 'object',
|
|
444
|
+
required: ['actionId', 'resourceId', 'recordIds', 'ok'],
|
|
445
|
+
properties: {
|
|
446
|
+
actionId: actionIdentifierSchema,
|
|
447
|
+
resourceId: { type: 'string' },
|
|
448
|
+
recordIds: {
|
|
449
|
+
type: 'array',
|
|
450
|
+
items: recordIdentifierSchema,
|
|
451
|
+
},
|
|
452
|
+
ok: { const: true },
|
|
453
|
+
},
|
|
454
|
+
additionalProperties: true,
|
|
455
|
+
});
|
|
456
|
+
const validateColumnsRequestSchema = {
|
|
457
|
+
type: 'object',
|
|
458
|
+
required: ['resourceId', 'editableColumns', 'record'],
|
|
459
|
+
properties: {
|
|
460
|
+
resourceId: { type: 'string' },
|
|
461
|
+
editableColumns: {
|
|
462
|
+
type: 'array',
|
|
463
|
+
items: {
|
|
464
|
+
type: 'object',
|
|
465
|
+
required: ['name'],
|
|
466
|
+
properties: {
|
|
467
|
+
name: { type: 'string' },
|
|
468
|
+
value: {},
|
|
469
|
+
},
|
|
470
|
+
additionalProperties: true,
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
record: genericObjectSchema,
|
|
474
|
+
},
|
|
475
|
+
additionalProperties: true,
|
|
476
|
+
};
|
|
477
|
+
const validateColumnsResponseSchema = createErrorOrSuccessSchema({
|
|
478
|
+
type: 'object',
|
|
479
|
+
required: ['validationResults'],
|
|
480
|
+
properties: {
|
|
481
|
+
validationResults: {
|
|
482
|
+
type: 'object',
|
|
483
|
+
additionalProperties: validationResultSchema,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
additionalProperties: true,
|
|
487
|
+
});
|
|
29
488
|
export async function interpretResource(adminUser, resource, meta, source, adminforth) {
|
|
30
489
|
afLogger.trace(`🪲Interpreting resource, ${resource.resourceId}, ${source}, 'adminUser', ${adminUser}`);
|
|
31
490
|
const allowedActions = {};
|
|
@@ -182,7 +641,7 @@ export default class AdminForthRestAPI {
|
|
|
182
641
|
handler: async ({ tr }) => {
|
|
183
642
|
const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
|
|
184
643
|
return {
|
|
185
|
-
loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
|
|
644
|
+
loginPromptHTML: loginPromptHTML ? await tr(loginPromptHTML, 'system.loginPromptHTML') : null,
|
|
186
645
|
};
|
|
187
646
|
}
|
|
188
647
|
});
|
|
@@ -222,7 +681,7 @@ export default class AdminForthRestAPI {
|
|
|
222
681
|
server.endpoint({
|
|
223
682
|
method: 'GET',
|
|
224
683
|
path: '/get_base_config',
|
|
225
|
-
handler: async ({
|
|
684
|
+
handler: async ({ adminUser, cookies, tr, response }) => {
|
|
226
685
|
var _a, _b, _c, _d, _e;
|
|
227
686
|
let username = '';
|
|
228
687
|
let userFullName = '';
|
|
@@ -385,6 +844,8 @@ export default class AdminForthRestAPI {
|
|
|
385
844
|
server.endpoint({
|
|
386
845
|
method: 'GET',
|
|
387
846
|
path: '/get_menu_badges',
|
|
847
|
+
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.',
|
|
848
|
+
response_schema: getMenuBadgesResponseSchema,
|
|
388
849
|
handler: async ({ adminUser }) => {
|
|
389
850
|
const badges = {};
|
|
390
851
|
const badgeFunctions = [];
|
|
@@ -421,6 +882,9 @@ export default class AdminForthRestAPI {
|
|
|
421
882
|
server.endpoint({
|
|
422
883
|
method: 'POST',
|
|
423
884
|
path: '/get_resource',
|
|
885
|
+
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.',
|
|
886
|
+
request_schema: getResourceRequestSchema,
|
|
887
|
+
response_schema: getResourceResponseSchema,
|
|
424
888
|
handler: async ({ body, adminUser, tr }) => {
|
|
425
889
|
var _a, _b;
|
|
426
890
|
const { resourceId } = body;
|
|
@@ -508,7 +972,7 @@ export default class AdminForthRestAPI {
|
|
|
508
972
|
col.foreignResource.unsetLabel = await tr(col.foreignResource.unsetLabel, `resource.${resource.resourceId}.foreignResource.unsetLabel`);
|
|
509
973
|
}
|
|
510
974
|
if (inCol.suggestOnCreate && typeof inCol.suggestOnCreate === 'function') {
|
|
511
|
-
col.suggestOnCreate = await inCol.suggestOnCreate(adminUser);
|
|
975
|
+
col.suggestOnCreate = await inCol.suggestOnCreate({ adminUser });
|
|
512
976
|
}
|
|
513
977
|
return Object.assign(Object.assign({}, col), { showIn,
|
|
514
978
|
validation, label: translated[`resCol${i}`], enum: enumItems });
|
|
@@ -526,6 +990,9 @@ export default class AdminForthRestAPI {
|
|
|
526
990
|
server.endpoint({
|
|
527
991
|
method: 'POST',
|
|
528
992
|
path: '/get_resource_data',
|
|
993
|
+
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.',
|
|
994
|
+
request_schema: getResourceDataRequestSchema,
|
|
995
|
+
response_schema: getResourceDataResponseSchema,
|
|
529
996
|
handler: async ({ body, adminUser, headers, query, cookies, requestUrl, abortSignal }) => {
|
|
530
997
|
var _a, _b, _c, _d, _e, _f;
|
|
531
998
|
const { resourceId, source } = body;
|
|
@@ -793,6 +1260,9 @@ export default class AdminForthRestAPI {
|
|
|
793
1260
|
server.endpoint({
|
|
794
1261
|
method: 'POST',
|
|
795
1262
|
path: '/get_resource_foreign_data',
|
|
1263
|
+
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.',
|
|
1264
|
+
request_schema: getResourceForeignDataRequestSchema,
|
|
1265
|
+
response_schema: getResourceForeignDataResponseSchema,
|
|
796
1266
|
handler: async ({ body, adminUser, headers, query, cookies, requestUrl }) => {
|
|
797
1267
|
const { resourceId, column, search } = body;
|
|
798
1268
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
@@ -953,6 +1423,9 @@ export default class AdminForthRestAPI {
|
|
|
953
1423
|
server.endpoint({
|
|
954
1424
|
method: 'POST',
|
|
955
1425
|
path: '/get_min_max_for_columns',
|
|
1426
|
+
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.',
|
|
1427
|
+
request_schema: getMinMaxForColumnsRequestSchema,
|
|
1428
|
+
response_schema: getMinMaxForColumnsResponseSchema,
|
|
956
1429
|
handler: async ({ body }) => {
|
|
957
1430
|
const { resourceId } = body;
|
|
958
1431
|
if (!this.adminforth.statuses.dbDiscover) {
|
|
@@ -982,6 +1455,9 @@ export default class AdminForthRestAPI {
|
|
|
982
1455
|
server.endpoint({
|
|
983
1456
|
method: 'POST',
|
|
984
1457
|
path: '/create_record',
|
|
1458
|
+
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.',
|
|
1459
|
+
request_schema: createRecordRequestSchema,
|
|
1460
|
+
response_schema: createRecordResponseSchema,
|
|
985
1461
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
986
1462
|
var _a, _b, _c, _d;
|
|
987
1463
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
@@ -1111,6 +1587,9 @@ export default class AdminForthRestAPI {
|
|
|
1111
1587
|
server.endpoint({
|
|
1112
1588
|
method: 'POST',
|
|
1113
1589
|
path: '/update_record',
|
|
1590
|
+
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.',
|
|
1591
|
+
request_schema: updateRecordRequestSchema,
|
|
1592
|
+
response_schema: updateRecordResponseSchema,
|
|
1114
1593
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1115
1594
|
var _a, _b;
|
|
1116
1595
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
@@ -1229,6 +1708,9 @@ export default class AdminForthRestAPI {
|
|
|
1229
1708
|
server.endpoint({
|
|
1230
1709
|
method: 'POST',
|
|
1231
1710
|
path: '/delete_record',
|
|
1711
|
+
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.',
|
|
1712
|
+
request_schema: deleteRecordRequestSchema,
|
|
1713
|
+
response_schema: deleteRecordResponseSchema,
|
|
1232
1714
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1233
1715
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
1234
1716
|
if (!resource) {
|
|
@@ -1289,6 +1771,9 @@ export default class AdminForthRestAPI {
|
|
|
1289
1771
|
server.endpoint({
|
|
1290
1772
|
method: 'POST',
|
|
1291
1773
|
path: '/start_custom_action',
|
|
1774
|
+
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.',
|
|
1775
|
+
request_schema: startCustomActionRequestSchema,
|
|
1776
|
+
response_schema: startCustomActionResponseSchema,
|
|
1292
1777
|
handler: async ({ body, adminUser, tr, cookies, response, headers }) => {
|
|
1293
1778
|
const { resourceId, actionId, recordId, extra } = body;
|
|
1294
1779
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
@@ -1323,6 +1808,9 @@ export default class AdminForthRestAPI {
|
|
|
1323
1808
|
server.endpoint({
|
|
1324
1809
|
method: 'POST',
|
|
1325
1810
|
path: '/start_custom_bulk_action',
|
|
1811
|
+
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.',
|
|
1812
|
+
request_schema: startCustomBulkActionRequestSchema,
|
|
1813
|
+
response_schema: startCustomBulkActionResponseSchema,
|
|
1326
1814
|
handler: async ({ body, adminUser, tr, response, cookies, headers }) => {
|
|
1327
1815
|
const { resourceId, actionId, recordIds, extra } = body;
|
|
1328
1816
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
@@ -1358,6 +1846,9 @@ export default class AdminForthRestAPI {
|
|
|
1358
1846
|
server.endpoint({
|
|
1359
1847
|
method: 'POST',
|
|
1360
1848
|
path: '/validate_columns',
|
|
1849
|
+
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.',
|
|
1850
|
+
request_schema: validateColumnsRequestSchema,
|
|
1851
|
+
response_schema: validateColumnsResponseSchema,
|
|
1361
1852
|
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
1362
1853
|
const { resourceId, editableColumns, record } = body;
|
|
1363
1854
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|