adminforth 1.3.54-next.9 → 1.3.55-next.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.
Files changed (250) hide show
  1. package/dist/auth.d.ts +31 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +42 -56
  4. package/dist/auth.js.map +1 -0
  5. package/dist/basePlugin.d.ts +18 -0
  6. package/dist/basePlugin.d.ts.map +1 -0
  7. package/dist/basePlugin.js +1 -0
  8. package/dist/basePlugin.js.map +1 -0
  9. package/dist/dataConnectors/baseConnector.d.ts +94 -0
  10. package/dist/dataConnectors/baseConnector.d.ts.map +1 -0
  11. package/dist/dataConnectors/baseConnector.js +108 -122
  12. package/dist/dataConnectors/baseConnector.js.map +1 -0
  13. package/dist/dataConnectors/clickhouse.d.ts +92 -0
  14. package/dist/dataConnectors/clickhouse.d.ts.map +1 -0
  15. package/dist/dataConnectors/clickhouse.js +132 -149
  16. package/dist/dataConnectors/clickhouse.js.map +1 -0
  17. package/dist/dataConnectors/mongo.d.ts +93 -0
  18. package/dist/dataConnectors/mongo.d.ts.map +1 -0
  19. package/dist/dataConnectors/mongo.js +75 -101
  20. package/dist/dataConnectors/mongo.js.map +1 -0
  21. package/dist/dataConnectors/postgres.d.ts +71 -0
  22. package/dist/dataConnectors/postgres.d.ts.map +1 -0
  23. package/dist/dataConnectors/postgres.js +124 -143
  24. package/dist/dataConnectors/postgres.js.map +1 -0
  25. package/dist/dataConnectors/sqlite.d.ts +67 -0
  26. package/dist/dataConnectors/sqlite.d.ts.map +1 -0
  27. package/dist/dataConnectors/sqlite.js +113 -130
  28. package/dist/dataConnectors/sqlite.js.map +1 -0
  29. package/dist/index.d.ts +92 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +197 -217
  32. package/dist/index.js.map +1 -0
  33. package/dist/modules/codeInjector.d.ts +35 -0
  34. package/dist/modules/codeInjector.d.ts.map +1 -0
  35. package/dist/modules/codeInjector.js +480 -486
  36. package/dist/modules/codeInjector.js.map +1 -0
  37. package/dist/modules/configValidator.d.ts +12 -0
  38. package/dist/modules/configValidator.d.ts.map +1 -0
  39. package/dist/modules/configValidator.js +31 -22
  40. package/dist/modules/configValidator.js.map +1 -0
  41. package/dist/modules/operationalResource.d.ts +17 -0
  42. package/dist/modules/operationalResource.d.ts.map +1 -0
  43. package/dist/modules/operationalResource.js +50 -70
  44. package/dist/modules/operationalResource.js.map +1 -0
  45. package/dist/modules/restApi.d.ts +10 -0
  46. package/dist/modules/restApi.d.ts.map +1 -0
  47. package/dist/modules/restApi.js +104 -116
  48. package/dist/modules/restApi.js.map +1 -0
  49. package/dist/modules/styleGenerator.d.ts +9 -0
  50. package/dist/modules/styleGenerator.d.ts.map +1 -0
  51. package/dist/modules/styleGenerator.js +1 -0
  52. package/dist/modules/styleGenerator.js.map +1 -0
  53. package/dist/modules/styles.d.ts +96 -0
  54. package/dist/modules/styles.d.ts.map +1 -0
  55. package/dist/modules/styles.js +1 -0
  56. package/dist/modules/styles.js.map +1 -0
  57. package/dist/modules/utils.d.ts +39 -0
  58. package/dist/modules/utils.d.ts.map +1 -0
  59. package/dist/modules/utils.js +1 -0
  60. package/dist/modules/utils.js.map +1 -0
  61. package/dist/plugins/audit-log/types.d.ts +35 -0
  62. package/dist/plugins/audit-log/types.d.ts.map +1 -0
  63. package/dist/plugins/audit-log/types.js +2 -0
  64. package/dist/plugins/audit-log/types.js.map +1 -0
  65. package/dist/plugins/chat-gpt/types.d.ts +82 -0
  66. package/dist/plugins/chat-gpt/types.d.ts.map +1 -0
  67. package/dist/plugins/chat-gpt/types.js +2 -0
  68. package/dist/plugins/chat-gpt/types.js.map +1 -0
  69. package/dist/plugins/email-password-reset/types.d.ts +28 -0
  70. package/dist/plugins/email-password-reset/types.d.ts.map +1 -0
  71. package/dist/plugins/email-password-reset/types.js +2 -0
  72. package/dist/plugins/email-password-reset/types.js.map +1 -0
  73. package/dist/plugins/foreign-inline-list/types.d.ts +19 -0
  74. package/dist/plugins/foreign-inline-list/types.d.ts.map +1 -0
  75. package/dist/plugins/foreign-inline-list/types.js +2 -0
  76. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  77. package/dist/plugins/import-export/types.d.ts +3 -0
  78. package/dist/plugins/import-export/types.d.ts.map +1 -0
  79. package/dist/plugins/import-export/types.js +2 -0
  80. package/dist/plugins/import-export/types.js.map +1 -0
  81. package/dist/plugins/rich-editor/custom/async-queue.d.ts +8 -0
  82. package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +1 -0
  83. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  84. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  85. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +8 -0
  86. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +1 -0
  87. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  88. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  89. package/dist/plugins/rich-editor/types.d.ts +153 -0
  90. package/dist/plugins/rich-editor/types.d.ts.map +1 -0
  91. package/dist/plugins/rich-editor/types.js +16 -0
  92. package/dist/plugins/rich-editor/types.js.map +1 -0
  93. package/dist/plugins/two-factors-auth/types.d.ts +18 -0
  94. package/dist/plugins/two-factors-auth/types.d.ts.map +1 -0
  95. package/dist/plugins/two-factors-auth/types.js +2 -0
  96. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  97. package/dist/plugins/upload/types.d.ts +132 -0
  98. package/dist/plugins/upload/types.d.ts.map +1 -0
  99. package/dist/plugins/upload/types.js +2 -0
  100. package/dist/plugins/upload/types.js.map +1 -0
  101. package/dist/servers/express.d.ts +18 -0
  102. package/dist/servers/express.d.ts.map +1 -0
  103. package/dist/servers/express.js +30 -42
  104. package/dist/servers/express.js.map +1 -0
  105. package/dist/types/AdminForthConfig.d.ts +1619 -0
  106. package/dist/types/AdminForthConfig.d.ts.map +1 -0
  107. package/dist/types/AdminForthConfig.js +1 -0
  108. package/dist/types/AdminForthConfig.js.map +1 -0
  109. package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
  110. package/dist/types/FrontendAPI.d.ts.map +1 -0
  111. package/dist/types/FrontendAPI.js +1 -0
  112. package/dist/types/FrontendAPI.js.map +1 -0
  113. package/package.json +15 -5
  114. package/auth.ts +0 -140
  115. package/basePlugin.ts +0 -70
  116. package/dataConnectors/baseConnector.ts +0 -216
  117. package/dataConnectors/clickhouse.ts +0 -338
  118. package/dataConnectors/mongo.ts +0 -202
  119. package/dataConnectors/postgres.ts +0 -306
  120. package/dataConnectors/sqlite.ts +0 -254
  121. package/dist/spa/.eslintrc.cjs +0 -14
  122. package/dist/spa/README.md +0 -39
  123. package/dist/spa/env.d.ts +0 -1
  124. package/dist/spa/index.html +0 -23
  125. package/dist/spa/package-lock.json +0 -4573
  126. package/dist/spa/package.json +0 -49
  127. package/dist/spa/postcss.config.js +0 -6
  128. package/dist/spa/public/assets/favicon.png +0 -0
  129. package/dist/spa/src/App.vue +0 -314
  130. package/dist/spa/src/assets/base.css +0 -2
  131. package/dist/spa/src/assets/logo.svg +0 -19
  132. package/dist/spa/src/components/AcceptModal.vue +0 -45
  133. package/dist/spa/src/components/Breadcrumbs.vue +0 -41
  134. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  135. package/dist/spa/src/components/CustomDatePicker.vue +0 -176
  136. package/dist/spa/src/components/CustomDateRangePicker.vue +0 -218
  137. package/dist/spa/src/components/CustomRangePicker.vue +0 -152
  138. package/dist/spa/src/components/Dropdown.vue +0 -168
  139. package/dist/spa/src/components/Filters.vue +0 -222
  140. package/dist/spa/src/components/HelloWorld.vue +0 -17
  141. package/dist/spa/src/components/MenuLink.vue +0 -24
  142. package/dist/spa/src/components/ResourceForm.vue +0 -294
  143. package/dist/spa/src/components/ResourceListTable.vue +0 -394
  144. package/dist/spa/src/components/SingleSkeletLoader.vue +0 -13
  145. package/dist/spa/src/components/SkeleteLoader.vue +0 -23
  146. package/dist/spa/src/components/Toast.vue +0 -77
  147. package/dist/spa/src/components/ValueRenderer.vue +0 -66
  148. package/dist/spa/src/components/icons/IconCalendar.vue +0 -5
  149. package/dist/spa/src/components/icons/IconCommunity.vue +0 -7
  150. package/dist/spa/src/components/icons/IconDocumentation.vue +0 -7
  151. package/dist/spa/src/components/icons/IconEcosystem.vue +0 -7
  152. package/dist/spa/src/components/icons/IconSupport.vue +0 -7
  153. package/dist/spa/src/components/icons/IconTime.vue +0 -5
  154. package/dist/spa/src/components/icons/IconTooling.vue +0 -19
  155. package/dist/spa/src/composables/useFrontendApi.ts +0 -26
  156. package/dist/spa/src/composables/useStores.ts +0 -127
  157. package/dist/spa/src/index.scss +0 -27
  158. package/dist/spa/src/main.ts +0 -18
  159. package/dist/spa/src/router/index.ts +0 -63
  160. package/dist/spa/src/spa_types/core.ts +0 -51
  161. package/dist/spa/src/stores/core.ts +0 -144
  162. package/dist/spa/src/stores/filters.ts +0 -22
  163. package/dist/spa/src/stores/modal.ts +0 -48
  164. package/dist/spa/src/stores/toast.ts +0 -15
  165. package/dist/spa/src/stores/user.ts +0 -72
  166. package/dist/spa/src/types/AdminForthConfig.ts +0 -1477
  167. package/dist/spa/src/types/FrontendAPI.ts +0 -121
  168. package/dist/spa/src/utils.ts +0 -103
  169. package/dist/spa/src/views/CreateView.vue +0 -156
  170. package/dist/spa/src/views/EditView.vue +0 -157
  171. package/dist/spa/src/views/ListView.vue +0 -258
  172. package/dist/spa/src/views/LoginView.vue +0 -160
  173. package/dist/spa/src/views/ResourceParent.vue +0 -17
  174. package/dist/spa/src/views/ShowView.vue +0 -184
  175. package/dist/spa/tailwind.config.js +0 -17
  176. package/dist/spa/tsconfig.app.json +0 -14
  177. package/dist/spa/tsconfig.json +0 -11
  178. package/dist/spa/tsconfig.node.json +0 -19
  179. package/dist/spa/vite.config.ts +0 -56
  180. package/index.ts +0 -428
  181. package/modules/codeInjector.ts +0 -736
  182. package/modules/configValidator.ts +0 -571
  183. package/modules/operationalResource.ts +0 -98
  184. package/modules/restApi.ts +0 -718
  185. package/modules/styleGenerator.ts +0 -55
  186. package/modules/styles.ts +0 -126
  187. package/modules/utils.ts +0 -472
  188. package/servers/express.ts +0 -259
  189. package/spa/.eslintrc.cjs +0 -14
  190. package/spa/README.md +0 -39
  191. package/spa/env.d.ts +0 -1
  192. package/spa/index.html +0 -23
  193. package/spa/package-lock.json +0 -4602
  194. package/spa/package.json +0 -51
  195. package/spa/postcss.config.js +0 -6
  196. package/spa/public/assets/favicon.png +0 -0
  197. package/spa/src/App.vue +0 -418
  198. package/spa/src/assets/base.css +0 -2
  199. package/spa/src/assets/logo.svg +0 -19
  200. package/spa/src/components/AcceptModal.vue +0 -45
  201. package/spa/src/components/Breadcrumbs.vue +0 -41
  202. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  203. package/spa/src/components/CustomDatePicker.vue +0 -176
  204. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  205. package/spa/src/components/CustomRangePicker.vue +0 -156
  206. package/spa/src/components/Dropdown.vue +0 -168
  207. package/spa/src/components/Filters.vue +0 -222
  208. package/spa/src/components/HelloWorld.vue +0 -17
  209. package/spa/src/components/MenuLink.vue +0 -27
  210. package/spa/src/components/ResourceForm.vue +0 -290
  211. package/spa/src/components/ResourceListTable.vue +0 -466
  212. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  213. package/spa/src/components/SkeleteLoader.vue +0 -23
  214. package/spa/src/components/ThreeDotsMenu.vue +0 -43
  215. package/spa/src/components/Toast.vue +0 -78
  216. package/spa/src/components/ValueRenderer.vue +0 -114
  217. package/spa/src/components/icons/IconCalendar.vue +0 -5
  218. package/spa/src/components/icons/IconCommunity.vue +0 -7
  219. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  220. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  221. package/spa/src/components/icons/IconSupport.vue +0 -7
  222. package/spa/src/components/icons/IconTime.vue +0 -5
  223. package/spa/src/components/icons/IconTooling.vue +0 -19
  224. package/spa/src/composables/useFrontendApi.ts +0 -26
  225. package/spa/src/composables/useStores.ts +0 -131
  226. package/spa/src/index.scss +0 -31
  227. package/spa/src/main.ts +0 -18
  228. package/spa/src/renderers/CompactUUID.vue +0 -48
  229. package/spa/src/renderers/CountryFlag.vue +0 -69
  230. package/spa/src/router/index.ts +0 -59
  231. package/spa/src/spa_types/core.ts +0 -53
  232. package/spa/src/stores/core.ts +0 -148
  233. package/spa/src/stores/filters.ts +0 -27
  234. package/spa/src/stores/modal.ts +0 -48
  235. package/spa/src/stores/toast.ts +0 -31
  236. package/spa/src/stores/user.ts +0 -72
  237. package/spa/src/utils.ts +0 -160
  238. package/spa/src/views/CreateView.vue +0 -167
  239. package/spa/src/views/EditView.vue +0 -170
  240. package/spa/src/views/ListView.vue +0 -352
  241. package/spa/src/views/LoginView.vue +0 -192
  242. package/spa/src/views/ResourceParent.vue +0 -17
  243. package/spa/src/views/ShowView.vue +0 -186
  244. package/spa/tailwind.config.js +0 -17
  245. package/spa/tsconfig.app.json +0 -14
  246. package/spa/tsconfig.json +0 -11
  247. package/spa/tsconfig.node.json +0 -19
  248. package/spa/vite.config.ts +0 -56
  249. package/tsconfig.json +0 -112
  250. package/types/AdminForthConfig.ts +0 -1762
