adminforth 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,625 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { AdminForthFilterOperators, AdminForthDataTypes, AllowedActionsEnum, ActionCheckSource } from "../types/AdminForthConfig.js";
11
+ import { ADMINFORTH_VERSION, listify } from './utils.js';
12
+ import AdminForthAuth from "../auth.js";
13
+ export default class AdminForthRestAPI {
14
+ constructor(adminforth) {
15
+ this.adminforth = adminforth;
16
+ }
17
+ registerEndpoints(server) {
18
+ server.endpoint({
19
+ noAuth: true,
20
+ method: 'POST',
21
+ path: '/login',
22
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
23
+ var _b, _c, _d, _e;
24
+ const INVALID_MESSAGE = 'Invalid username or password';
25
+ const { username, password } = body;
26
+ let adminUser;
27
+ let toReturn = { ok: true, allowedLogin: true };
28
+ let token;
29
+ if (username === this.adminforth.config.rootUser.username && password === this.adminforth.config.rootUser.password) {
30
+ this.adminforth.auth.setAuthCookie({ response, username, pk: null });
31
+ adminUser = { isRoot: true, dbUser: null, pk: null, username: this.adminforth.config.rootUser.username };
32
+ }
33
+ else {
34
+ // get resource from db
35
+ if (!this.adminforth.config.auth) {
36
+ throw new Error('No config.auth defined');
37
+ }
38
+ const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.resourceId);
39
+ // if there is no passwordHashField, in columns, add it, with backendOnly and showIn: []
40
+ if (!userResource.dataSourceColumns.find((col) => col.name === this.adminforth.config.auth.passwordHashField)) {
41
+ userResource.dataSourceColumns.push({
42
+ name: this.adminforth.config.auth.passwordHashField,
43
+ backendOnly: true,
44
+ showIn: [],
45
+ type: AdminForthDataTypes.STRING,
46
+ });
47
+ console.log('Adding passwordHashField to userResource', userResource);
48
+ }
49
+ const userRecord = (_b = (yield this.adminforth.connectors[userResource.dataSource].getData({
50
+ resource: userResource,
51
+ filters: [
52
+ { field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
53
+ ],
54
+ limit: 1,
55
+ offset: 0,
56
+ sort: [],
57
+ })).data) === null || _b === void 0 ? void 0 : _b[0];
58
+ if (!userRecord) {
59
+ return { error: 'User not found' };
60
+ }
61
+ const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
62
+ const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
63
+ if (valid) {
64
+ adminUser = {
65
+ isRoot: false, dbUser: userRecord,
66
+ pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
67
+ username,
68
+ };
69
+ const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
70
+ if (beforeLoginConfirmation === null || beforeLoginConfirmation === void 0 ? void 0 : beforeLoginConfirmation.length) {
71
+ for (const hook of beforeLoginConfirmation) {
72
+ const resp = yield hook({ adminUser, response });
73
+ if ((_c = resp === null || resp === void 0 ? void 0 : resp.body) === null || _c === void 0 ? void 0 : _c.redirectTo) {
74
+ toReturn = { ok: resp.ok, redirectTo: (_d = resp === null || resp === void 0 ? void 0 : resp.body) === null || _d === void 0 ? void 0 : _d.redirectTo, allowedLogin: (_e = resp === null || resp === void 0 ? void 0 : resp.body) === null || _e === void 0 ? void 0 : _e.allowedLogin };
75
+ break;
76
+ }
77
+ }
78
+ }
79
+ if (toReturn.allowedLogin) {
80
+ this.adminforth.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
81
+ }
82
+ }
83
+ else {
84
+ return { error: INVALID_MESSAGE };
85
+ }
86
+ }
87
+ return toReturn;
88
+ })
89
+ });
90
+ server.endpoint({
91
+ method: 'POST',
92
+ path: '/check_auth',
93
+ handler: (_f) => __awaiter(this, [_f], void 0, function* ({ adminUser }) {
94
+ return { ok: true };
95
+ }),
96
+ });
97
+ server.endpoint({
98
+ noAuth: true,
99
+ method: 'POST',
100
+ path: '/logout',
101
+ handler: (_g) => __awaiter(this, [_g], void 0, function* ({ response }) {
102
+ this.adminforth.auth.removeAuthCookie(response);
103
+ return { ok: true };
104
+ }),
105
+ });
106
+ server.endpoint({
107
+ noAuth: true,
108
+ method: 'GET',
109
+ path: '/get_public_config',
110
+ handler: (_h) => __awaiter(this, [_h], void 0, function* ({ body }) {
111
+ var _j;
112
+ // find resource
113
+ if (!this.adminforth.config.auth) {
114
+ throw new Error('No config.auth defined');
115
+ }
116
+ const usernameField = this.adminforth.config.auth.usernameField;
117
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.resourceId);
118
+ const usernameColumn = resource.columns.find((col) => col.name === usernameField);
119
+ return {
120
+ brandName: this.adminforth.config.customization.brandName,
121
+ usernameFieldName: usernameColumn.label,
122
+ loginBackgroundImage: this.adminforth.config.auth.loginBackgroundImage,
123
+ title: (_j = this.adminforth.config.customization) === null || _j === void 0 ? void 0 : _j.title,
124
+ };
125
+ }),
126
+ });
127
+ server.endpoint({
128
+ method: 'GET',
129
+ path: '/get_base_config',
130
+ handler: (_k) => __awaiter(this, [_k], void 0, function* ({ input, adminUser, cookies }) {
131
+ var _l, _m;
132
+ let username = '';
133
+ let userFullName = '';
134
+ if (adminUser.isRoot) {
135
+ username = this.adminforth.config.rootUser.username;
136
+ }
137
+ else {
138
+ const dbUser = adminUser.dbUser;
139
+ username = dbUser[this.adminforth.config.auth.usernameField];
140
+ userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
141
+ }
142
+ const userData = {
143
+ [this.adminforth.config.auth.usernameField]: username,
144
+ [this.adminforth.config.auth.userFullNameField]: userFullName
145
+ };
146
+ const checkIsMenuItemVisible = (menuItem) => {
147
+ if (typeof menuItem.visible === 'function') {
148
+ const toReturn = menuItem.visible(adminUser);
149
+ if (typeof toReturn !== 'boolean') {
150
+ throw new Error(`'visible' function of ${menuItem.label || menuItem.type} must return boolean value`);
151
+ }
152
+ return toReturn;
153
+ }
154
+ };
155
+ let newMenu = [];
156
+ for (let menuItem of this.adminforth.config.menu) {
157
+ let newMenuItem = Object.assign({}, menuItem);
158
+ if (menuItem.visible) {
159
+ if (!checkIsMenuItemVisible(menuItem)) {
160
+ continue;
161
+ }
162
+ }
163
+ if (menuItem.children) {
164
+ let newChildren = [];
165
+ for (let child of menuItem.children) {
166
+ let newChild = Object.assign({}, child);
167
+ if (child.visible) {
168
+ if (!checkIsMenuItemVisible(child)) {
169
+ continue;
170
+ }
171
+ }
172
+ newChildren.push(newChild);
173
+ }
174
+ newMenuItem = Object.assign(Object.assign({}, newMenuItem), { children: newChildren });
175
+ }
176
+ newMenu.push(newMenuItem);
177
+ }
178
+ return {
179
+ user: userData,
180
+ resources: this.adminforth.config.resources.map((res) => ({
181
+ resourceId: res.resourceId,
182
+ label: res.label,
183
+ })),
184
+ menu: newMenu,
185
+ config: {
186
+ brandName: this.adminforth.config.customization.brandName,
187
+ brandLogo: this.adminforth.config.customization.brandLogo,
188
+ datesFormat: this.adminforth.config.customization.datesFormat,
189
+ deleteConfirmation: this.adminforth.config.deleteConfirmation,
190
+ auth: this.adminforth.config.auth,
191
+ usernameField: this.adminforth.config.auth.usernameField,
192
+ title: (_l = this.adminforth.config.customization) === null || _l === void 0 ? void 0 : _l.title,
193
+ emptyFieldPlaceholder: (_m = this.adminforth.config.customization) === null || _m === void 0 ? void 0 : _m.emptyFieldPlaceholder,
194
+ },
195
+ adminUser,
196
+ version: ADMINFORTH_VERSION,
197
+ };
198
+ }),
199
+ });
200
+ function interpretResource(adminUser, resource, meta, source) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ var _a;
203
+ if (process.env.HEAVY_DEBUG) {
204
+ console.log('🪲Interpreting resource', resource.resourceId, source);
205
+ }
206
+ const allowedActions = {};
207
+ yield Promise.all(Object.entries(((_a = resource.options) === null || _a === void 0 ? void 0 : _a.allowedActions) || {}).map((_b) => __awaiter(this, [_b], void 0, function* ([key, value]) {
208
+ if (process.env.HEAVY_DEBUG) {
209
+ console.log('🪲checking for allowed call', key, 'value:', value, 'typeof', typeof value);
210
+ }
211
+ // if callable then call
212
+ if (typeof value === 'function') {
213
+ allowedActions[key] = yield value({ adminUser, resource, meta, source });
214
+ }
215
+ else {
216
+ allowedActions[key] = value;
217
+ }
218
+ })));
219
+ return { allowedActions };
220
+ });
221
+ }
222
+ function checkAccess(action, allowedActions) {
223
+ const allowed = allowedActions[action];
224
+ if (allowed !== true) {
225
+ return { error: typeof allowed === 'string' ? allowed : 'Action is not allowed', allowed: false };
226
+ }
227
+ return { allowed: true };
228
+ }
229
+ server.endpoint({
230
+ method: 'POST',
231
+ path: '/get_resource',
232
+ handler: (_o) => __awaiter(this, [_o], void 0, function* ({ body, adminUser }) {
233
+ const { resourceId } = body;
234
+ if (!this.adminforth.statuses.dbDiscover) {
235
+ return { error: 'Database discovery not started' };
236
+ }
237
+ if (this.adminforth.statuses.dbDiscover !== 'done') {
238
+ return { error: 'Database discovery is still in progress, please try later' };
239
+ }
240
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
241
+ if (!resource) {
242
+ return { error: `Resource ${resourceId} not found` };
243
+ }
244
+ const { allowedActions } = yield interpretResource(adminUser, resource, {}, ActionCheckSource.DisplayButtons);
245
+ // exclude "plugins" key
246
+ return {
247
+ resource: Object.assign(Object.assign({}, resource), { plugins: undefined, options: Object.assign(Object.assign({}, resource.options), { allowedActions }) })
248
+ };
249
+ }),
250
+ });
251
+ server.endpoint({
252
+ method: 'POST',
253
+ path: '/get_resource_data',
254
+ handler: (_p) => __awaiter(this, [_p], void 0, function* ({ body, adminUser }) {
255
+ var _q, _r, _s, _t;
256
+ const { resourceId, source } = body;
257
+ if (['show', 'list'].includes(source) === false) {
258
+ return { error: 'Invalid source, should be list or show' };
259
+ }
260
+ if (!this.adminforth.statuses.dbDiscover) {
261
+ return { error: 'Database discovery not started' };
262
+ }
263
+ if (this.adminforth.statuses.dbDiscover !== 'done') {
264
+ return { error: 'Database discovery is still in progress, please try later' };
265
+ }
266
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
267
+ if (!resource) {
268
+ return { error: `Resource ${resourceId} not found` };
269
+ }
270
+ const { allowedActions } = yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.DisplayButtons);
271
+ const { allowed, error } = checkAccess(source, allowedActions);
272
+ if (!allowed) {
273
+ return { error };
274
+ }
275
+ for (const hook of listify((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.beforeDatasourceRequest)) {
276
+ const resp = yield hook({ resource, query: body, adminUser });
277
+ if (!resp || (!resp.ok && !resp.error)) {
278
+ throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
279
+ }
280
+ if (resp.error) {
281
+ return { error: resp.error };
282
+ }
283
+ }
284
+ const { limit, offset, filters, sort } = body;
285
+ for (const filter of (filters || [])) {
286
+ if (!Object.values(AdminForthFilterOperators).includes(filter.operator)) {
287
+ throw new Error(`Operator '${filter.operator}' is not allowed`);
288
+ }
289
+ if (!resource.columns.some((col) => col.name === filter.field)) {
290
+ throw new Error(`Field '${filter.field}' is not in resource '${resource.resourceId}'. Available fields: ${resource.columns.map((col) => col.name).join(', ')}`);
291
+ }
292
+ if (filter.operator === AdminForthFilterOperators.IN || filter.operator === AdminForthFilterOperators.NIN) {
293
+ if (!Array.isArray(filter.value)) {
294
+ throw new Error(`Value for operator '${filter.operator}' should be an array`);
295
+ }
296
+ }
297
+ if (filter.operator === AdminForthFilterOperators.IN && filter.value.length === 0) {
298
+ // nonsense
299
+ return { data: [], total: 0 };
300
+ }
301
+ }
302
+ const data = yield this.adminforth.connectors[resource.dataSource].getData({
303
+ resource,
304
+ limit,
305
+ offset,
306
+ filters,
307
+ sort,
308
+ });
309
+ // for foreign keys, add references
310
+ yield Promise.all(resource.columns.filter((col) => col.foreignResource).map((col) => __awaiter(this, void 0, void 0, function* () {
311
+ const targetResource = this.adminforth.config.resources.find((res) => res.resourceId == col.foreignResource.resourceId);
312
+ const targetConnector = this.adminforth.connectors[targetResource.dataSource];
313
+ const targetResourcePkField = targetResource.columns.find((col) => col.primaryKey).name;
314
+ const pksUnique = [...new Set(data.data.map((item) => item[col.name]))];
315
+ if (pksUnique.length === 0) {
316
+ return;
317
+ }
318
+ const targetData = yield targetConnector.getData({
319
+ resource: targetResource,
320
+ limit: limit,
321
+ offset: 0,
322
+ filters: [
323
+ {
324
+ field: targetResourcePkField,
325
+ operator: AdminForthFilterOperators.IN,
326
+ value: pksUnique,
327
+ }
328
+ ],
329
+ sort: [],
330
+ });
331
+ const targetDataMap = targetData.data.reduce((acc, item) => {
332
+ acc[item[targetResourcePkField]] = {
333
+ label: targetResource.recordLabel(item),
334
+ pk: item[targetResourcePkField],
335
+ };
336
+ return acc;
337
+ }, {});
338
+ data.data.forEach((item) => {
339
+ item[col.name] = targetDataMap[item[col.name]];
340
+ });
341
+ })));
342
+ for (const hook of listify((_t = (_s = resource.hooks) === null || _s === void 0 ? void 0 : _s[source]) === null || _t === void 0 ? void 0 : _t.afterDatasourceResponse)) {
343
+ const resp = yield hook({ resource, response: data.data, adminUser });
344
+ if (!resp || (!resp.ok && !resp.error)) {
345
+ throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
346
+ }
347
+ if (resp.error) {
348
+ return { error: resp.error };
349
+ }
350
+ }
351
+ // remove all columns which are not defined in resources, or defined but backendOnly
352
+ data.data.forEach((item) => {
353
+ Object.keys(item).forEach((key) => {
354
+ if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
355
+ delete item[key];
356
+ }
357
+ });
358
+ });
359
+ data.data.forEach((item) => {
360
+ item._label = resource.recordLabel(item);
361
+ });
362
+ return Object.assign(Object.assign({}, data), { options: resource === null || resource === void 0 ? void 0 : resource.options });
363
+ }),
364
+ });
365
+ server.endpoint({
366
+ method: 'POST',
367
+ path: '/get_resource_foreign_data',
368
+ handler: (_u) => __awaiter(this, [_u], void 0, function* ({ body, adminUser }) {
369
+ var _v, _w, _x, _y;
370
+ const { resourceId, column } = body;
371
+ if (!this.adminforth.statuses.dbDiscover) {
372
+ return { error: 'Database discovery not started' };
373
+ }
374
+ if (this.adminforth.statuses.dbDiscover !== 'done') {
375
+ return { error: 'Database discovery is still in progress, please try later' };
376
+ }
377
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
378
+ if (!resource) {
379
+ return { error: `Resource '${resourceId}' not found` };
380
+ }
381
+ const columnConfig = resource.columns.find((col) => col.name == column);
382
+ if (!columnConfig) {
383
+ return { error: `Column "${column}' not found in resource with resourceId '${resourceId}'` };
384
+ }
385
+ if (!columnConfig.foreignResource) {
386
+ return { error: `Column '${column}' in resource '${resourceId}' is not a foreign key` };
387
+ }
388
+ const targetResourceId = columnConfig.foreignResource.resourceId;
389
+ const targetResource = this.adminforth.config.resources.find((res) => res.resourceId == targetResourceId);
390
+ for (const hook of listify((_w = (_v = columnConfig.foreignResource.hooks) === null || _v === void 0 ? void 0 : _v.dropdownList) === null || _w === void 0 ? void 0 : _w.beforeDatasourceRequest)) {
391
+ const resp = yield hook({ query: body, adminUser, resource: targetResource });
392
+ if (!resp || (!resp.ok && !resp.error)) {
393
+ throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
394
+ }
395
+ if (resp.error) {
396
+ return { error: resp.error };
397
+ }
398
+ }
399
+ const { limit, offset, filters, sort } = body;
400
+ const dbDataItems = yield this.adminforth.connectors[targetResource.dataSource].getData({
401
+ resource: targetResource,
402
+ limit,
403
+ offset,
404
+ filters: filters || [],
405
+ sort: sort || [],
406
+ });
407
+ const items = dbDataItems.data.map((item) => {
408
+ const pk = item[targetResource.columns.find((col) => col.primaryKey).name];
409
+ const labler = targetResource.recordLabel;
410
+ return {
411
+ value: pk,
412
+ label: labler(item),
413
+ _item: item, // user might need it in hook to form new label
414
+ };
415
+ });
416
+ const response = {
417
+ items
418
+ };
419
+ for (const hook of listify((_y = (_x = columnConfig.foreignResource.hooks) === null || _x === void 0 ? void 0 : _x.dropdownList) === null || _y === void 0 ? void 0 : _y.afterDatasourceResponse)) {
420
+ const resp = yield hook({ response, adminUser, resource: targetResource });
421
+ if (!resp || (!resp.ok && !resp.error)) {
422
+ throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
423
+ }
424
+ if (resp.error) {
425
+ return { error: resp.error };
426
+ }
427
+ }
428
+ return response;
429
+ }),
430
+ });
431
+ server.endpoint({
432
+ method: 'POST',
433
+ path: '/get_min_max_for_columns',
434
+ handler: (_z) => __awaiter(this, [_z], void 0, function* ({ body }) {
435
+ const { resourceId } = body;
436
+ if (!this.adminforth.statuses.dbDiscover) {
437
+ return { error: 'Database discovery not started' };
438
+ }
439
+ if (this.adminforth.statuses.dbDiscover !== 'done') {
440
+ return { error: 'Database discovery is still in progress, please try later' };
441
+ }
442
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
443
+ if (!resource) {
444
+ return { error: `Resource '${resourceId}' not found` };
445
+ }
446
+ const item = yield this.adminforth.connectors[resource.dataSource].getMinMaxForColumns({
447
+ resource,
448
+ columns: resource.columns.filter((col) => [
449
+ AdminForthDataTypes.INTEGER,
450
+ AdminForthDataTypes.FLOAT,
451
+ AdminForthDataTypes.DATE,
452
+ AdminForthDataTypes.DATETIME,
453
+ AdminForthDataTypes.TIME,
454
+ AdminForthDataTypes.DECIMAL,
455
+ ].includes(col.type) && col.allowMinMaxQuery === true),
456
+ });
457
+ return item;
458
+ }),
459
+ });
460
+ server.endpoint({
461
+ method: 'POST',
462
+ path: '/create_record',
463
+ handler: (_0) => __awaiter(this, [_0], void 0, function* ({ body, adminUser }) {
464
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
465
+ if (!resource) {
466
+ return { error: `Resource '${body['resourceId']}' not found` };
467
+ }
468
+ const { allowedActions } = yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.CreateRequest);
469
+ const { allowed, error } = checkAccess(AllowedActionsEnum.create, allowedActions);
470
+ if (!allowed) {
471
+ return { error };
472
+ }
473
+ const { record } = body;
474
+ const response = yield this.adminforth.createResourceRecord({ resource, record, adminUser });
475
+ if (response.error) {
476
+ return { error: response.error };
477
+ }
478
+ const connector = this.adminforth.connectors[resource.dataSource];
479
+ return {
480
+ newRecordId: record[connector.getPrimaryKey(resource)]
481
+ };
482
+ })
483
+ });
484
+ server.endpoint({
485
+ method: 'POST',
486
+ path: '/update_record',
487
+ handler: (_1) => __awaiter(this, [_1], void 0, function* ({ body, adminUser }) {
488
+ var _2, _3, _4, _5;
489
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
490
+ if (!resource) {
491
+ return { error: `Resource '${body['resourceId']}' not found` };
492
+ }
493
+ const recordId = body['recordId'];
494
+ const connector = this.adminforth.connectors[resource.dataSource];
495
+ const oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
496
+ if (!oldRecord) {
497
+ const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
498
+ return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
499
+ }
500
+ const record = body['record'];
501
+ const { allowedActions } = yield interpretResource(adminUser, resource, { requestBody: body, newRecord: record, oldRecord }, ActionCheckSource.EditRequest);
502
+ const { allowed, error } = checkAccess(AllowedActionsEnum.edit, allowedActions);
503
+ if (!allowed) {
504
+ return { error };
505
+ }
506
+ // execute hook if needed
507
+ for (const hook of listify((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.edit) === null || _3 === void 0 ? void 0 : _3.beforeSave)) {
508
+ const resp = yield hook({ resource, record, adminUser });
509
+ if (!resp || (!resp.ok && !resp.error)) {
510
+ throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
511
+ }
512
+ if (resp.error) {
513
+ return { error: resp.error };
514
+ }
515
+ }
516
+ const newValues = {};
517
+ for (const recordField in record) {
518
+ if (record[recordField] !== oldRecord[recordField]) {
519
+ const column = resource.columns.find((col) => col.name === recordField);
520
+ if (column) {
521
+ if (!column.virtual) {
522
+ newValues[recordField] = connector.setFieldValue(column, record[recordField]);
523
+ }
524
+ }
525
+ else {
526
+ newValues[recordField] = record[recordField];
527
+ }
528
+ }
529
+ }
530
+ if (Object.keys(newValues).length > 0) {
531
+ yield connector.updateRecord({ resource, recordId, newValues });
532
+ }
533
+ // execute hook if needed
534
+ for (const hook of listify((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.edit) === null || _5 === void 0 ? void 0 : _5.afterSave)) {
535
+ const resp = yield hook({ resource, record, adminUser, oldRecord });
536
+ if (!resp || (!resp.ok && !resp.error)) {
537
+ throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
538
+ }
539
+ if (resp.error) {
540
+ return { error: resp.error };
541
+ }
542
+ }
543
+ return {
544
+ newRecordId: recordId
545
+ };
546
+ })
547
+ });
548
+ server.endpoint({
549
+ method: 'POST',
550
+ path: '/delete_record',
551
+ handler: (_6) => __awaiter(this, [_6], void 0, function* ({ body, adminUser }) {
552
+ var _7, _8, _9, _10;
553
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
554
+ const record = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
555
+ if (!resource) {
556
+ return { error: `Resource '${body['resourceId']}' not found` };
557
+ }
558
+ if (!record) {
559
+ return { error: `Record with ${body['primaryKey']} not found` };
560
+ }
561
+ if (resource.options.allowedActions.delete === false) {
562
+ return { error: `Resource '${resource.resourceId}' does not allow delete action` };
563
+ }
564
+ const { allowedActions } = yield interpretResource(adminUser, resource, { requestBody: body }, ActionCheckSource.DeleteRequest);
565
+ const { allowed, error } = checkAccess(AllowedActionsEnum.delete, allowedActions);
566
+ if (!allowed) {
567
+ return { error };
568
+ }
569
+ // execute hook if needed
570
+ for (const hook of listify((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.delete) === null || _8 === void 0 ? void 0 : _8.beforeSave)) {
571
+ const resp = yield hook({ resource, record, adminUser });
572
+ if (!resp || (!resp.ok && !resp.error)) {
573
+ throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
574
+ }
575
+ if (resp.error) {
576
+ return { error: resp.error };
577
+ }
578
+ }
579
+ const connector = this.adminforth.connectors[resource.dataSource];
580
+ yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
581
+ // execute hook if needed
582
+ for (const hook of listify((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.delete) === null || _10 === void 0 ? void 0 : _10.afterSave)) {
583
+ const resp = yield hook({ resource, record, adminUser });
584
+ if (!resp || (!resp.ok && !resp.error)) {
585
+ throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
586
+ }
587
+ if (resp.error) {
588
+ return { error: resp.error };
589
+ }
590
+ }
591
+ return {
592
+ recordId: body['primaryKey']
593
+ };
594
+ })
595
+ });
596
+ server.endpoint({
597
+ method: 'POST',
598
+ path: '/start_bulk_action',
599
+ handler: (_11) => __awaiter(this, [_11], void 0, function* ({ body }) {
600
+ const { resourceId, actionId, recordIds } = body;
601
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
602
+ if (!resource) {
603
+ return { error: `Resource '${resourceId}' not found` };
604
+ }
605
+ const action = resource.options.bulkActions.find((act) => act.id == actionId);
606
+ if (!action) {
607
+ return { error: `Action '${actionId}' not found` };
608
+ }
609
+ else {
610
+ yield action.action({ selectedIds: recordIds });
611
+ }
612
+ return {
613
+ actionId,
614
+ recordIds,
615
+ resourceId,
616
+ status: 'success'
617
+ };
618
+ })
619
+ });
620
+ // setup endpoints for all plugins
621
+ this.adminforth.activatedPlugins.forEach((plugin) => {
622
+ plugin.setupEndpoints(server);
623
+ });
624
+ }
625
+ }