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.
- package/dist/plugins/audit-log/types.js +2 -0
- package/dist/plugins/audit-log/types.js.map +1 -0
- package/dist/plugins/chat-gpt/types.js +2 -0
- package/dist/plugins/chat-gpt/types.js.map +1 -0
- package/dist/plugins/email-password-reset/types.js +2 -0
- package/dist/plugins/email-password-reset/types.js.map +1 -0
- package/dist/plugins/foreign-inline-list/types.js +2 -0
- package/dist/plugins/foreign-inline-list/types.js.map +1 -0
- package/dist/plugins/import-export/types.js +2 -0
- package/dist/plugins/import-export/types.js.map +1 -0
- package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
- package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
- package/dist/plugins/rich-editor/dist/async-queue.js +41 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
- package/dist/plugins/rich-editor/types.js +16 -0
- package/dist/plugins/rich-editor/types.js.map +1 -0
- package/dist/plugins/two-factors-auth/types.js +2 -0
- package/dist/plugins/two-factors-auth/types.js.map +1 -0
- package/dist/plugins/upload/types.js +2 -0
- package/dist/plugins/upload/types.js.map +1 -0
- package/package.json +5 -1
- package/auth.ts +0 -140
- package/basePlugin.ts +0 -70
- package/dataConnectors/baseConnector.ts +0 -221
- package/dataConnectors/clickhouse.ts +0 -343
- package/dataConnectors/mongo.ts +0 -202
- package/dataConnectors/postgres.ts +0 -310
- package/dataConnectors/sqlite.ts +0 -258
- package/index.ts +0 -428
- package/modules/codeInjector.ts +0 -747
- package/modules/configValidator.ts +0 -588
- package/modules/operationalResource.ts +0 -98
- package/modules/restApi.ts +0 -718
- package/modules/styleGenerator.ts +0 -55
- package/modules/styles.ts +0 -126
- package/modules/utils.ts +0 -472
- package/servers/express.ts +0 -259
- package/tsconfig.json +0 -112
- package/types/AdminForthConfig.ts +0 -1762
- package/types/FrontendAPI.ts +0 -143
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/audit-log/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/chat-gpt/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/email-password-reset/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/foreign-inline-list/types.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../plugins/two-factors-auth/types.ts"],"names":[],"mappings":""}
|
|
@@ -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.
|
|
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
|
-
}
|