adminforth 1.2.32 → 1.2.34

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.
@@ -1,10 +1,7 @@
1
- import betterSqlite3 from 'better-sqlite3';
2
1
  import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
3
2
  import AdminForthBaseConnector from './baseConnector.js';
4
3
  import dayjs from 'dayjs';
5
- import { createClient, ClickHouseLogLevel } from '@clickhouse/client' // or '@clickhouse/client-web'
6
- import { isObject } from 'util';
7
- import { date } from 'zod';
4
+ import { createClient } from '@clickhouse/client'
8
5
 
9
6
 
10
7
 
@@ -12,6 +9,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
12
9
 
13
10
  client: any;
14
11
  dbName: string;
12
+ url: string;
15
13
 
16
14
  /**
17
15
  * url: http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]
@@ -20,6 +18,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
20
18
  constructor({ url }: { url: string }) {
21
19
  super();
22
20
  this.dbName = new URL(url).pathname.replace('/', '');
21
+ this.url = url;;
23
22
  // create connection here
24
23
  this.client = createClient({
25
24
  url: url.replace('clickhouse://', 'http://'),
@@ -36,15 +35,23 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
36
35
  // level: ClickHouseLogLevel.TRACE,
37
36
  // }
38
37
  });
38
+
39
39
  }
40
40
 
41
41
  async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
42
42
  const tableName = resource.table;
43
- const q = await this.client.query({
44
- query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
45
- format: 'JSONEachRow',
46
- });
47
- const rows = await q.json();
43
+
44
+ let rows;
45
+ try {
46
+ const q = await this.client.query({
47
+ query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
48
+ format: 'JSONEachRow',
49
+ });
50
+ rows = await q.json();
51
+ } catch (e) {
52
+ console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
53
+ return null;
54
+ }
48
55
 
49
56
  const fieldTypes = {};
50
57
  rows.forEach((row) => {
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
11
11
  import AdminForthBaseConnector from './baseConnector.js';
12
12
  import dayjs from 'dayjs';
13
- import { createClient } from '@clickhouse/client'; // or '@clickhouse/client-web'
13
+ import { createClient } from '@clickhouse/client';
14
14
  class ClickhouseConnector extends AdminForthBaseConnector {
15
15
  /**
16
16
  * url: http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]
@@ -35,6 +35,8 @@ class ClickhouseConnector extends AdminForthBaseConnector {
35
35
  [AdminForthSortDirections.desc]: 'DESC',
36
36
  };
37
37
  this.dbName = new URL(url).pathname.replace('/', '');
38
+ this.url = url;
39
+ ;
38
40
  // create connection here
39
41
  this.client = createClient({
40
42
  url: url.replace('clickhouse://', 'http://'),
@@ -54,11 +56,18 @@ class ClickhouseConnector extends AdminForthBaseConnector {
54
56
  discoverFields(resource) {
55
57
  return __awaiter(this, void 0, void 0, function* () {
56
58
  const tableName = resource.table;
57
- const q = yield this.client.query({
58
- query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
59
- format: 'JSONEachRow',
60
- });
61
- const rows = yield q.json();
59
+ let rows;
60
+ try {
61
+ const q = yield this.client.query({
62
+ query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
63
+ format: 'JSONEachRow',
64
+ });
65
+ rows = yield q.json();
66
+ }
67
+ catch (e) {
68
+ console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
69
+ return null;
70
+ }
62
71
  const fieldTypes = {};
63
72
  rows.forEach((row) => {
64
73
  const field = {};
package/dist/index.js CHANGED
@@ -83,9 +83,13 @@ class AdminForth {
83
83
  throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
84
84
  }
85
85
  const fieldTypes = yield this.connectors[res.dataSource].discoverFields(res);
86
- if (!Object.keys(fieldTypes).length) {
86
+ if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
87
87
  throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
88
88
  }
89
+ if (fieldTypes === null) {
90
+ console.error(`DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
91
+ return;
92
+ }
89
93
  if (!res.columns) {
90
94
  res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
91
95
  }
@@ -246,11 +246,27 @@ export default class ConfigValidator {
246
246
  state: 'danger',
247
247
  icon: 'flowbite:trash-bin-outline',
248
248
  allowed: (_b) => __awaiter(this, [_b], void 0, function* ({ resource, adminUser, allowedActions }) { return allowedActions.delete; }),
249
- action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds }) {
249
+ action: (_c) => __awaiter(this, [_c], void 0, function* ({ selectedIds, adminUser }) {
250
250
  const connector = this.adminforth.connectors[res.dataSource];
251
+ let error = null;
251
252
  yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
253
+ const record = yield connector.getRecordByPrimaryKey(res, recordId);
254
+ yield Promise.all(res.hooks.delete.beforeSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
255
+ const resp = yield hook({ resource: res, record, adminUser });
256
+ if (!error && resp.error) {
257
+ error = resp.error;
258
+ }
259
+ })));
252
260
  yield connector.deleteRecord({ resource: res, recordId });
261
+ // call afterDelete hook
262
+ yield Promise.all(res.hooks.delete.afterSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
263
+ yield hook({ resource: res, record, adminUser });
264
+ })));
253
265
  })));
266
+ if (error) {
267
+ return { error, ok: false };
268
+ }
269
+ return { ok: true };
254
270
  })
255
271
  });
256
272
  }
package/index.ts CHANGED
@@ -111,10 +111,13 @@ class AdminForth implements IAdminForth {
111
111
  throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
112
112
  }
113
113
  const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
114
- if (!Object.keys(fieldTypes).length) {
114
+ if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
115
115
  throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
116
116
  }
117
-
117
+ if (fieldTypes === null) {
118
+ console.error(`DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
119
+ return;
120
+ }
118
121
  if (!res.columns) {
119
122
  res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
120
123
  }
@@ -5,6 +5,7 @@ import {
5
5
  AdminForthComponentDeclaration ,
6
6
  AdminForthResourcePages, AllowedActionsEnum,
7
7
  type AdminForthComponentDeclarationFull,
8
+ type AfterSaveFunction,
8
9
  } from "../types/AdminForthConfig.js";
9
10
 
10
11
  import fs from 'fs';
@@ -143,7 +144,7 @@ export default class ConfigValidator implements IConfigValidator {
143
144
  }
144
145
 
145
146
  if (this.config.resources) {
146
- this.config.resources.forEach((res) => {
147
+ this.config.resources.forEach((res: AdminForthResource) => {
147
148
  if (!res.table) {
148
149
  errors.push(`Resource "${res.dataSource}" is missing table`);
149
150
  }
@@ -266,11 +267,42 @@ export default class ConfigValidator implements IConfigValidator {
266
267
  state: 'danger',
267
268
  icon: 'flowbite:trash-bin-outline',
268
269
  allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },
269
- action: async ({ selectedIds }) => {
270
+ action: async ({ selectedIds, adminUser }) => {
270
271
  const connector = this.adminforth.connectors[res.dataSource];
271
- await Promise.all(selectedIds.map(async (recordId) => {
272
- await connector.deleteRecord({ resource: res, recordId });
273
- }));
272
+ let error = null;
273
+
274
+ await Promise.all(
275
+ selectedIds.map(async (recordId) => {
276
+ const record = await connector.getRecordByPrimaryKey(res, recordId);
277
+
278
+ await Promise.all(
279
+ (res.hooks.delete.beforeSave as AfterSaveFunction[]).map(
280
+ async (hook) => {
281
+ const resp = await hook({ resource: res, record, adminUser });
282
+ if (!error && resp.error) {
283
+ error = resp.error;
284
+ }
285
+ }
286
+ )
287
+ )
288
+
289
+ await connector.deleteRecord({ resource: res, recordId });
290
+ // call afterDelete hook
291
+ await Promise.all(
292
+ (res.hooks.delete.afterSave as AfterSaveFunction[]).map(
293
+ async (hook) => {
294
+ await hook({ resource: res, record, adminUser });
295
+ }
296
+ )
297
+ )
298
+
299
+ })
300
+ );
301
+
302
+ if (error) {
303
+ return { error, ok: false };
304
+ }
305
+ return { ok: true };
274
306
  }
275
307
  });
276
308
  }
@@ -251,10 +251,10 @@ export default class AdminForthRestAPI {
251
251
 
252
252
  function checkAccess(action: AllowedActionsEnum, allowedActions: AllowedActions): { allowed: boolean, error?: string } {
253
253
  const allowed = (allowedActions[action] as boolean | string | undefined);
254
- if (allowed !== true) {
255
- return { error: typeof allowed === 'string' ? allowed : 'Action is not allowed', allowed: false };
256
- }
257
- return { allowed: true };
254
+ if (allowed !== true) {
255
+ return { error: typeof allowed === 'string' ? allowed : 'Action is not allowed', allowed: false };
256
+ }
257
+ return { allowed: true };
258
258
  }
259
259
 
260
260
  server.endpoint({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.2.32",
3
+ "version": "1.2.34",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -680,7 +680,7 @@ export type AdminForthBulkAction = {
680
680
  * Callback which will be called on backend when user clicks on action button.
681
681
  * It should return Promise which will be resolved when action is done.
682
682
  */
683
- action: ({ resource, selectedIds, adminUser }: { resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser }) => Promise<void>,
683
+ action: ({ resource, selectedIds, adminUser }: { resource: AdminForthResource, selectedIds: Array<any>, adminUser: AdminUser }) => Promise<{ ok: boolean, error?: string }>,
684
684
 
685
685
  /**
686
686
  * Confirmation message which will be displayed to user before action is executed.