adminforth 2.4.0-next.33 → 2.4.0-next.331

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 (177) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/createApp/templates/api.ts.hbs +10 -0
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +12 -1
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
  7. package/commands/createApp/templates/schema.prisma.hbs +0 -1
  8. package/commands/createApp/utils.js +10 -0
  9. package/commands/createCustomComponent/configLoader.js +17 -4
  10. package/commands/createCustomComponent/main.js +13 -7
  11. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  12. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
  13. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  14. package/commands/createPlugin/templates/package.json.hbs +1 -1
  15. package/commands/generateModels.js +30 -22
  16. package/dist/auth.d.ts +9 -1
  17. package/dist/auth.d.ts.map +1 -1
  18. package/dist/auth.js +21 -2
  19. package/dist/auth.js.map +1 -1
  20. package/dist/dataConnectors/baseConnector.d.ts +1 -1
  21. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  22. package/dist/dataConnectors/baseConnector.js +70 -18
  23. package/dist/dataConnectors/baseConnector.js.map +1 -1
  24. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  25. package/dist/dataConnectors/clickhouse.js +15 -0
  26. package/dist/dataConnectors/clickhouse.js.map +1 -1
  27. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  28. package/dist/dataConnectors/mongo.js +50 -15
  29. package/dist/dataConnectors/mongo.js.map +1 -1
  30. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  31. package/dist/dataConnectors/mysql.js +11 -0
  32. package/dist/dataConnectors/mysql.js.map +1 -1
  33. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  34. package/dist/dataConnectors/postgres.js +43 -14
  35. package/dist/dataConnectors/postgres.js.map +1 -1
  36. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  37. package/dist/dataConnectors/sqlite.js +11 -0
  38. package/dist/dataConnectors/sqlite.js.map +1 -1
  39. package/dist/index.d.ts +11 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +44 -21
  42. package/dist/index.js.map +1 -1
  43. package/dist/modules/codeInjector.d.ts +2 -0
  44. package/dist/modules/codeInjector.d.ts.map +1 -1
  45. package/dist/modules/codeInjector.js +62 -6
  46. package/dist/modules/codeInjector.js.map +1 -1
  47. package/dist/modules/configValidator.d.ts +6 -0
  48. package/dist/modules/configValidator.d.ts.map +1 -1
  49. package/dist/modules/configValidator.js +209 -25
  50. package/dist/modules/configValidator.js.map +1 -1
  51. package/dist/modules/restApi.d.ts +1 -1
  52. package/dist/modules/restApi.d.ts.map +1 -1
  53. package/dist/modules/restApi.js +199 -31
  54. package/dist/modules/restApi.js.map +1 -1
  55. package/dist/modules/styles.d.ts +499 -13
  56. package/dist/modules/styles.d.ts.map +1 -1
  57. package/dist/modules/styles.js +555 -31
  58. package/dist/modules/styles.js.map +1 -1
  59. package/dist/modules/utils.d.ts +7 -15
  60. package/dist/modules/utils.d.ts.map +1 -1
  61. package/dist/modules/utils.js +45 -68
  62. package/dist/modules/utils.js.map +1 -1
  63. package/dist/servers/express.d.ts +5 -0
  64. package/dist/servers/express.d.ts.map +1 -1
  65. package/dist/servers/express.js +40 -1
  66. package/dist/servers/express.js.map +1 -1
  67. package/dist/spa/index.html +1 -1
  68. package/dist/spa/package-lock.json +1208 -708
  69. package/dist/spa/package.json +34 -34
  70. package/dist/spa/src/App.vue +132 -174
  71. package/dist/spa/src/adminforth.ts +41 -17
  72. package/dist/spa/src/afcl/AreaChart.vue +0 -1
  73. package/dist/spa/src/afcl/BarChart.vue +2 -2
  74. package/dist/spa/src/afcl/Button.vue +3 -3
  75. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  76. package/dist/spa/src/afcl/Card.vue +25 -0
  77. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  78. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  79. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  80. package/dist/spa/src/afcl/Dialog.vue +47 -27
  81. package/dist/spa/src/afcl/Dropzone.vue +145 -48
  82. package/dist/spa/src/afcl/Input.vue +14 -6
  83. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  84. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  85. package/dist/spa/src/afcl/PieChart.vue +5 -5
  86. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  87. package/dist/spa/src/afcl/Select.vue +82 -34
  88. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  89. package/dist/spa/src/afcl/Table.vue +313 -75
  90. package/dist/spa/src/afcl/Textarea.vue +31 -0
  91. package/dist/spa/src/afcl/Toggle.vue +32 -0
  92. package/dist/spa/src/afcl/Tooltip.vue +28 -18
  93. package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
  94. package/dist/spa/src/afcl/index.ts +6 -3
  95. package/dist/spa/src/components/AcceptModal.vue +48 -14
  96. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  97. package/dist/spa/src/components/CallActionWrapper.vue +15 -0
  98. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  99. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  100. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  101. package/dist/spa/src/components/CustomRangePicker.vue +37 -21
  102. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  103. package/dist/spa/src/components/Filters.vue +195 -132
  104. package/dist/spa/src/components/GroupsTable.vue +9 -8
  105. package/dist/spa/src/components/MenuLink.vue +95 -23
  106. package/dist/spa/src/components/ResourceForm.vue +99 -51
  107. package/dist/spa/src/components/ResourceListTable.vue +121 -95
  108. package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
  109. package/dist/spa/src/components/ShowTable.vue +21 -15
  110. package/dist/spa/src/components/Sidebar.vue +472 -0
  111. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  112. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  113. package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
  114. package/dist/spa/src/components/Toast.vue +40 -29
  115. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  116. package/dist/spa/src/components/ValueRenderer.vue +44 -17
  117. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  118. package/dist/spa/src/i18n.ts +5 -3
  119. package/dist/spa/src/main.ts +1 -1
  120. package/dist/spa/src/renderers/CompactField.vue +1 -1
  121. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  122. package/dist/spa/src/router/index.ts +8 -0
  123. package/dist/spa/src/shims-vue.d.ts +5 -0
  124. package/dist/spa/src/spa_types/core.ts +13 -1
  125. package/dist/spa/src/stores/core.ts +15 -1
  126. package/dist/spa/src/stores/filters.ts +33 -2
  127. package/dist/spa/src/stores/modal.ts +6 -1
  128. package/dist/spa/src/stores/toast.ts +22 -3
  129. package/dist/spa/src/types/Back.ts +168 -23
  130. package/dist/spa/src/types/Common.ts +109 -32
  131. package/dist/spa/src/types/FrontendAPI.ts +32 -23
  132. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
  134. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  135. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  136. package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
  137. package/dist/spa/src/types/adapters/index.ts +3 -0
  138. package/dist/spa/src/utils.ts +291 -11
  139. package/dist/spa/src/views/CreateView.vue +88 -22
  140. package/dist/spa/src/views/EditView.vue +55 -22
  141. package/dist/spa/src/views/ListView.vue +144 -87
  142. package/dist/spa/src/views/LoginView.vue +26 -35
  143. package/dist/spa/src/views/ResourceParent.vue +2 -2
  144. package/dist/spa/src/views/SettingsView.vue +121 -0
  145. package/dist/spa/src/views/ShowView.vue +83 -53
  146. package/dist/spa/src/websocket.ts +6 -1
  147. package/dist/spa/tsconfig.app.json +1 -1
  148. package/dist/spa/vite.config.ts +45 -2
  149. package/dist/types/Back.d.ts +151 -14
  150. package/dist/types/Back.d.ts.map +1 -1
  151. package/dist/types/Back.js +15 -0
  152. package/dist/types/Back.js.map +1 -1
  153. package/dist/types/Common.d.ts +123 -29
  154. package/dist/types/Common.d.ts.map +1 -1
  155. package/dist/types/Common.js.map +1 -1
  156. package/dist/types/FrontendAPI.d.ts +32 -18
  157. package/dist/types/FrontendAPI.d.ts.map +1 -1
  158. package/dist/types/FrontendAPI.js.map +1 -1
  159. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  160. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  162. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  163. package/dist/types/adapters/EmailAdapter.d.ts +2 -3
  164. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
  165. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  166. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  167. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  168. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  169. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  170. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  171. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  172. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  173. package/dist/types/adapters/StorageAdapter.d.ts +2 -0
  174. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
  175. package/dist/types/adapters/index.d.ts +3 -0
  176. package/dist/types/adapters/index.d.ts.map +1 -1
  177. package/package.json +4 -2
