adminforth 1.3.54-next.23 → 1.3.54-next.24

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 (101) hide show
  1. package/dist/plugins/audit-log/types.js +2 -0
  2. package/dist/plugins/audit-log/types.js.map +1 -0
  3. package/dist/plugins/chat-gpt/types.js +2 -0
  4. package/dist/plugins/chat-gpt/types.js.map +1 -0
  5. package/dist/plugins/email-password-reset/types.js +2 -0
  6. package/dist/plugins/email-password-reset/types.js.map +1 -0
  7. package/dist/plugins/foreign-inline-list/types.js +2 -0
  8. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  9. package/dist/plugins/import-export/types.js +2 -0
  10. package/dist/plugins/import-export/types.js.map +1 -0
  11. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  12. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  13. package/dist/plugins/rich-editor/dist/async-queue.js +41 -0
  14. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  15. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  16. package/dist/plugins/rich-editor/types.js +16 -0
  17. package/dist/plugins/rich-editor/types.js.map +1 -0
  18. package/dist/plugins/two-factors-auth/types.js +2 -0
  19. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  20. package/dist/plugins/upload/types.js +2 -0
  21. package/dist/plugins/upload/types.js.map +1 -0
  22. package/package.json +4 -1
  23. package/auth.ts +0 -140
  24. package/basePlugin.ts +0 -70
  25. package/dataConnectors/baseConnector.ts +0 -221
  26. package/dataConnectors/clickhouse.ts +0 -343
  27. package/dataConnectors/mongo.ts +0 -202
  28. package/dataConnectors/postgres.ts +0 -310
  29. package/dataConnectors/sqlite.ts +0 -258
  30. package/index.ts +0 -428
  31. package/modules/codeInjector.ts +0 -747
  32. package/modules/configValidator.ts +0 -588
  33. package/modules/operationalResource.ts +0 -98
  34. package/modules/restApi.ts +0 -718
  35. package/modules/styleGenerator.ts +0 -55
  36. package/modules/styles.ts +0 -126
  37. package/modules/utils.ts +0 -472
  38. package/servers/express.ts +0 -259
  39. package/spa/.eslintrc.cjs +0 -14
  40. package/spa/README.md +0 -39
  41. package/spa/env.d.ts +0 -1
  42. package/spa/index.html +0 -23
  43. package/spa/package-lock.json +0 -4659
  44. package/spa/package.json +0 -52
  45. package/spa/postcss.config.js +0 -6
  46. package/spa/public/assets/favicon.png +0 -0
  47. package/spa/src/App.vue +0 -418
  48. package/spa/src/assets/base.css +0 -2
  49. package/spa/src/assets/logo.svg +0 -19
  50. package/spa/src/components/AcceptModal.vue +0 -45
  51. package/spa/src/components/Breadcrumbs.vue +0 -41
  52. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  53. package/spa/src/components/CustomDatePicker.vue +0 -176
  54. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  55. package/spa/src/components/CustomRangePicker.vue +0 -156
  56. package/spa/src/components/Dropdown.vue +0 -168
  57. package/spa/src/components/Filters.vue +0 -222
  58. package/spa/src/components/HelloWorld.vue +0 -17
  59. package/spa/src/components/MenuLink.vue +0 -27
  60. package/spa/src/components/ResourceForm.vue +0 -325
  61. package/spa/src/components/ResourceListTable.vue +0 -466
  62. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  63. package/spa/src/components/SkeleteLoader.vue +0 -23
  64. package/spa/src/components/ThreeDotsMenu.vue +0 -43
  65. package/spa/src/components/Toast.vue +0 -78
  66. package/spa/src/components/ValueRenderer.vue +0 -141
  67. package/spa/src/components/icons/IconCalendar.vue +0 -5
  68. package/spa/src/components/icons/IconCommunity.vue +0 -7
  69. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  70. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  71. package/spa/src/components/icons/IconSupport.vue +0 -7
  72. package/spa/src/components/icons/IconTime.vue +0 -5
  73. package/spa/src/components/icons/IconTooling.vue +0 -19
  74. package/spa/src/composables/useFrontendApi.ts +0 -26
  75. package/spa/src/composables/useStores.ts +0 -131
  76. package/spa/src/index.scss +0 -31
  77. package/spa/src/main.ts +0 -18
  78. package/spa/src/renderers/CompactUUID.vue +0 -48
  79. package/spa/src/renderers/CountryFlag.vue +0 -69
  80. package/spa/src/router/index.ts +0 -59
  81. package/spa/src/spa_types/core.ts +0 -53
  82. package/spa/src/stores/core.ts +0 -148
  83. package/spa/src/stores/filters.ts +0 -27
  84. package/spa/src/stores/modal.ts +0 -48
  85. package/spa/src/stores/toast.ts +0 -31
  86. package/spa/src/stores/user.ts +0 -72
  87. package/spa/src/utils.ts +0 -160
  88. package/spa/src/views/CreateView.vue +0 -167
  89. package/spa/src/views/EditView.vue +0 -170
  90. package/spa/src/views/ListView.vue +0 -352
  91. package/spa/src/views/LoginView.vue +0 -192
  92. package/spa/src/views/ResourceParent.vue +0 -17
  93. package/spa/src/views/ShowView.vue +0 -194
  94. package/spa/tailwind.config.js +0 -17
  95. package/spa/tsconfig.app.json +0 -14
  96. package/spa/tsconfig.json +0 -11
  97. package/spa/tsconfig.node.json +0 -19
  98. package/spa/vite.config.ts +0 -56
  99. package/tsconfig.json +0 -112
  100. package/types/AdminForthConfig.ts +0 -1762
  101. package/types/FrontendAPI.ts +0 -143
