adminforth 2.27.0-next.5 → 2.27.0-next.51

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.
Files changed (82) hide show
  1. package/commands/callTsProxy.js +10 -5
  2. package/commands/createApp/templates/api.ts.hbs +28 -9
  3. package/commands/createApp/templates/package.json.hbs +2 -1
  4. package/commands/proxy.ts +18 -10
  5. package/dist/commands/proxy.js +14 -10
  6. package/dist/commands/proxy.js.map +1 -1
  7. package/dist/dataConnectors/clickhouse.d.ts +5 -2
  8. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  9. package/dist/dataConnectors/clickhouse.js +73 -9
  10. package/dist/dataConnectors/clickhouse.js.map +1 -1
  11. package/dist/index.d.ts +4 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +17 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/modules/configValidator.d.ts.map +1 -1
  16. package/dist/modules/configValidator.js +16 -9
  17. package/dist/modules/configValidator.js.map +1 -1
  18. package/dist/modules/restApi.d.ts.map +1 -1
  19. package/dist/modules/restApi.js +515 -9
  20. package/dist/modules/restApi.js.map +1 -1
  21. package/dist/modules/styles.js +1 -1
  22. package/dist/modules/utils.d.ts +1 -1
  23. package/dist/modules/utils.d.ts.map +1 -1
  24. package/dist/modules/utils.js +3 -5
  25. package/dist/modules/utils.js.map +1 -1
  26. package/dist/servers/express.d.ts +13 -7
  27. package/dist/servers/express.d.ts.map +1 -1
  28. package/dist/servers/express.js +141 -1
  29. package/dist/servers/express.js.map +1 -1
  30. package/dist/servers/openapi.d.ts +25 -0
  31. package/dist/servers/openapi.d.ts.map +1 -0
  32. package/dist/servers/openapi.js +126 -0
  33. package/dist/servers/openapi.js.map +1 -0
  34. package/dist/spa/package-lock.json +41 -0
  35. package/dist/spa/package.json +4 -0
  36. package/dist/spa/pnpm-lock.yaml +38 -0
  37. package/dist/spa/src/App.vue +77 -76
  38. package/dist/spa/src/afcl/Button.vue +2 -3
  39. package/dist/spa/src/afcl/Dialog.vue +1 -1
  40. package/dist/spa/src/afcl/Input.vue +1 -1
  41. package/dist/spa/src/afcl/Select.vue +8 -2
  42. package/dist/spa/src/afcl/Skeleton.vue +5 -0
  43. package/dist/spa/src/afcl/Spinner.vue +1 -1
  44. package/dist/spa/src/components/CallActionWrapper.vue +1 -1
  45. package/dist/spa/src/components/ColumnValueInput.vue +16 -3
  46. package/dist/spa/src/components/ColumnValueInputWrapper.vue +25 -2
  47. package/dist/spa/src/components/CustomRangePicker.vue +16 -14
  48. package/dist/spa/src/components/Filters.vue +95 -63
  49. package/dist/spa/src/components/GroupsTable.vue +9 -6
  50. package/dist/spa/src/components/MenuLink.vue +2 -2
  51. package/dist/spa/src/components/ResourceForm.vue +101 -7
  52. package/dist/spa/src/components/ResourceListTable.vue +14 -8
  53. package/dist/spa/src/components/ShowTable.vue +1 -1
  54. package/dist/spa/src/components/Sidebar.vue +29 -8
  55. package/dist/spa/src/components/ThreeDotsMenu.vue +25 -10
  56. package/dist/spa/src/components/ValueRenderer.vue +1 -0
  57. package/dist/spa/src/spa_types/core.ts +32 -0
  58. package/dist/spa/src/stores/filters.ts +16 -12
  59. package/dist/spa/src/types/Back.ts +137 -26
  60. package/dist/spa/src/types/Common.ts +24 -5
  61. package/dist/spa/src/types/adapters/CompletionAdapter.ts +27 -5
  62. package/dist/spa/src/types/adapters/index.ts +2 -2
  63. package/dist/spa/src/utils/createEditUtils.ts +65 -0
  64. package/dist/spa/src/utils/index.ts +2 -1
  65. package/dist/spa/src/utils/utils.ts +42 -7
  66. package/dist/spa/src/utils.ts +2 -1
  67. package/dist/spa/src/views/CreateEditSkeleton.vue +78 -0
  68. package/dist/spa/src/views/CreateView.vue +24 -50
  69. package/dist/spa/src/views/EditView.vue +23 -40
  70. package/dist/spa/src/views/ListView.vue +22 -32
  71. package/dist/spa/src/views/ShowView.vue +66 -24
  72. package/dist/types/Back.d.ts +140 -32
  73. package/dist/types/Back.d.ts.map +1 -1
  74. package/dist/types/Back.js.map +1 -1
  75. package/dist/types/Common.d.ts +31 -5
  76. package/dist/types/Common.d.ts.map +1 -1
  77. package/dist/types/Common.js.map +1 -1
  78. package/dist/types/adapters/CompletionAdapter.d.ts +18 -3
  79. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -1
  80. package/dist/types/adapters/index.d.ts +1 -1
  81. package/dist/types/adapters/index.d.ts.map +1 -1
  82. package/package.json +11 -6
@@ -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 ({ input, adminUser, cookies, tr, response }) => {
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);