@@ -1,7 +1,29 @@
1
- import { ADMINFORTH_VERSION, listify } from './utils.js';
1
+ import { Filters, } from "../types/Back.js";
2
+ import { ADMINFORTH_VERSION, listify, getLoginPromptHTML } from './utils.js';
2
3
  import AdminForthAuth from "../auth.js";
3
4
  import { ActionCheckSource, AdminForthDataTypes, AdminForthFilterOperators, AdminForthResourcePages, AllowedActionsEnum } from "../types/Common.js";
4
5
  import { filtersTools } from "../modules/filtersTools.js";
6
+ async function resolveBoolOrFn(val, ctx) {
7
+ if (typeof val === 'function') {
8
+ return !!(await (val)(ctx));
9
+ }
10
+ return !!val;
11
+ }
12
+ async function isBackendOnly(col, ctx) {
13
+ return await resolveBoolOrFn(col.backendOnly, ctx);
14
+ }
15
+ async function isShown(col, page, ctx) {
16
+ const s = col.showIn || {};
17
+ if (s[page] !== undefined)
18
+ return await resolveBoolOrFn(s[page], ctx);
19
+ if (s.all !== undefined)
20
+ return await resolveBoolOrFn(s.all, ctx);
21
+ return true;
22
+ }
23
+ async function isFilledOnCreate(col) {
24
+ const fillOnCreate = !!col.fillOnCreate;
25
+ return fillOnCreate;
26
+ }
5
27
  export async function interpretResource(adminUser, resource, meta, source, adminforth) {
6
28
  if (process.env.HEAVY_DEBUG) {
7
29
  console.log('🪲Interpreting resource', resource.resourceId, source, 'adminUser', adminUser);
@@ -44,7 +66,7 @@ export default class AdminForthRestAPI {
44
66
  constructor(adminforth) {
45
67
  this.adminforth = adminforth;
46
68
  }
47
- async processLoginCallbacks(adminUser, toReturn, response, extra) {
69
+ async processLoginCallbacks(adminUser, toReturn, response, extra, rememberMeDays) {
48
70
  var _a, _b, _c;
49
71
  const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
50
72
  for (const hook of listify(beforeLoginConfirmation)) {
@@ -53,6 +75,7 @@ export default class AdminForthRestAPI {
53
75
  response,
54
76
  adminforth: this.adminforth,
55
77
  extra,
78
+ rememberMeDays
56
79
  });
57
80
  if (((_a = resp === null || resp === void 0 ? void 0 : resp.body) === null || _a === void 0 ? void 0 : _a.redirectTo) || (resp === null || resp === void 0 ? void 0 : resp.error)) {
58
81
  // delete all items from toReturn and add these:
@@ -109,9 +132,11 @@ export default class AdminForthRestAPI {
109
132
  pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
110
133
  username,
111
134
  };
112
- await this.processLoginCallbacks(adminUser, toReturn, response, { body, headers, query, cookies, requestUrl });
135
+ const expireInDays = rememberMe ? this.adminforth.config.auth.rememberMeDays || 30 : 1;
136
+ await this.processLoginCallbacks(adminUser, toReturn, response, {
137
+ body, headers, query, cookies, requestUrl,
138
+ }, expireInDays);
113
139
  if (toReturn.allowedLogin) {
114
- const expireInDays = rememberMe && this.adminforth.config.auth.rememberMeDays;
115
140
  this.adminforth.auth.setAuthCookie({
116
141
  expireInDays,
117
142
  response,
@@ -142,6 +167,17 @@ export default class AdminForthRestAPI {
142
167
  return { ok: true };
143
168
  },
144
169
  });
170
+ server.endpoint({
171
+ noAuth: true,
172
+ method: 'GET',
173
+ path: '/get_login_form_config',
174
+ handler: async ({ tr }) => {
175
+ const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
176
+ return {
177
+ loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
178
+ };
179
+ }
180
+ });
145
181
  server.endpoint({
146
182
  noAuth: true,
147
183
  method: 'GET',
@@ -161,21 +197,24 @@ export default class AdminForthRestAPI {
161
197
  usernameFieldName: usernameColumn.label,
162
198
  loginBackgroundImage: this.adminforth.config.auth.loginBackgroundImage,
163
199
  loginBackgroundPosition: this.adminforth.config.auth.loginBackgroundPosition,
200
+ removeBackgroundBlendMode: this.adminforth.config.auth.removeBackgroundBlendMode,
164
201
  title: (_a = this.adminforth.config.customization) === null || _a === void 0 ? void 0 : _a.title,
165
202
  demoCredentials: this.adminforth.config.auth.demoCredentials,
166
- loginPromptHTML: await tr(this.adminforth.config.auth.loginPromptHTML, 'system.loginPromptHTML'),
167
203
  loginPageInjections: this.adminforth.config.customization.loginPageInjections,
168
204
  globalInjections: {
169
205
  everyPageBottom: this.adminforth.config.customization.globalInjections.everyPageBottom,
206
+ sidebarTop: this.adminforth.config.customization.globalInjections.sidebarTop,
170
207
  },
171
208
  rememberMeDays: this.adminforth.config.auth.rememberMeDays,
209
+ singleTheme: this.adminforth.config.customization.singleTheme,
210
+ customHeadItems: this.adminforth.config.customization.customHeadItems,
172
211
  };
173
212
  },
174
213
  });
175
214
  server.endpoint({
176
215
  method: 'GET',
177
216
  path: '/get_base_config',
178
- handler: async ({ input, adminUser, cookies, tr }) => {
217
+ handler: async ({ input, adminUser, cookies, tr, response }) => {
179
218
  var _a, _b, _c;
180
219
  let username = '';
181
220
  let userFullName = '';
@@ -183,6 +222,10 @@ export default class AdminForthRestAPI {
183
222
  if (!this.adminforth.config.auth) {
184
223
  throw new Error('No config.auth defined');
185
224
  }
225
+ response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
226
+ response.setHeader('Pragma', 'no-cache');
227
+ response.setHeader('Expires', '0');
228
+ response.setHeader('Surrogate-Control', 'no-store');
186
229
  const dbUser = adminUser.dbUser;
187
230
  username = dbUser[this.adminforth.config.auth.usernameField];
188
231
  userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
@@ -228,20 +271,31 @@ export default class AdminForthRestAPI {
228
271
  newMenu.push(newMenuItem);
229
272
  }
230
273
  const announcementBadge = (_b = (_a = this.adminforth.config.customization).announcementBadge) === null || _b === void 0 ? void 0 : _b.call(_a, adminUser);
274
+ const settingPages = [];
275
+ for (const settingPage of this.adminforth.config.auth.userMenuSettingsPages || []) {
276
+ if (settingPage.isVisible) {
277
+ const isVisible = await settingPage.isVisible(adminUser);
278
+ settingPages.push(Object.assign(Object.assign({}, settingPage), { isVisible }));
279
+ }
280
+ }
231
281
  const publicPart = {
232
282
  brandName: this.adminforth.config.customization.brandName,
233
283
  usernameFieldName: usernameColumn.label,
234
284
  loginBackgroundImage: this.adminforth.config.auth.loginBackgroundImage,
235
285
  loginBackgroundPosition: this.adminforth.config.auth.loginBackgroundPosition,
286
+ removeBackgroundBlendMode: this.adminforth.config.auth.removeBackgroundBlendMode,
236
287
  title: (_c = this.adminforth.config.customization) === null || _c === void 0 ? void 0 : _c.title,
237
288
  demoCredentials: this.adminforth.config.auth.demoCredentials,
238
- loginPromptHTML: await tr(this.adminforth.config.auth.loginPromptHTML, 'system.loginPromptHTML'),
239
289
  loginPageInjections: this.adminforth.config.customization.loginPageInjections,
240
290
  rememberMeDays: this.adminforth.config.auth.rememberMeDays,
291
+ singleTheme: this.adminforth.config.customization.singleTheme,
292
+ customHeadItems: this.adminforth.config.customization.customHeadItems,
241
293
  };
242
294
  const loggedInPart = {
243
295
  showBrandNameInSidebar: this.adminforth.config.customization.showBrandNameInSidebar,
296
+ showBrandLogoInSidebar: this.adminforth.config.customization.showBrandLogoInSidebar,
244
297
  brandLogo: this.adminforth.config.customization.brandLogo,
298
+ iconOnlySidebar: this.adminforth.config.customization.iconOnlySidebar,
245
299
  datesFormat: this.adminforth.config.customization.datesFormat,
246
300
  timeFormat: this.adminforth.config.customization.timeFormat,
247
301
  auth: this.adminforth.config.auth,
@@ -251,6 +305,7 @@ export default class AdminForthRestAPI {
251
305
  announcementBadge,
252
306
  globalInjections: this.adminforth.config.customization.globalInjections,
253
307
  userFullnameField: this.adminforth.config.auth.userFullNameField,
308
+ settingPages: settingPages,
254
309
  };
255
310
  // translate menu labels
256
311
  const translateRoutines = [];
@@ -268,19 +323,37 @@ export default class AdminForthRestAPI {
268
323
  if (menuItem.children) {
269
324
  menuItem.children.forEach(processItem);
270
325
  }
326
+ if (menuItem.pageLabel) {
327
+ translateRoutines.push((async () => {
328
+ menuItem.pageLabel = await tr(menuItem.pageLabel, `UserMenu.${menuItem.pageLabel}`);
329
+ })());
330
+ }
271
331
  };
272
332
  newMenu.forEach((menuItem) => {
273
333
  processItem(menuItem);
274
334
  });
335
+ if (this.adminforth.config.auth.userMenuSettingsPages) {
336
+ this.adminforth.config.auth.userMenuSettingsPages.forEach((page) => {
337
+ processItem(page);
338
+ });
339
+ }
275
340
  await Promise.all(translateRoutines);
276
341
  // strip all backendOnly fields or not described in adminForth fields from dbUser
277
342
  // (when user defines column and does not set backendOnly, we assume it is not backendOnly)
278
- Object.keys(adminUser.dbUser).forEach((key) => {
279
- const col = userResource.columns.find((col) => col.name === key);
280
- if (!col || col.backendOnly) {
343
+ const ctx = {
344
+ adminUser,
345
+ resource: userResource,
346
+ meta: {},
347
+ source: ActionCheckSource.ShowRequest,
348
+ adminforth: this.adminforth,
349
+ };
350
+ for (const key of Object.keys(adminUser.dbUser)) {
351
+ const col = userResource.columns.find((c) => c.name === key);
352
+ const bo = col ? await isBackendOnly(col, ctx) : true;
353
+ if (!col || bo) {
281
354
  delete adminUser.dbUser[key];
282
355
  }
283
- });
356
+ }
284
357
  return {
285
358
  user: userData,
286
359
  resources: this.adminforth.config.resources.map((res) => ({
@@ -435,7 +508,7 @@ export default class AdminForthRestAPI {
435
508
  method: 'POST',
436
509
  path: '/get_resource_data',
437
510
  handler: async ({ body, adminUser, headers, query, cookies, requestUrl }) => {
438
- var _a, _b, _c, _d, _e;
511
+ var _a, _b, _c, _d, _e, _f;
439
512
  const { resourceId, source } = body;
440
513
  if (['show', 'list', 'edit'].includes(source) === false) {
441
514
  return { error: 'Invalid source, should be list or show' };
@@ -632,22 +705,38 @@ export default class AdminForthRestAPI {
632
705
  }
633
706
  });
634
707
  }));
708
+ const pkField = (_d = resource.columns.find((col) => col.primaryKey)) === null || _d === void 0 ? void 0 : _d.name;
635
709
  // remove all columns which are not defined in resources, or defined but backendOnly
636
- data.data.forEach((item) => {
637
- Object.keys(item).forEach((key) => {
638
- if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
639
- delete item[key];
710
+ {
711
+ const ctx = {
712
+ adminUser,
713
+ resource,
714
+ meta,
715
+ source: {
716
+ show: ActionCheckSource.ShowRequest,
717
+ list: ActionCheckSource.ListRequest,
718
+ edit: ActionCheckSource.EditLoadRequest,
719
+ }[source],
720
+ adminforth: this.adminforth,
721
+ };
722
+ for (const item of data.data) {
723
+ for (const key of Object.keys(item)) {
724
+ const col = resource.columns.find((c) => c.name === key);
725
+ const bo = col ? await isBackendOnly(col, ctx) : true;
726
+ if (!col || bo) {
727
+ delete item[key];
728
+ }
640
729
  }
641
- });
642
- item._label = resource.recordLabel(item);
643
- });
730
+ item._label = resource.recordLabel(item);
731
+ }
732
+ }
644
733
  if (source === 'list' && resource.options.listTableClickUrl) {
645
734
  await Promise.all(data.data.map(async (item) => {
646
735
  item._clickUrl = await resource.options.listTableClickUrl(item, adminUser);
647
736
  }));
648
737
  }
649
738
  // only after adminforth made all post processing, give user ability to edit it
650
- for (const hook of listify((_e = (_d = resource.hooks) === null || _d === void 0 ? void 0 : _d[hookSource]) === null || _e === void 0 ? void 0 : _e.afterDatasourceResponse)) {
739
+ for (const hook of listify((_f = (_e = resource.hooks) === null || _e === void 0 ? void 0 : _e[hookSource]) === null || _f === void 0 ? void 0 : _f.afterDatasourceResponse)) {
651
740
  const resp = await hook({
652
741
  resource,
653
742
  query: body,
@@ -672,7 +761,7 @@ export default class AdminForthRestAPI {
672
761
  method: 'POST',
673
762
  path: '/get_resource_foreign_data',
674
763
  handler: async ({ body, adminUser, headers, query, cookies, requestUrl }) => {
675
- const { resourceId, column } = body;
764
+ const { resourceId, column, search } = body;
676
765
  if (!this.adminforth.statuses.dbDiscover) {
677
766
  return { error: 'Database discovery not started' };
678
767
  }
@@ -742,6 +831,43 @@ export default class AdminForthRestAPI {
742
831
  throw new Error(`Wrong filter object value: ${JSON.stringify(filters)}`);
743
832
  }
744
833
  }
834
+ if (search && search.trim() && columnConfig.foreignResource.searchableFields) {
835
+ const searchableFields = Array.isArray(columnConfig.foreignResource.searchableFields)
836
+ ? columnConfig.foreignResource.searchableFields
837
+ : [columnConfig.foreignResource.searchableFields];
838
+ const searchOperator = columnConfig.foreignResource.searchIsCaseSensitive
839
+ ? AdminForthFilterOperators.LIKE
840
+ : AdminForthFilterOperators.ILIKE;
841
+ const availableSearchFields = searchableFields.filter((fieldName) => {
842
+ const fieldExists = targetResource.columns.some(col => col.name === fieldName);
843
+ if (!fieldExists) {
844
+ process.env.HEAVY_DEBUG && console.log(`⚠️ Field '${fieldName}' not found in polymorphic target resource '${targetResource.resourceId}', skipping in search filter.`);
845
+ }
846
+ return fieldExists;
847
+ });
848
+ if (availableSearchFields.length === 0) {
849
+ process.env.HEAVY_DEBUG && console.log(`⚠️ No searchable fields available in polymorphic target resource '${targetResource.resourceId}', skipping resource.`);
850
+ resolve({ items: [] });
851
+ return;
852
+ }
853
+ const searchFilters = availableSearchFields.map((fieldName) => {
854
+ const filter = {
855
+ field: fieldName,
856
+ operator: searchOperator,
857
+ value: search.trim(),
858
+ };
859
+ return filter;
860
+ });
861
+ if (searchFilters.length > 1) {
862
+ normalizedFilters.subFilters.push({
863
+ operator: AdminForthFilterOperators.OR,
864
+ subFilters: searchFilters,
865
+ });
866
+ }
867
+ else if (searchFilters.length === 1) {
868
+ normalizedFilters.subFilters.push(searchFilters[0]);
869
+ }
870
+ }
745
871
  const dbDataItems = await this.adminforth.connectors[targetResource.dataSource].getData({
746
872
  resource: targetResource,
747
873
  limit,
@@ -835,6 +961,7 @@ export default class AdminForthRestAPI {
835
961
  return { error };
836
962
  }
837
963
  const { record } = body;
964
+ // todo if showIn.create is function, code below will be buggy (will not detect required fact)
838
965
  for (const column of resource.columns) {
839
966
  if (((_a = column.required) === null || _a === void 0 ? void 0 : _a.create) &&
840
967
  record[column.name] === undefined &&
@@ -842,11 +969,36 @@ export default class AdminForthRestAPI {
842
969
  return { error: `Column '${column.name}' is required`, ok: false };
843
970
  }
844
971
  }
972
+ const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
973
+ if (record[primaryKeyColumn.name] !== undefined) {
974
+ const existingRecord = await this.adminforth.resource(resource.resourceId).get([Filters.EQ(primaryKeyColumn.name, record[primaryKeyColumn.name])]);
975
+ if (existingRecord) {
976
+ return { error: `Record with ${primaryKeyColumn.name} '${record[primaryKeyColumn.name]}' already exists`, ok: false };
977
+ }
978
+ }
979
+ const ctxCreate = {
980
+ adminUser,
981
+ resource,
982
+ meta: { requestBody: body },
983
+ source: ActionCheckSource.CreateRequest,
984
+ adminforth: this.adminforth,
985
+ };
986
+ for (const column of resource.columns) {
987
+ if ((_b = column.required) === null || _b === void 0 ? void 0 : _b.create) {
988
+ const shown = await isShown(column, 'create', ctxCreate);
989
+ if (shown && record[column.name] === undefined) {
990
+ return { error: `Column '${column.name}' is required`, ok: false };
991
+ }
992
+ }
993
+ }
845
994
  for (const column of resource.columns) {
846
995
  const fieldName = column.name;
847
996
  if (fieldName in record) {
848
- if (!((_b = column.showIn) === null || _b === void 0 ? void 0 : _b.create) || column.backendOnly) {
849
- return { error: `Field "${fieldName}" cannot be modified as it is restricted from creation (showIn.create is false, please set it to true)`, ok: false };
997
+ const shown = await isShown(column, 'create', ctxCreate); //
998
+ const bo = await isBackendOnly(column, ctxCreate);
999
+ const filledOnCreate = await isFilledOnCreate(column);
1000
+ if ((!shown && !filledOnCreate) || bo) {
1001
+ return { error: `Field "${fieldName}" cannot be modified as it is restricted from creation (backendOnly or showIn.create is false, please set it to true)`, ok: false };
850
1002
  }
851
1003
  }
852
1004
  }
@@ -890,7 +1042,7 @@ export default class AdminForthRestAPI {
890
1042
  }
891
1043
  const response = await this.adminforth.createResourceRecord({ resource, record, adminUser, extra: { body, query, headers, cookies, requestUrl } });
892
1044
  if (response.error) {
893
- return { error: response.error, ok: false };
1045
+ return { error: response.error, ok: false, newRecordId: response.newRecordId };
894
1046
  }
895
1047
  const connector = this.adminforth.connectors[resource.dataSource];
896
1048
  return {
@@ -903,7 +1055,7 @@ export default class AdminForthRestAPI {
903
1055
  method: 'POST',
904
1056
  path: '/update_record',
905
1057
  handler: async ({ body, adminUser, query, headers, cookies, requestUrl }) => {
906
- var _a, _b, _c;
1058
+ var _a, _b;
907
1059
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
908
1060
  if (!resource) {
909
1061
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -921,21 +1073,37 @@ export default class AdminForthRestAPI {
921
1073
  if (!allowed) {
922
1074
  return { error: allowedError };
923
1075
  }
1076
+ const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
1077
+ if (record[primaryKeyColumn.name] !== undefined) {
1078
+ const existingRecord = await this.adminforth.resource(resource.resourceId).get([Filters.EQ(primaryKeyColumn.name, record[primaryKeyColumn.name])]);
1079
+ if (existingRecord) {
1080
+ return { error: `Record with ${primaryKeyColumn.name} '${record[primaryKeyColumn.name]}' already exists`, ok: false };
1081
+ }
1082
+ }
1083
+ const ctxEdit = {
1084
+ adminUser,
1085
+ resource,
1086
+ meta: { requestBody: body, newRecord: record, oldRecord, pk: recordId },
1087
+ source: ActionCheckSource.EditRequest,
1088
+ adminforth: this.adminforth,
1089
+ };
924
1090
  for (const column of resource.columns) {
925
1091
  const fieldName = column.name;
926
1092
  if (fieldName in record) {
927
- if (!((_a = column.showIn) === null || _a === void 0 ? void 0 : _a.edit) || column.editReadonly || column.backendOnly) {
928
- return { error: `Field "${fieldName}" cannot be modified as it is restricted from editing (showIn.edit is false, please set it to true)`, ok: false };
1093
+ const shown = await isShown(column, 'edit', ctxEdit);
1094
+ const bo = await isBackendOnly(column, ctxEdit);
1095
+ if (!shown || column.editReadonly || bo) {
1096
+ return { error: `Field "${fieldName}" cannot be modified as it is restricted from editing (backendOnly or showIn.edit is false, please set it to true)`, ok: false };
929
1097
  }
930
1098
  }
931
1099
  }
932
1100
  // for polymorphic foreign resources, we need to find out the value for polymorphicOn column
933
1101
  for (const column of resource.columns) {
934
- if (((_b = column.foreignResource) === null || _b === void 0 ? void 0 : _b.polymorphicOn) && record[column.name] === null) {
1102
+ if (((_a = column.foreignResource) === null || _a === void 0 ? void 0 : _a.polymorphicOn) && record[column.name] === null) {
935
1103
  const systemResource = column.foreignResource.polymorphicResources.find(pr => pr.resourceId === null);
936
1104
  record[column.foreignResource.polymorphicOn] = systemResource.whenValue;
937
1105
  }
938
- else if (((_c = column.foreignResource) === null || _c === void 0 ? void 0 : _c.polymorphicOn) && record[column.name]) {
1106
+ else if (((_b = column.foreignResource) === null || _b === void 0 ? void 0 : _b.polymorphicOn) && record[column.name]) {
939
1107
  let newPolymorphicOnValue = null;
940
1108
  if (record[column.name]) {
941
1109
  const targetResources = {};
@@ -1047,7 +1215,7 @@ export default class AdminForthRestAPI {
1047
1215
  method: 'POST',
1048
1216
  path: '/start_custom_action',
1049
1217
  handler: async ({ body, adminUser, tr }) => {
1050
- const { resourceId, actionId, recordId } = body;
1218
+ const { resourceId, actionId, recordId, extra } = body;
1051
1219
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
1052
1220
  if (!resource) {
1053
1221
  return { error: await tr(`Resource {resourceId} not found`, 'errors', { resourceId }) };
@@ -1071,7 +1239,7 @@ export default class AdminForthRestAPI {
1071
1239
  redirectUrl: action.url
1072
1240
  };
1073
1241
  }
1074
- const response = await action.action({ recordId, adminUser, resource, tr, adminforth: this.adminforth });
1242
+ const response = await action.action({ recordId, adminUser, resource, tr, adminforth: this.adminforth, extra });
1075
1243
  return Object.assign({ actionId,
1076
1244
  recordId,
1077
1245
  resourceId }, response);