package/index.ts DELETED
@@ -1,428 +0,0 @@
1
-
2
- import AdminForthAuth from './auth.js';
3
- import MongoConnector from './dataConnectors/mongo.js';
4
- import PostgresConnector from './dataConnectors/postgres.js';
5
- import SQLiteConnector from './dataConnectors/sqlite.js';
6
- import CodeInjector from './modules/codeInjector.js';
7
- import ExpressServer from './servers/express.js';
8
- import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, getClinetIp } from './modules/utils.js';
9
- import {
10
- type AdminForthConfig,
11
- type IAdminForth,
12
- type IConfigValidator,
13
- IOperationalResource,
14
- AdminForthFilterOperators,
15
- AdminForthDataTypes, IHttpServer,
16
- BeforeSaveFunction,
17
- AfterSaveFunction,
18
- AdminUser,
19
- AdminForthResource,
20
- IAdminForthDataSourceConnectorBase,
21
- } from './types/AdminForthConfig.js';
22
- import AdminForthPlugin from './basePlugin.js';
23
- import ConfigValidator from './modules/configValidator.js';
24
- import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
25
- import ClickhouseConnector from './dataConnectors/clickhouse.js';
26
- import OperationalResource from './modules/operationalResource.js';
27
-
28
- // exports
29
- export * from './types/AdminForthConfig.js';
30
- export { interpretResource };
31
- export { AdminForthPlugin };
32
- export { suggestIfTypo, RateLimiter, getClinetIp };
33
-
34
-
35
- class AdminForth implements IAdminForth {
36
- static Types = AdminForthDataTypes;
37
-
38
- static Utils = {
39
- generatePasswordHash: async (password) => {
40
- return await AdminForthAuth.generatePasswordHash(password);
41
- },
42
-
43
- PASSWORD_VALIDATORS: {
44
- UP_LOW_NUM_SPECIAL: {
45
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*\\(\\)\\-_=\\+\\[\\]\\{\\}\\|;:\',\\.<>\\/\\?]).+$',
46
- message: 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character'
47
- },
48
- UP_LOW_NUM: {
49
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).+$',
50
- message: 'Password must include at least one uppercase letter, one lowercase letter, and one number'
51
- },
52
- },
53
- EMAIL_VALIDATOR: {
54
- regExp: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$',
55
- message: 'Email is not valid, must be in format example@test.com',
56
- }
57
- }
58
-
59
- #defaultConfig = {
60
- deleteConfirmation: true,
61
- }
62
-
63
- config: AdminForthConfig;
64
- express: ExpressServer;
65
- auth: AdminForthAuth;
66
- codeInjector: CodeInjector;
67
- connectors: {
68
- [dataSourceId: string]: IAdminForthDataSourceConnectorBase,
69
- };
70
- connectorClasses: any;
71
- runningHotReload: boolean;
72
- activatedPlugins: Array<AdminForthPlugin>;
73
- configValidator: IConfigValidator;
74
- restApi: AdminForthRestAPI;
75
- operationalResources: {
76
- [resourceId: string]: IOperationalResource,
77
- }
78
- baseUrlSlashed: string;
79
-
80
- statuses: {
81
- dbDiscover: 'running' | 'done',
82
- }
83
-
84
- constructor(config: AdminForthConfig) {
85
- this.config = {...this.#defaultConfig,...config};
86
- this.codeInjector = new CodeInjector(this);
87
- this.configValidator = new ConfigValidator(this, this.config);
88
- this.restApi = new AdminForthRestAPI(this);
89
- this.activatedPlugins = [];
90
-
91
- this.configValidator.validateConfig();
92
- this.activatePlugins();
93
- this.configValidator.validateConfig(); // revalidate after plugins
94
-
95
- this.express = new ExpressServer(this);
96
- this.auth = new AdminForthAuth(this);
97
- this.connectors = {};
98
- this.statuses = {
99
- dbDiscover: 'running',
100
- };
101
-
102
- console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`)
103
- }
104
-
105
- activatePlugins() {
106
- process.env.HEAVY_DEBUG && console.log('🔌🔌🔌 Activating plugins');
107
- const allPluginInstances = [];
108
- for (let resource of this.config.resources) {
109
- for (let pluginInstance of resource.plugins || []) {
110
- allPluginInstances.push({pi: pluginInstance, resource});
111
- }
112
- }
113
- allPluginInstances.sort(({pi: a}, {pi: b}) => a.activationOrder - b.activationOrder);
114
- allPluginInstances.forEach(
115
- ({pi: pluginInstance, resource}) => {
116
- pluginInstance.modifyResourceConfig(this, resource);
117
- const plugin = this.activatedPlugins.find((p) => p.pluginInstanceId === pluginInstance.pluginInstanceId);
118
- if (plugin) {
119
- process.env.HEAVY_DEBUG && console.log(`Current plugin pluginInstance.pluginInstanceId ${pluginInstance.pluginInstanceId}`);
120
-
121
- throw new Error(`Attempt to activate Plugin ${pluginInstance.constructor.name} second time for same resource, but plugin does not support it.
122
- To support multiple plugin instance pre one resource, plugin should return unique string values for each installation from instanceUniqueRepresentation`);
123
- }
124
- this.activatedPlugins.push(pluginInstance);
125
- }
126
- );
127
- }
128
-
129
- getPluginsByClassName<T>(className: string): T[] {
130
- const plugins = this.activatedPlugins.filter((plugin) => plugin.className === className);
131
- return plugins as T[];
132
- }
133
-
134
- getPluginByClassName<T>(className: string): T {
135
- const plugins = this.getPluginsByClassName(className);
136
- if (plugins.length > 1) {
137
- throw new Error(`Multiple plugins with className ${className} found. Use getPluginsByClassName instead`);
138
- }
139
- if (plugins.length === 0) {
140
- const similar = suggestIfTypo(this.activatedPlugins.map((p) => p.className), className);
141
- throw new Error(`Plugin with className ${className} not found. ${similar ? `Did you mean ${similar}?` : ''}`);
142
- }
143
- return plugins[0] as T;
144
- }
145
-
146
- async discoverDatabases() {
147
- this.statuses.dbDiscover = 'running';
148
- this.connectorClasses = {
149
- 'sqlite': SQLiteConnector,
150
- 'postgres': PostgresConnector,
151
- 'mongodb': MongoConnector,
152
- 'clickhouse': ClickhouseConnector,
153
- };
154
- if (!this.config.databaseConnectors) {
155
- this.config.databaseConnectors = {...this.connectorClasses};
156
- }
157
- this.config.dataSources.forEach((ds) => {
158
- const dbType = ds.url.split(':')[0];
159
- if (!this.config.databaseConnectors[dbType]) {
160
- throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
161
- }
162
- this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
163
- });
164
-
165
- await Promise.all(this.config.resources.map(async (res) => {
166
- if (!this.connectors[res.dataSource]) {
167
- const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
168
- throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
169
- ? `. Did you mean '${similar}'?` : 'Available dataSources: '+Object.keys(this.connectors).join(', ')}`
170
- );
171
- }
172
- const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
173
- if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
174
- throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
175
- }
176
- if (fieldTypes === null) {
177
- console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
178
- return;
179
- }
180
- if (!res.columns) {
181
- res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
182
- }
183
-
184
- res.columns.forEach((col, i) => {
185
- if (!fieldTypes[col.name] && !col.virtual) {
186
- const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
187
- throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
188
- }
189
- // first find discovered values, but allow override
190
- res.columns[i] = { ...fieldTypes[col.name], ...col };
191
- });
192
-
193
- this.configValidator.postProcessAfterDiscover(res);
194
-
195
- // check if primaryKey column is present
196
- if (!res.columns.some((col) => col.primaryKey)) {
197
- throw new Error(`Resource '${res.table}' has no column defined or auto-discovered. Please set 'primaryKey: true' in a columns which has unique value for each record and index`);
198
- }
199
-
200
- }));
201
-
202
- this.statuses.dbDiscover = 'done';
203
-
204
- this.operationalResources = {};
205
- this.config.resources.forEach((resource) => {
206
- this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
207
- });
208
-
209
- // console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
210
- }
211
-
212
- async bundleNow({ hotReload=false }) {
213
- await this.codeInjector.bundleNow({ hotReload });
214
- }
215
-
216
- async getUserByPk(pk: string) {
217
- // if database discovery is not done, throw
218
- if (this.statuses.dbDiscover !== 'done') {
219
- if (this.statuses.dbDiscover === 'running') {
220
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
221
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
222
- }
223
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
224
- 'Call admin.discoverDatabases() first and await it before using data API');
225
- }
226
-
227
- const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
228
- if (!resource) {
229
- const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
230
- throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
231
- `Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`
232
- );
233
- }
234
- const users = await this.connectors[resource.dataSource].getData({
235
- resource,
236
- filters: [
237
- { field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
238
- ],
239
- limit: 1,
240
- offset: 0,
241
- sort: [],
242
- });
243
- return users.data[0] || null;
244
- }
245
-
246
- async createResourceRecord(
247
- { resource, record, adminUser }:
248
- { resource: AdminForthResource, record: any, adminUser: AdminUser }
249
- ): Promise<{ error?: string, createdRecord?: any }> {
250
-
251
- // execute hook if needed
252
- for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
253
- const resp = await hook({ recordId: undefined, resource, record, adminUser });
254
- if (!resp || (!resp.ok && !resp.error)) {
255
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
256
- }
257
-
258
- if (resp.error) {
259
- return { error: resp.error };
260
- }
261
- }
262
-
263
- // remove virtual columns from record
264
- for (const column of resource.columns.filter((col) => col.virtual)) {
265
- if (record[column.name]) {
266
- delete record[column.name];
267
- }
268
- }
269
- const connector = this.connectors[resource.dataSource];
270
- process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record createResourceRecord', record);
271
- const { error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
272
- if ( error ) {
273
- return { error };
274
- }
275
-
276
- const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
277
-
278
- // execute hook if needed
279
- for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
280
- process.env.HEAVY_DEBUG && console.log('🪲 Hook afterSave', hook);
281
- const resp = await hook({
282
- recordId: primaryKey,
283
- resource,
284
- record: createdRecord,
285
- adminUser
286
- });
287
-
288
- if (!resp || (!resp.ok && !resp.error)) {
289
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
290
- }
291
-
292
- if (resp.error) {
293
- return { error: resp.error };
294
- }
295
- }
296
-
297
- return { error, createdRecord };
298
- }
299
-
300
- /**
301
- * record is partial record with only changed fields
302
- */
303
- async updateResourceRecord(
304
- { resource, recordId, record, oldRecord, adminUser }:
305
- { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser }
306
- ): Promise<{ error?: string }> {
307
-
308
- // execute hook if needed
309
- for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
310
- const resp = await hook({
311
- recordId,
312
- resource,
313
- record,
314
- oldRecord,
315
- adminUser
316
- });
317
- if (!resp || (!resp.ok && !resp.error)) {
318
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
319
- }
320
- if (resp.error) {
321
- return { error: resp.error };
322
- }
323
- }
324
- const newValues = {};
325
- const connector = this.connectors[resource.dataSource];
326
-
327
- for (const recordField in record) {
328
- if (record[recordField] !== oldRecord[recordField]) {
329
- // leave only changed fields to reduce data transfer/modifications in db
330
- const column = resource.columns.find((col) => col.name === recordField);
331
- if (!column || !column.virtual) {
332
- // exclude virtual columns
333
- newValues[recordField] = record[recordField];
334
- }
335
- }
336
- }
337
-
338
- if (Object.keys(newValues).length > 0) {
339
- await connector.updateRecord({ resource, recordId, newValues });
340
- }
341
-
342
- // execute hook if needed
343
- for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
344
- const resp = await hook({
345
- resource,
346
- record,
347
- adminUser,
348
- oldRecord,
349
- recordId,
350
- });
351
- if (!resp || (!resp.ok && !resp.error)) {
352
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
353
- }
354
- if (resp.error) {
355
- return { error: resp.error };
356
- }
357
- }
358
-
359
- return { error: null };
360
- }
361
-
362
- async deleteResourceRecord(
363
- { resource, recordId, adminUser, record }:
364
- { resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any }
365
- ): Promise<{ error?: string }> {
366
- // execute hook if needed
367
- for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
368
- const resp = await hook({
369
- resource,
370
- record,
371
- adminUser,
372
- recordId,
373
- });
374
- if (!resp || (!resp.ok && !resp.error)) {
375
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
376
- }
377
-
378
- if (resp.error) {
379
- return { error: resp.error };
380
- }
381
- }
382
-
383
- const connector = this.connectors[resource.dataSource];
384
- await connector.deleteRecord({ resource, recordId});
385
-
386
- // execute hook if needed
387
- for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
388
- const resp = await hook({
389
- resource,
390
- record,
391
- adminUser,
392
- recordId,
393
- });
394
- if (!resp || (!resp.ok && !resp.error)) {
395
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
396
- }
397
-
398
- if (resp.error) {
399
- return { error: resp.error };
400
- }
401
- }
402
-
403
- return { error: null };
404
- }
405
-
406
- resource(resourceId: string): IOperationalResource {
407
- if (this.statuses.dbDiscover !== 'done') {
408
- if (this.statuses.dbDiscover === 'running') {
409
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
410
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
411
- } else {
412
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
413
- 'Call admin.discoverDatabases() first and await it before using data API');
414
- }
415
- }
416
- if (!this.operationalResources[resourceId]) {
417
- const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
418
- throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
419
- }
420
- return this.operationalResources[resourceId];
421
- }
422
-
423
- setupEndpoints(server: IHttpServer) {
424
- this.restApi.registerEndpoints(server);
425
- }
426
- }
427
-
428
- export default AdminForth;