@@ -1,571 +0,0 @@
1
- import {
2
- AdminForthConfig,
3
- AdminForthResource,
4
- IAdminForth, IConfigValidator,
5
- AdminForthComponentDeclaration ,
6
- AdminForthResourcePages, AllowedActionsEnum,
7
- type AdminForthComponentDeclarationFull,
8
- type AfterSaveFunction,
9
- AdminForthBulkAction,
10
- } from "../types/AdminForthConfig.js";
11
-
12
- import fs from 'fs';
13
- import path from 'path';
14
- import { guessLabelFromName, suggestIfTypo } from './utils.js';
15
-
16
- import crypto from 'crypto';
17
-
18
-
19
-
20
-
21
- export default class ConfigValidator implements IConfigValidator {
22
-
23
- constructor(private adminforth: IAdminForth, private config: AdminForthConfig) {
24
- this.adminforth = adminforth;
25
- this.config = config;
26
- }
27
-
28
- validateAndListifyInjection(obj, key, errors) {
29
- if (!Array.isArray(obj[key])) {
30
- // not array
31
- obj[key] = [obj[key]];
32
- }
33
- obj[key].forEach((target, i) => {
34
- obj[key][i] = this.validateComponent(target, errors);
35
- });
36
- }
37
-
38
- checkCustomFileExists(filePath: string): Array<string> {
39
- if (filePath.startsWith('@@/')) {
40
- const checkPath = path.join(this.config.customization.customComponentsDir, filePath.replace('@@/', ''));
41
- if (!fs.existsSync(checkPath)) {
42
- return [`File file ${filePath} does not exist in ${this.config.customization.customComponentsDir}`];
43
- }
44
- }
45
- return [];
46
- }
47
-
48
- validateComponent(component: AdminForthComponentDeclaration, errors: Array<string>): AdminForthComponentDeclaration {
49
-
50
- if (!component) {
51
- return component;
52
- }
53
- let obj: AdminForthComponentDeclarationFull;
54
- if (typeof component === 'string') {
55
- obj = { file: component, meta: {} };
56
- } else {
57
- obj = component;
58
- }
59
-
60
- let ignoreExistsCheck = false;
61
- if (
62
- this.adminforth.codeInjector.allComponentNames.hasOwnProperty(
63
- (component as AdminForthComponentDeclarationFull).file)
64
- ) {
65
- // not obvious, but if we are in this if, it means that this is plugin component
66
- // if component is plugin component, we don't need to check if it exists in users folder
67
- ignoreExistsCheck = true;
68
- }
69
-
70
-
71
- if (!ignoreExistsCheck) {
72
- errors.push(...this.checkCustomFileExists(obj.file));
73
- }
74
-
75
- return obj;
76
- }
77
-
78
- validateConfig() {
79
- const errors = [];
80
-
81
- if (!this.config.customization.customComponentsDir) {
82
- this.config.customization.customComponentsDir = './custom';
83
- }
84
-
85
- try {
86
- // check customComponentsDir exists
87
- fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
88
- } catch (e) {
89
- this.config.customization.customComponentsDir = undefined;
90
- }
91
-
92
- if (!this.config.customization) {
93
- this.config.customization = {};
94
- }
95
-
96
- if (!this.config.customization.customComponentsDir) {
97
- this.config.customization.customComponentsDir = './custom';
98
- }
99
-
100
- try {
101
- // check customComponentsDir exists
102
- fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
103
- } catch (e) {
104
- this.config.customization.customComponentsDir = undefined;
105
- }
106
-
107
- if (this.config.customization.customPages) {
108
- this.config.customization.customPages.forEach((page, i) => {
109
- this.validateComponent(page.component, errors);
110
- });
111
- } else {
112
- this.config.customization.customPages = [];
113
- }
114
- if (!this.config.baseUrl) {
115
- this.config.baseUrl = '';
116
- }
117
- if (!this.config.baseUrl.endsWith('/')) {
118
- this.adminforth.baseUrlSlashed = this.config.baseUrl + '/';
119
- } else {
120
- this.adminforth.baseUrlSlashed = this.config.baseUrl;
121
- }
122
- if (this.config?.customization.brandName === undefined) {
123
- this.config.customization.brandName = 'AdminForth';
124
- }
125
- if (this.config.customization.loginPageInjections === undefined) {
126
- this.config.customization.loginPageInjections = {};
127
- }
128
- if (this.config.customization.globalInjections === undefined) {
129
- this.config.customization.globalInjections = {};
130
- }
131
- if (this.config.customization.loginPageInjections.underInputs === undefined) {
132
- this.config.customization.loginPageInjections.underInputs = [];
133
- }
134
- if (this.config.customization.brandLogo) {
135
- errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
136
- }
137
- if (this.config.customization.showBrandNameInSidebar === undefined) {
138
- this.config.customization.showBrandNameInSidebar = true;
139
- }
140
- if (this.config.customization.favicon) {
141
- errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
142
- }
143
-
144
- if (!this.config.customization.datesFormat) {
145
- this.config.customization.datesFormat = 'MMM D, YYYY';
146
- }
147
-
148
- if (!this.config.customization.timeFormat) {
149
- this.config.customization.timeFormat = 'HH:mm:ss';
150
- }
151
-
152
- if (this.config.resources) {
153
- this.config.resources.forEach((res: AdminForthResource) => {
154
- if (!res.table) {
155
- errors.push(`Resource "${res.dataSource}" is missing table`);
156
- }
157
- // if recordLabel is not callable, throw error
158
- if (res.recordLabel && typeof res.recordLabel !== 'function') {
159
- errors.push(`Resource "${res.dataSource}" recordLabel is not a function`);
160
- }
161
- if (!res.recordLabel) {
162
- res.recordLabel = (item) => {
163
- const pkVal = item[res.columns.find((col) => col.primaryKey).name];
164
- return `${res.label} ${pkVal}`;
165
- }
166
- }
167
-
168
-
169
- res.resourceId = res.resourceId || res.table;
170
- // as fallback value, capitalize and then replace _ with space
171
- res.label = res.label || res.resourceId.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
172
- if (!res.dataSource) {
173
- errors.push(`Resource "${res.resourceId}" is missing dataSource`);
174
- }
175
- if (!res.columns) {
176
- res.columns = [];
177
- }
178
- res.columns.forEach((col) => {
179
- col.label = col.label || guessLabelFromName(col.name);
180
- //define default sortable
181
- if (!Object.keys(col).includes('sortable')) { col.sortable = true; }
182
- if (col.showIn && !Array.isArray(col.showIn)) {
183
- errors.push(`Resource "${res.resourceId}" column "${col.name}" showIn must be an array`);
184
- }
185
-
186
- // check col.required is string or object
187
- if (col.required && !((typeof col.required === 'boolean') || (typeof col.required === 'object'))) {
188
- errors.push(`Resource "${res.resourceId}" column "${col.name}" required must be a string or object`);
189
- }
190
-
191
- // if it is object check the keys are one of ['create', 'edit']
192
- if (typeof col.required === 'object') {
193
- const wrongRequiredOn = Object.keys(col.required).find((c) => !['create', 'edit'].includes(c));
194
- if (wrongRequiredOn) {
195
- errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid required value "${wrongRequiredOn}", allowed keys are 'create', 'edit']`);
196
- }
197
- }
198
-
199
- // same for editingNote
200
- if (col.editingNote && !((typeof col.editingNote === 'string') || (typeof col.editingNote === 'object'))) {
201
- errors.push(`Resource "${res.resourceId}" column "${col.name}" editingNote must be a string or object`);
202
- }
203
- if (typeof col.editingNote === 'object') {
204
- const wrongEditingNoteOn = Object.keys(col.editingNote).find((c) => !['create', 'edit'].includes(c));
205
- if (wrongEditingNoteOn) {
206
- errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid editingNote value "${wrongEditingNoteOn}", allowed keys are 'create', 'edit']`);
207
- }
208
- }
209
-
210
- const wrongShowIn = col.showIn && col.showIn.find((c) => AdminForthResourcePages[c] === undefined);
211
- if (wrongShowIn) {
212
- errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid showIn value "${wrongShowIn}", allowed values are ${Object.keys(AdminForthResourcePages).join(', ')}`);
213
- }
214
- col.showIn = col.showIn || Object.values(AdminForthResourcePages);
215
-
216
- if (col.foreignResource) {
217
-
218
- if (!col.foreignResource.resourceId) {
219
- errors.push(`Resource "${res.resourceId}" column "${col.name}" has foreignResource without resourceId`);
220
- }
221
- const resource = this.config.resources.find((r) => r.resourceId === col.foreignResource.resourceId);
222
- if (!resource) {
223
- const similar = suggestIfTypo(this.config.resources.map((r) => r.resourceId), col.foreignResource.resourceId);
224
- errors.push(`Resource "${res.resourceId}" column "${col.name}" has foreignResource resourceId which is not in resources: "${col.foreignResource.resourceId}".
225
- ${similar ? `Did you mean "${similar}" instead of "${col.foreignResource.resourceId}"?` : ''}`);
226
- }
227
- const befHook = col.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest;
228
- if (befHook) {
229
- if (!Array.isArray(befHook)) {
230
- col.foreignResource.hooks.dropdownList.beforeDatasourceRequest = [befHook];
231
- }
232
- }
233
- const aftHook = col.foreignResource.hooks?.dropdownList?.afterDatasourceResponse;
234
- if (aftHook) {
235
- if (!Array.isArray(aftHook)) {
236
- col.foreignResource.hooks.dropdownList.afterDatasourceResponse = [aftHook];
237
- }
238
- }
239
- }
240
- })
241
-
242
- if (!res.options) {
243
- res.options = { bulkActions: [], allowedActions: {} };
244
- }
245
-
246
- if (!res.options.allowedActions) {
247
- res.options.allowedActions = {
248
- all: true,
249
- };
250
- }
251
-
252
-
253
- if (Object.keys(res.options.allowedActions).includes('all')) {
254
- if (Object.keys(res.options.allowedActions).length > 1) {
255
- errors.push(`Resource "${res.resourceId}" allowedActions cannot have "all" and other keys at same time: ${Object.keys(res.options.allowedActions).join(', ')}`);
256
- }
257
- for (const key of Object.keys(AllowedActionsEnum)) {
258
- if (key !== 'all') {
259
- res.options.allowedActions[key] = res.options.allowedActions.all;
260
- }
261
- }
262
- delete res.options.allowedActions.all;
263
- } else {
264
- // by default allow all actions
265
- for (const key of Object.keys(AllowedActionsEnum)) {
266
- if (!Object.keys(res.options.allowedActions).includes(key)) {
267
- res.options.allowedActions[key] = true;
268
- }
269
- }
270
- }
271
-
272
-
273
- //check if resource has bulkActions
274
- let bulkActions: AdminForthBulkAction[] = res?.options?.bulkActions || [];
275
-
276
- if (!Array.isArray(bulkActions)) {
277
- errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
278
- bulkActions = [];
279
- }
280
-
281
- if (!bulkActions.find((action) => action.label === 'Delete checked')) {
282
- bulkActions.push({
283
- label: `Delete checked`,
284
- state: 'danger',
285
- icon: 'flowbite:trash-bin-outline',
286
- confirm: 'Are you sure you want to delete selected items?',
287
- allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
288
- action: async ({ selectedIds, adminUser }) => {
289
- const connector = this.adminforth.connectors[res.dataSource];
290
-
291
- // for now if at least one error, stop and return error
292
- let error = null;
293
-
294
- await Promise.all(
295
- selectedIds.map(async (recordId) => {
296
- const record = await connector.getRecordByPrimaryKey(res, recordId);
297
-
298
- await Promise.all(
299
- (res.hooks.delete.beforeSave as AfterSaveFunction[]).map(
300
- async (hook) => {
301
- const resp = await hook({
302
- recordId: recordId,
303
- resource: res,
304
- record,
305
- adminUser,
306
- });
307
- if (!error && resp.error) {
308
- error = resp.error;
309
- }
310
- }
311
- )
312
- )
313
-
314
- if (error) {
315
- return;
316
- }
317
-
318
- await connector.deleteRecord({ resource: res, recordId });
319
- // call afterDelete hook
320
- await Promise.all(
321
- (res.hooks.delete.afterSave as AfterSaveFunction[]).map(
322
- async (hook) => {
323
- await hook({
324
- resource: res,
325
- record,
326
- adminUser,
327
- recordId: recordId
328
- });
329
- }
330
- )
331
- )
332
-
333
- })
334
- );
335
-
336
- if (error) {
337
- return { error, ok: false };
338
- }
339
- return { ok: true, successMessage: `${selectedIds.length} item${selectedIds.length > 1 ? 's' : ''} deleted` };
340
- }
341
- });
342
- }
343
-
344
- bulkActions.map((action) => {
345
- if (!action.id) {
346
- action.id = crypto.createHash('sha256').update(
347
- action.label,
348
- ).digest('hex');
349
- }
350
- });
351
- res.options.bulkActions = bulkActions;
352
-
353
- // if pageInjection is a string, make array with one element. Also check file exists
354
- const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems', 'customActionIcons'];
355
- const possiblePages = ['list', 'show', 'create', 'edit'];
356
-
357
-
358
-
359
-
360
- if (res.options.pageInjections) {
361
-
362
- Object.entries(res.options.pageInjections).map(([key, value]) => {
363
- if (!possiblePages.includes(key)) {
364
- const similar = suggestIfTypo(possiblePages, key);
365
- errors.push(`Resource "${res.resourceId}" has invalid pageInjection key "${key}", allowed keys are ${possiblePages.join(', ')}. ${similar ? `Did you mean "${similar}"?` : ''}`);
366
- }
367
-
368
- Object.entries(value).map(([injection, target]) => {
369
- if (possibleInjections.includes(injection)) {
370
- this.validateAndListifyInjection(res.options.pageInjections[key], injection, errors);
371
- } else {
372
- const similar = suggestIfTypo(possibleInjections, injection);
373
- errors.push(`Resource "${res.resourceId}" has invalid pageInjection key "${injection}", Supported keys are ${possibleInjections.join(', ')} ${similar ? `Did you mean "${similar}"?` : ''}`);
374
- }
375
- });
376
-
377
- })
378
-
379
- }
380
-
381
- // transform all hooks Functions to array of functions
382
- if (!res.hooks) {
383
- res.hooks = {};
384
- }
385
- for (const hookName of ['show', 'list']) {
386
- if (!res.hooks[hookName]) {
387
- res.hooks[hookName] = {};
388
- }
389
- if (!res.hooks[hookName].beforeDatasourceRequest) {
390
- res.hooks[hookName].beforeDatasourceRequest = [];
391
- }
392
-
393
- if (!Array.isArray(res.hooks[hookName].beforeDatasourceRequest)) {
394
- res.hooks[hookName].beforeDatasourceRequest = [res.hooks[hookName].beforeDatasourceRequest];
395
- }
396
-
397
- if (!res.hooks[hookName].afterDatasourceResponse) {
398
- res.hooks[hookName].afterDatasourceResponse = [];
399
- }
400
-
401
- if (!Array.isArray(res.hooks[hookName].afterDatasourceResponse)) {
402
- res.hooks[hookName].afterDatasourceResponse = [res.hooks[hookName].afterDatasourceResponse];
403
- }
404
- }
405
- for (const hookName of ['create', 'edit', 'delete']) {
406
- if (!res.hooks[hookName]) {
407
- res.hooks[hookName] = {};
408
- }
409
- if (!res.hooks[hookName].beforeSave) {
410
- res.hooks[hookName].beforeSave = [];
411
- }
412
- if (!Array.isArray(res.hooks[hookName].beforeSave)) {
413
- res.hooks[hookName].beforeSave = [res.hooks[hookName].beforeSave];
414
- }
415
- if (!res.hooks[hookName].afterSave) {
416
- res.hooks[hookName].afterSave = [];
417
- }
418
- if (!Array.isArray(res.hooks[hookName].afterSave)) {
419
- res.hooks[hookName].afterSave = [res.hooks[hookName].afterSave];
420
- }
421
- }
422
- });
423
-
424
- if (this.config.customization.globalInjections) {
425
- const ALLOWED_GLOBAL_INJECTIONS = ['userMenu', 'header', 'sidebar',]
426
- Object.keys(this.config.customization.globalInjections).forEach((injection) => {
427
- if (ALLOWED_GLOBAL_INJECTIONS.includes(injection)) {
428
- this.validateAndListifyInjection(this.config.customization.globalInjections, injection, errors);
429
- } else {
430
- const similar = suggestIfTypo(ALLOWED_GLOBAL_INJECTIONS, injection);
431
- errors.push(`Global injection key "${injection}" is not allowed. Allowed keys are ${ALLOWED_GLOBAL_INJECTIONS.join(', ')}. ${similar ? `Did you mean "${similar}"?` : ''}`);
432
- }
433
- });
434
- }
435
-
436
- if (!this.config.menu) {
437
- errors.push('No config.menu defined');
438
- }
439
-
440
- // check if there is only one homepage: true in menu, recursivly
441
- let homepages = 0;
442
- const browseMenu = (menu) => {
443
- menu.forEach((item) => {
444
- if (item.component && item.resourceId) {
445
- errors.push(`Menu item cannot have both component and resourceId: ${JSON.stringify(item)}`);
446
- }
447
- if (item.component && !item.path) {
448
- errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
449
- }
450
-
451
- if (item.type === 'resource' && !item.resourceId) {
452
- errors.push(`Menu item with type 'resource' must have resourceId : ${JSON.stringify(item)}`);
453
- }
454
-
455
- if (item.resourceId && !this.config.resources.find((res) => res.resourceId === item.resourceId)) {
456
- const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), item.resourceId);
457
- errors.push(`Menu item with type 'resourceId' has resourceId which is not in resources: "${JSON.stringify(item)}".
458
- ${similar ? `Did you mean "${similar}" instead of "${item.resourceId}"?` : ''}`);
459
- }
460
-
461
- if (item.type === 'component' && !item.component) {
462
- errors.push(`Menu item with type 'component' must have component : ${JSON.stringify(item)}`);
463
- }
464
-
465
- // make sure component starts with @@
466
- if (item.component) {
467
- if (!item.component.startsWith('@@')) {
468
- errors.push(`Menu item component must start with @@ : ${JSON.stringify(item)}`);
469
- }
470
-
471
- const path = item.component.replace('@@', this.config.customization.customComponentsDir);
472
- if (!fs.existsSync(path)) {
473
- errors.push(`Menu item component "${item.component.replace('@@', '')}" does not exist in "${this.config.customization.customComponentsDir}"`);
474
- }
475
- }
476
-
477
- if (item.homepage) {
478
- homepages++;
479
- if (homepages > 1) {
480
- errors.push('There must be only one homepage: true in menu, found second one in ' + JSON.stringify(item));
481
- }
482
- }
483
- if (item.children) {
484
- browseMenu(item.children);
485
- }
486
- });
487
- };
488
- browseMenu(this.config.menu);
489
-
490
- }
491
-
492
- if (this.config.auth) {
493
- // TODO: remove in future releases
494
- if (!this.config.auth.usersResourceId && this.config.auth.resourceId) {
495
- this.config.auth.usersResourceId = this.config.auth.resourceId;
496
- }
497
-
498
- if (!this.config.auth.usersResourceId) {
499
- throw new Error('No config.auth.usersResourceId defined');
500
- }
501
- if (!this.config.auth.passwordHashField) {
502
- throw new Error('No config.auth.passwordHashField defined');
503
- }
504
- if (!this.config.auth.usernameField) {
505
- throw new Error('No config.auth.usernameField defined');
506
- }
507
- if (this.config.auth.loginBackgroundImage) {
508
- errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
509
- }
510
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
511
- if (!userResource) {
512
- const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId ), this.config.auth.usersResourceId);
513
- throw new Error(`Resource with id "${this.config.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
514
- }
515
-
516
- if (!this.config.auth.beforeLoginConfirmation) {
517
- this.config.auth.beforeLoginConfirmation = [];
518
- }
519
- }
520
-
521
- // check for duplicate resourceIds and show which ones are duplicated
522
- const resourceIds = this.config.resources.map((res) => res.resourceId);
523
- const uniqueResourceIds = new Set(resourceIds);
524
- if (uniqueResourceIds.size != resourceIds.length) {
525
- const duplicates = resourceIds.filter((item, index) => resourceIds.indexOf(item) != index);
526
- errors.push(`Duplicate fields "resourceId" or "table": ${duplicates.join(', ')}`);
527
- }
528
-
529
- //add ids for onSelectedAllActions for each resource
530
- if (errors.length > 0) {
531
- throw new Error(`Invalid AdminForth config: ${errors.join(', ')}`);
532
- }
533
-
534
- // check is all custom components files exists
535
- for (const resource of this.config.resources) {
536
- for (const column of resource.columns) {
537
- if (column.components) {
538
-
539
- for (const [key, comp] of Object.entries(column.components as Record<string, AdminForthComponentDeclarationFull>)) {
540
-
541
- column.components[key] = this.validateComponent(comp, errors);
542
- }
543
- }
544
- }
545
- }
546
- }
547
-
548
- postProcessAfterDiscover(resource: AdminForthResource) {
549
- resource.columns.forEach((column) => {
550
- // if db/user says column is required in boolean, expand
551
- if (typeof column.required === 'boolean') {
552
- column.required = { create: column.required, edit: column.required };
553
- }
554
-
555
- if (!column.required) {
556
- column.required = { create: false, edit: false };
557
- }
558
-
559
- // same for editingNote
560
- if (typeof column.editingNote === 'string') {
561
- column.editingNote = { create: column.editingNote, edit: column.editingNote };
562
- }
563
- })
564
- resource.dataSourceColumns = resource.columns.filter((col) => !col.virtual);
565
- (resource.plugins || []).forEach((plugin) => {
566
- if (plugin.validateConfigAfterDiscover) {
567
- plugin.validateConfigAfterDiscover(this.adminforth, resource);
568
- }
569
- });
570
- }
571
- }
@@ -1,98 +0,0 @@
1
- import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource } from '../types/AdminForthConfig.js';
2
-
3
-
4
- function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): IAdminForthFilter[] {
5
- if (!filter) {
6
- return [];
7
- }
8
- return (Array.isArray(filter) ? filter : [filter]) as IAdminForthFilter[];
9
- }
10
-
11
- function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
12
- return (Array.isArray(sort) ? sort : [sort]) as IAdminForthSort[];
13
- }
14
-
15
- export default class OperationalResource implements IOperationalResource {
16
- dataConnector: IAdminForthDataSourceConnectorBase;
17
- resourceConfig: AdminForthResource;
18
-
19
- constructor(dataConnector: IAdminForthDataSourceConnectorBase, resourceConfig: AdminForthResource) {
20
- this.dataConnector = dataConnector;
21
- this.resourceConfig = resourceConfig;
22
- }
23
-
24
- async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any | null> {
25
- return (
26
- await this.dataConnector.getData({
27
- resource: this.resourceConfig,
28
- filters: filtersIfFilter(filter),
29
- limit: 1,
30
- offset: 0,
31
- sort: [],
32
- })
33
- ).data[0] || null;
34
- }
35
-
36
- async list(
37
- filter: IAdminForthFilter | IAdminForthFilter[],
38
- limit: number | null = null,
39
- offset: number | null = null,
40
- sort: IAdminForthSort | IAdminForthSort[] = []
41
- ): Promise<any[]> {
42
- // check if type of limit and offset is number
43
- if (limit !== null && typeof limit !== 'number') {
44
- throw new Error('Limit must be a number');
45
- }
46
- if (offset !== null && typeof offset !== 'number') {
47
- throw new Error('Offset must be a number');
48
- }
49
-
50
- let appliedLimit = limit;
51
- if (limit === null) {
52
- appliedLimit = 1000000000;
53
- }
54
- let appliedOffset = offset;
55
- if (offset === null) {
56
- appliedOffset = 0;
57
- }
58
-
59
- const { data } = await this.dataConnector.getData({
60
- resource: this.resourceConfig,
61
- filters: filtersIfFilter(filter),
62
- limit: appliedLimit,
63
- offset: appliedOffset,
64
- sort: sortsIfSort(sort),
65
- getTotals: false,
66
- });
67
- return data;
68
- }
69
-
70
- async count(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): Promise<number> {
71
- return await this.dataConnector.getCount({
72
- resource: this.resourceConfig,
73
- filters: filtersIfFilter(filter),
74
- });
75
- }
76
-
77
- async create(recordValues: any): Promise<{ ok: boolean; createdRecord: any; error?: string; }> {
78
- const { ok, createdRecord, error } = await this.dataConnector.createRecord({
79
- resource: this.resourceConfig,
80
- record: recordValues,
81
- adminUser: null
82
- });
83
- return { ok, createdRecord, error };
84
- }
85
-
86
- async update(primaryKey: any, record: any): Promise<any> {
87
- return await this.dataConnector.updateRecord({
88
- resource: this.resourceConfig,
89
- recordId: primaryKey,
90
- newValues: record
91
- });
92
- }
93
-
94
- async delete(primaryKey: any): Promise<boolean> {
95
- return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
96
- }
97
-
98
- }