adminforth 1.3.54-next.23 → 1.3.54-next.25

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 (41) 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 +5 -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/tsconfig.json +0 -112
  40. package/types/AdminForthConfig.ts +0 -1762
  41. package/types/FrontendAPI.ts +0 -143
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/audit-log/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/chat-gpt/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/email-password-reset/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/foreign-inline-list/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/import-export/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class AsyncQueue {
4
+ constructor() {
5
+ this.queue = [];
6
+ this.processing = false;
7
+ }
8
+ async add(task) {
9
+ this.queue.push(task);
10
+ if (!this.processing) {
11
+ this.process();
12
+ }
13
+ }
14
+ async process() {
15
+ this.processing = true;
16
+ while (this.queue.length > 0) {
17
+ const task = this.queue.shift();
18
+ try {
19
+ await task();
20
+ }
21
+ catch (error) {
22
+ console.error('Task encountered an error:', error);
23
+ }
24
+ }
25
+ this.processing = false;
26
+ }
27
+ }
28
+ exports.default = AsyncQueue;
29
+ //# sourceMappingURL=async-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-queue.js","sourceRoot":"","sources":["../../../../plugins/rich-editor/custom/async-queue.ts"],"names":[],"mappings":";;AAEA,MAAqB,UAAU;IAI7B;QACE,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAwB;QAChC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;CACF;AA5BD,6BA4BC"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ class AsyncQueue {
13
+ constructor() {
14
+ this.queue = [];
15
+ this.processing = false;
16
+ }
17
+ add(task) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ this.queue.push(task);
20
+ if (!this.processing) {
21
+ this.process();
22
+ }
23
+ });
24
+ }
25
+ process() {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ this.processing = true;
28
+ while (this.queue.length > 0) {
29
+ const task = this.queue.shift();
30
+ try {
31
+ yield task();
32
+ }
33
+ catch (error) {
34
+ console.error('Task encountered an error:', error);
35
+ }
36
+ }
37
+ this.processing = false;
38
+ });
39
+ }
40
+ }
41
+ exports.default = AsyncQueue;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class AsyncQueue {
4
+ constructor() {
5
+ this.queue = [];
6
+ this.processing = false;
7
+ }
8
+ async add(task) {
9
+ this.queue.push(task);
10
+ if (!this.processing) {
11
+ this.process();
12
+ }
13
+ }
14
+ async process() {
15
+ this.processing = true;
16
+ while (this.queue.length > 0) {
17
+ const task = this.queue.shift();
18
+ try {
19
+ await task();
20
+ }
21
+ catch (error) {
22
+ console.error('Task encountered an error:', error);
23
+ }
24
+ }
25
+ this.processing = false;
26
+ }
27
+ }
28
+ exports.default = AsyncQueue;
29
+ //# sourceMappingURL=async-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-queue.js","sourceRoot":"","sources":["../../../../../plugins/rich-editor/dist/custom/async-queue.ts"],"names":[],"mappings":";;AAEA,MAAqB,UAAU;IAI7B;QACE,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAwB;QAChC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;CACF;AA5BD,6BA4BC"}
@@ -0,0 +1,16 @@
1
+ // example options ussage:
2
+ //{
3
+ // htmlFieldName: 'description',
4
+ // completion: {
5
+ // provider: 'openai-chat-gpt',
6
+ // params: {
7
+ // apiKey: process.env.OPENAI_API_KEY as string,
8
+ // model: 'gpt-4o',
9
+ // },
10
+ // expert: {
11
+ // debounceTime: 250,
12
+ // }
13
+ // }
14
+ //}
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/rich-editor/types.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,GAAG;AACH,iCAAiC;AACjC,iBAAiB;AACjB,kCAAkC;AAClC,eAAe;AACf,qDAAqD;AACrD,wBAAwB;AACxB,QAAQ;AACR,eAAe;AACf,0BAA0B;AAC1B,OAAO;AACP,KAAK;AACL,GAAG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/two-factors-auth/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/upload/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.3.54-next.23",
3
+ "version": "1.3.54-next.25",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/**/*",
9
+ "spa/**/*"
10
+ ],
7
11
  "scripts": {
8
12
  "test": "echo \"Error: no test specified\" && exit 1",
9
13
  "build": "tsc",
package/auth.ts DELETED
@@ -1,140 +0,0 @@
1
-
2
- import jwt from 'jsonwebtoken';
3
- import crypto from 'crypto';
4
- import AdminForth from './index.js';
5
-
6
- // Function to generate a password hash using PBKDF2
7
- function calcPasswordHash(password, salt, iterations = 100000, keyLength = 64, digest = 'sha512') {
8
- return new Promise((resolve, reject) => {
9
- crypto.pbkdf2(password, salt, iterations, keyLength, digest, (err, derivedKey) => {
10
- if (err) reject(err);
11
- resolve(derivedKey.toString('hex'));
12
- });
13
- });
14
- }
15
-
16
- // Function to generate a random salt
17
- function generateSalt(length = 16) {
18
- return crypto.randomBytes(length).toString('hex');
19
- }
20
-
21
- function parseTimeToSeconds(time: string): number {
22
- const unit = time.slice(-1);
23
- const value = parseInt(time.slice(0, -1), 10);
24
- switch (unit) {
25
- case 's':
26
- return value;
27
- case 'm':
28
- return value * 60;
29
- case 'h':
30
- return value * 60 * 60;
31
- case 'd':
32
- return value * 60 * 60 * 24;
33
- default:
34
- throw new Error(`Invalid time unit: ${unit}`);
35
- }
36
- }
37
-
38
- class AdminForthAuth {
39
- adminforth: AdminForth;
40
-
41
- constructor(adminforth) {
42
- this.adminforth = adminforth;
43
- }
44
-
45
- removeAuthCookie(response) {
46
- response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
47
- }
48
-
49
- setAuthCookie({ expireInDays, response, username, pk}: {
50
- expireInDays?: number,
51
- response: any,
52
- username: string,
53
- pk: string | null
54
- }) {
55
- const expiresIn: string = expireInDays ? `${expireInDays}d` : (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h');
56
- // might be h,m,d in string
57
- const expiresInSec = parseTimeToSeconds(expiresIn);
58
-
59
- const token = this.issueJWT({ username, pk}, 'auth', expiresIn);
60
- const expiresCookieFormat = new Date(Date.now() + expiresInSec * 1000).toUTCString();
61
-
62
- response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${expiresCookieFormat}`);
63
- }
64
-
65
- removeCustomCookie({response, name}) {
66
- response.setHeader('Set-Cookie', `adminforth_${name}=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
67
- }
68
-
69
- setCustomCookie({ response, payload }: {
70
- response: any, payload: {name: string, value: string, expiry: number, httpOnly: boolean}
71
- }) {
72
- const {name,value,expiry,httpOnly} = payload
73
- response.setHeader('Set-Cookie', `adminforth_${name}=${value}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${new Date(Date.now() + expiry).toUTCString() } `);
74
- }
75
-
76
- issueJWT(payload: Object, type: string, expiresIn: string = '24h'): string {
77
- // read ADMINFORH_SECRET from environment if not drop error
78
- const secret = process.env.ADMINFORTH_SECRET;
79
- if (!secret) {
80
- throw new Error('ADMINFORTH_SECRET environment not set');
81
- }
82
-
83
- // issue JWT token
84
- return jwt.sign({...payload, t: type}, secret, { expiresIn });
85
- }
86
-
87
- async verify(jwtToken: string, mustHaveType: string, decodeUser: boolean | undefined = true): Promise<Object> {
88
- // read ADMINFORH_SECRET from environment if not drop error
89
- const secret = process.env.ADMINFORTH_SECRET;
90
- if (!secret) {
91
- throw new Error('ADMINFORTH_SECRET environment not set');
92
- }
93
- let decoded;
94
- try {
95
- // verify JWT token
96
- decoded = jwt.verify(jwtToken, secret);
97
- } catch (err) {
98
- if (err.name === 'TokenExpiredError') {
99
- console.error('Token expired:', err.message);
100
- } else if (err.name === 'JsonWebTokenError') {
101
- console.error('Token error:', err.message);
102
- } else {
103
- console.error('Failed to verify JWT token', err);
104
- }
105
- return null;
106
- }
107
- const { pk, t } = decoded;
108
- if (t !== mustHaveType) {
109
- console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
110
- return null;
111
- }
112
- if (decodeUser !== false) {
113
- const dbUser = await this.adminforth.getUserByPk(pk);
114
- if (!dbUser) {
115
- console.error(`User with pk ${pk} not found in database`);
116
- // will logout user which was deleted
117
- return null;
118
- }
119
- decoded.dbUser = dbUser;
120
- }
121
-
122
- return decoded;
123
- }
124
-
125
- static async generatePasswordHash(password) {
126
- const salt = generateSalt();
127
- const hashedPassword = await calcPasswordHash(password, salt);
128
- return `${salt}:${hashedPassword}`;
129
- }
130
-
131
- static async verifyPassword(password, hashedPassword) {
132
- const [salt, hash] = hashedPassword.split(':');
133
- const newHash = await calcPasswordHash(password, salt);
134
- return newHash === hash;
135
- }
136
-
137
-
138
- }
139
-
140
- export default AdminForthAuth;
package/basePlugin.ts DELETED
@@ -1,70 +0,0 @@
1
- import { AdminForthResource, IAdminForthPlugin, IAdminForth } from './types/AdminForthConfig.js';
2
- import { getComponentNameFromPath } from './modules/utils.js';
3
- import { currentFileDir } from './modules/utils.js';
4
- import path from 'path';
5
- import fs from 'fs';
6
-
7
- import crypto from 'crypto';
8
-
9
-
10
- export default class AdminForthPlugin implements IAdminForthPlugin {
11
-
12
- adminforth: IAdminForth;
13
- pluginDir: string;
14
- customFolderName: string = 'custom';
15
- pluginInstanceId: string;
16
- customFolderPath: string;
17
- pluginOptions: any;
18
- resourceConfig: AdminForthResource;
19
- className: string;
20
- activationOrder: number = 0;
21
-
22
- constructor(pluginOptions: any, metaUrl: string) {
23
- // set up plugin here
24
- this.pluginDir = currentFileDir(metaUrl);
25
- this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
26
- this.pluginOptions = pluginOptions;
27
- console.log(`🪲 🪲 🪲 🪲 🪲 🪲 AdminForthPlugin.constructor`, this.constructor.name);
28
- this.className = this.constructor.name;
29
- }
30
-
31
- setupEndpoints(server: any) {
32
-
33
- }
34
-
35
- instanceUniqueRepresentation(pluginOptions: any) : string {
36
- return 'non-uniquely-identified';
37
- }
38
-
39
- modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
40
- this.resourceConfig = resourceConfig;
41
- const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
42
-
43
- const seed = `af_pl_${this.constructor.name}_${resourceConfig.resourceId}_${uniqueness}`;
44
- this.pluginInstanceId = crypto.createHash('sha256').update(
45
- seed
46
- ).digest('hex')
47
- process.env.HEAVY_DEBUG && console.log(`🪲 AdminForthPlugin.modifyResourceConfig`, seed, 'id', this.pluginInstanceId);
48
- this.adminforth = adminforth;
49
- }
50
-
51
- componentPath(componentFile: string) {
52
- const key = `@@/plugins/${this.constructor.name}/${componentFile}`;
53
- const componentName = getComponentNameFromPath(key);
54
-
55
- if (!this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath]) {
56
- this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath] = `./plugins/${this.constructor.name}/`;
57
- }
58
-
59
- if (!this.adminforth.codeInjector.allComponentNames[key]) {
60
- const absSrcPath = path.join(this.customFolderPath, componentFile);
61
- if (!fs.existsSync(absSrcPath)) {
62
- throw new Error(`Plugin "${this.constructor.name}" tried to use file as component which does not exist at "${absSrcPath}"`);
63
- }
64
- this.adminforth.codeInjector.allComponentNames[key] = componentName;
65
- }
66
-
67
- return key;
68
- }
69
-
70
- }
@@ -1,221 +0,0 @@
1
- import { AdminForthResource, IAdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn, IAdminForthSort, IAdminForthFilter } from "../types/AdminForthConfig.js";
2
- import { suggestIfTypo } from "../modules/utils.js";
3
-
4
-
5
- export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
6
- getPrimaryKey(resource: AdminForthResource): string {
7
- for (const col of resource.dataSourceColumns) {
8
- if (col.primaryKey) {
9
- return col.name;
10
- }
11
- }
12
- }
13
-
14
- async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
15
- const data = await this.getDataWithOriginalTypes({
16
- resource,
17
- limit: 1,
18
- offset: 0,
19
- sort: [],
20
- filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
21
- });
22
- return data.length > 0 ? data[0] : null;
23
- }
24
-
25
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
26
- resource: AdminForthResource,
27
- limit: number,
28
- offset: number,
29
- sort: IAdminForthSort[],
30
- filters: IAdminForthFilter[],
31
- }): Promise<any[]> {
32
- throw new Error('Method not implemented.');
33
- }
34
-
35
- getCount({ resource, filters }: { resource: AdminForthResource; filters: { field: string; operator: AdminForthFilterOperators; value: any; }[]; }): Promise<number> {
36
- throw new Error('Method not implemented.');
37
- }
38
-
39
- discoverFields(resource: AdminForthResource): Promise<{ [key: string]: AdminForthResourceColumn; }> {
40
- throw new Error('Method not implemented.');
41
- }
42
-
43
- getFieldValue(field: AdminForthResourceColumn, value: any) {
44
- throw new Error('Method not implemented.');
45
- }
46
-
47
- setFieldValue(field: AdminForthResourceColumn, value: any) {
48
- throw new Error('Method not implemented.');
49
- }
50
-
51
- getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
52
- throw new Error('Method not implemented.');
53
- }
54
-
55
- createRecordOriginalValues({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
56
- throw new Error('Method not implemented.');
57
- }
58
-
59
- async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any) {
60
- process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
61
- const existingRecord = await this.getData({
62
- resource,
63
- filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
64
- limit: 1,
65
- sort: [],
66
- offset: 0,
67
- getTotals: false
68
- });
69
-
70
- return existingRecord.data.length > 0;
71
- }
72
-
73
- async createRecord({ resource, record, adminUser }: {
74
- resource: AdminForthResource; record: any; adminUser: any;
75
- }): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
76
- // transform value using setFieldValue and call createRecordOriginalValues
77
- const filledRecord = {...record};
78
- const recordWithOriginalValues = {...record};
79
-
80
- for (const col of resource.dataSourceColumns) {
81
- if (col.fillOnCreate) {
82
- if (filledRecord[col.name] === undefined) {
83
- filledRecord[col.name] = col.fillOnCreate({
84
- initialRecord: record,
85
- adminUser
86
- });
87
- }
88
- }
89
- recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
90
- }
91
-
92
- let error: string | null = null;
93
- await Promise.all(
94
- resource.dataSourceColumns.map(async (col) => {
95
- if (col.isUnique && !col.virtual && !error) {
96
- const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
97
- if (exists) {
98
- error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
99
- }
100
- }
101
- })
102
- );
103
- if (error) {
104
- process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
105
- return { error, ok: false };
106
- }
107
-
108
- process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record', recordWithOriginalValues);
109
- await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
110
-
111
- return {
112
- ok: true,
113
- createdRecord: recordWithOriginalValues,
114
- }
115
- }
116
-
117
- updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
118
- throw new Error('Method not implemented.');
119
- }
120
-
121
- async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<{ error?: string; ok: boolean; }> {
122
- // transform value using setFieldValue and call updateRecordOriginalValues
123
- const recordWithOriginalValues = {...newValues};
124
-
125
- for (const field of Object.keys(newValues)) {
126
- const col = resource.dataSourceColumns.find((col) => col.name == field);
127
- // todo instead of throwing error, we can just not use setFieldValue here, and pass original value to updateRecordOriginalValues
128
- // we might consider this because some users might not want to define all columns in resource with showIn:[], but still want to use them in hooks
129
- if (!col) {
130
- const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), field);
131
- throw new Error(`
132
- Update record received field '${field}' (with value ${newValues[field]}), but such column not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}
133
- `);
134
- }
135
- recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
136
- }
137
-
138
- process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
139
-
140
- await this.updateRecordOriginalValues({ resource, recordId, newValues: recordWithOriginalValues });
141
-
142
- return { ok: true };
143
- }
144
-
145
- deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<boolean> {
146
- throw new Error('Method not implemented.');
147
- }
148
-
149
-
150
- async getData({ resource, limit, offset, sort, filters, getTotals }: {
151
- resource: AdminForthResource,
152
- limit: number,
153
- offset: number,
154
- sort: { field: string, direction: AdminForthSortDirections }[],
155
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
156
- getTotals: boolean,
157
- }): Promise<{ data: any[], total: number }> {
158
- if (filters) {
159
- filters.map((f) => {
160
- if (!f.field) {
161
- throw new Error(`Field "field" not specified in filter object: ${JSON.stringify(f)}`);
162
- }
163
- if (!f.operator) {
164
- throw new Error(`Field "operator" not specified in filter object: ${JSON.stringify(f)}`);
165
- }
166
- const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
167
- if (!fieldObj) {
168
- const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
169
- throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
170
- }
171
- if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
172
- f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
173
- } else {
174
- f.value = this.setFieldValue(fieldObj, f.value);
175
- }
176
- });
177
- }
178
-
179
- const promises: Promise<any>[] = [this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters })];
180
- if (getTotals) {
181
- promises.push(this.getCount({ resource, filters }));
182
- } else {
183
- promises.push(Promise.resolve(undefined));
184
- }
185
-
186
- const [data, total] = await Promise.all(promises);
187
-
188
- // call getFieldValue for each field
189
- data.map((record) => {
190
- for (const col of resource.dataSourceColumns) {
191
- record[col.name] = this.getFieldValue(col, record[col.name]);
192
- }
193
- });
194
-
195
- return { data, total };
196
- }
197
-
198
- async getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
199
- const mm = await this.getMinMaxForColumnsWithOriginalTypes({ resource, columns });
200
- const result = {};
201
- for (const col of columns) {
202
- result[col.name] = {
203
- min: this.getFieldValue(col, mm[col.name].min),
204
- max: this.getFieldValue(col, mm[col.name].max),
205
- };
206
- }
207
- return result;
208
- }
209
-
210
- getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any> {
211
- return this.getRecordByPrimaryKeyWithOriginalTypes(resource, recordId).then((record) => {
212
- const newRecord = {};
213
- for (const col of resource.dataSourceColumns) {
214
- newRecord[col.name] = this.getFieldValue(col, record[col.name]);
215
- }
216
- return newRecord;
217
- });
218
- }
219
-
220
-
221
- }