adminforth 1.0.17 → 1.0.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.
- package/dataConnectors/{mongo.js → mongo.ts} +15 -10
- package/dataConnectors/{postgres.js → postgres.ts} +6 -2
- package/dataConnectors/{sqlite.js → sqlite.ts} +6 -2
- package/dist/auth.js +68 -0
- package/dist/dataConnectors/mongo.js +204 -0
- package/dist/dataConnectors/postgres.js +298 -0
- package/dist/dataConnectors/sqlite.js +261 -0
- package/dist/index.js +693 -0
- package/dist/modules/codeInjector.js +337 -0
- package/dist/modules/utils.js +12 -0
- package/dist/servers/express.js +210 -0
- package/dist/spa/src/main.js +16 -0
- package/dist/spa/src/router/index.js +79 -0
- package/dist/spa/src/stores/core.js +154 -0
- package/dist/spa/src/stores/modal.js +35 -0
- package/dist/spa/src/utils.js +59 -0
- package/dist/spa/vite.config.js +44 -0
- package/dist/spa_tmp/src/custom/custom/vueUses.js +10 -0
- package/dist/spa_tmp/src/main.js +29 -0
- package/dist/spa_tmp/src/router/index.js +83 -0
- package/dist/spa_tmp/src/stores/core.js +150 -0
- package/dist/spa_tmp/src/stores/modal.js +35 -0
- package/dist/spa_tmp/src/utils.js +59 -0
- package/dist/spa_tmp/vite.config.js +43 -0
- package/dist/types.js +30 -0
- package/{index.js → index.ts} +115 -6
- package/modules/{codeInjector.js → codeInjector.ts} +17 -13
- package/package.json +9 -3
- package/servers/{express.js → express.ts} +8 -2
- package/spa/package.json +2 -2
- package/spa/src/views/ListView.vue +1 -1
- package/tsconfig.json +112 -0
- package/{types.js → types.ts} +2 -0
- /package/{auth.js → auth.ts} +0 -0
- /package/modules/{utils.js → utils.ts} +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
11
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
12
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
13
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
14
|
+
};
|
|
15
|
+
var _a, _AdminForth_defaultConfig;
|
|
16
|
+
import Auth from './auth.js';
|
|
17
|
+
import MongoConnector from './dataConnectors/mongo.js';
|
|
18
|
+
import PostgresConnector from './dataConnectors/postgres.js';
|
|
19
|
+
import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
20
|
+
import CodeInjector from './modules/codeInjector.js';
|
|
21
|
+
import { guessLabelFromName } from './modules/utils.js';
|
|
22
|
+
import ExpressServer from './servers/express.js';
|
|
23
|
+
import { v1 as uuid } from 'uuid';
|
|
24
|
+
import fs from 'fs';
|
|
25
|
+
import { AdminForthFilterOperators, AdminForthTypes } from './types.js';
|
|
26
|
+
const AVAILABLE_SHOW_IN = ['list', 'edit', 'create', 'filter', 'show'];
|
|
27
|
+
class AdminForth {
|
|
28
|
+
constructor(config) {
|
|
29
|
+
_AdminForth_defaultConfig.set(this, {
|
|
30
|
+
deleteConfirmation: true,
|
|
31
|
+
});
|
|
32
|
+
this.config = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _AdminForth_defaultConfig, "f")), config);
|
|
33
|
+
this.validateConfig();
|
|
34
|
+
this.express = new ExpressServer(this);
|
|
35
|
+
this.auth = new Auth();
|
|
36
|
+
this.codeInjector = new CodeInjector(this);
|
|
37
|
+
this.connectors = {};
|
|
38
|
+
this.statuses = {};
|
|
39
|
+
}
|
|
40
|
+
validateConfig() {
|
|
41
|
+
if (this.config.rootUser) {
|
|
42
|
+
if (!this.config.rootUser.username) {
|
|
43
|
+
throw new Error('rootUser.username is required');
|
|
44
|
+
}
|
|
45
|
+
if (!this.config.rootUser.password) {
|
|
46
|
+
throw new Error('rootUser.password is required');
|
|
47
|
+
}
|
|
48
|
+
console.log('\n ⚠️⚠️⚠️ [INSECURE ALERT] config.rootUser is set, please create a new user and remove config.rootUser from config before going to production\n');
|
|
49
|
+
}
|
|
50
|
+
if (this.config.auth) {
|
|
51
|
+
if (!this.config.auth.resourceId) {
|
|
52
|
+
throw new Error('No config.auth.resourceId defined');
|
|
53
|
+
}
|
|
54
|
+
if (!this.config.auth.passwordHashField) {
|
|
55
|
+
throw new Error('No config.auth.passwordHashField defined');
|
|
56
|
+
}
|
|
57
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
58
|
+
if (!userResource) {
|
|
59
|
+
throw new Error(`Resource with id "${this.config.auth.resourceId}" not found`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!this.config.customization) {
|
|
63
|
+
this.config.customization = {};
|
|
64
|
+
}
|
|
65
|
+
if (!this.config.customization.customComponentsDir) {
|
|
66
|
+
this.config.customization.customComponentsDir = './custom';
|
|
67
|
+
}
|
|
68
|
+
const errors = [];
|
|
69
|
+
if (!this.config.baseUrl) {
|
|
70
|
+
this.config.baseUrl = '';
|
|
71
|
+
}
|
|
72
|
+
if (!this.config.brandName) {
|
|
73
|
+
this.config.brandName = 'AdminForth';
|
|
74
|
+
}
|
|
75
|
+
if (!this.config.datesFormat) {
|
|
76
|
+
this.config.datesFormat = 'MMM D, YYYY HH:mm:ss';
|
|
77
|
+
}
|
|
78
|
+
if (this.config.resources) {
|
|
79
|
+
this.config.resources.forEach((res) => {
|
|
80
|
+
var _b, _c;
|
|
81
|
+
if (!res.table) {
|
|
82
|
+
errors.push(`Resource "${res.dataSource}" is missing table`);
|
|
83
|
+
}
|
|
84
|
+
// if itemLabel is not callable, throw error
|
|
85
|
+
if (res.itemLabel && typeof res.itemLabel !== 'function') {
|
|
86
|
+
errors.push(`Resource "${res.dataSource}" itemLabel is not a function`);
|
|
87
|
+
}
|
|
88
|
+
res.resourceId = res.resourceId || res.table;
|
|
89
|
+
res.label = res.label || res.table.charAt(0).toUpperCase() + res.table.slice(1);
|
|
90
|
+
if (!res.dataSource) {
|
|
91
|
+
errors.push(`Resource "${res.resourceId}" is missing dataSource`);
|
|
92
|
+
}
|
|
93
|
+
if (!res.columns) {
|
|
94
|
+
res.columns = [];
|
|
95
|
+
}
|
|
96
|
+
res.columns.forEach((col) => {
|
|
97
|
+
var _b;
|
|
98
|
+
col.label = col.label || guessLabelFromName(col.name);
|
|
99
|
+
//define default sortable
|
|
100
|
+
if (!Object.keys(col).includes('sortable')) {
|
|
101
|
+
col.sortable = true;
|
|
102
|
+
}
|
|
103
|
+
if (col.showIn && !Array.isArray(col.showIn)) {
|
|
104
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" showIn must be an array`);
|
|
105
|
+
}
|
|
106
|
+
// check col.required is string or object
|
|
107
|
+
if (col.required && !((typeof col.required === 'boolean') || (typeof col.required === 'object'))) {
|
|
108
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" required must be a string or object`);
|
|
109
|
+
}
|
|
110
|
+
// if it is object check the keys are one of ['create', 'edit']
|
|
111
|
+
if (typeof col.required === 'object') {
|
|
112
|
+
const wrongRequiredOn = Object.keys(col.required).find((c) => !['create', 'edit'].includes(c));
|
|
113
|
+
if (wrongRequiredOn) {
|
|
114
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid required value "${wrongRequiredOn}", allowed keys are 'create', 'edit']`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// same for editingNote
|
|
118
|
+
if (col.editingNote && !((typeof col.editingNote === 'string') || (typeof col.editingNote === 'object'))) {
|
|
119
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" editingNote must be a string or object`);
|
|
120
|
+
}
|
|
121
|
+
if (typeof col.editingNote === 'object') {
|
|
122
|
+
const wrongEditingNoteOn = Object.keys(col.editingNote).find((c) => !['create', 'edit'].includes(c));
|
|
123
|
+
if (wrongEditingNoteOn) {
|
|
124
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid editingNote value "${wrongEditingNoteOn}", allowed keys are 'create', 'edit']`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const wrongShowIn = col.showIn && col.showIn.find((c) => !AVAILABLE_SHOW_IN.includes(c));
|
|
128
|
+
if (wrongShowIn) {
|
|
129
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid showIn value "${wrongShowIn}", allowed values are ${AVAILABLE_SHOW_IN.join(', ')}`);
|
|
130
|
+
}
|
|
131
|
+
col.showIn = ((_b = col.showIn) === null || _b === void 0 ? void 0 : _b.map(c => c.toLowerCase())) || AVAILABLE_SHOW_IN;
|
|
132
|
+
});
|
|
133
|
+
//check if resource has bulkActions
|
|
134
|
+
if ((_b = res.options) === null || _b === void 0 ? void 0 : _b.bulkActions) {
|
|
135
|
+
let bulkActions = res.options.bulkActions;
|
|
136
|
+
if (!Array.isArray(bulkActions)) {
|
|
137
|
+
errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
|
|
138
|
+
bulkActions = [];
|
|
139
|
+
}
|
|
140
|
+
if ((_c = res.options) === null || _c === void 0 ? void 0 : _c.allowDelete) {
|
|
141
|
+
bulkActions.push({
|
|
142
|
+
label: `Delete checked`,
|
|
143
|
+
state: 'danger',
|
|
144
|
+
icon: 'flowbite:trash-bin-outline',
|
|
145
|
+
action: (_d) => __awaiter(this, [_d], void 0, function* ({ selectedIds }) {
|
|
146
|
+
const connector = this.connectors[res.dataSource];
|
|
147
|
+
yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
yield connector.deleteRecord({ resource: res, recordId });
|
|
149
|
+
})));
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const newBulkActions = bulkActions.map((action) => {
|
|
154
|
+
return Object.assign(action, { id: uuid() });
|
|
155
|
+
});
|
|
156
|
+
bulkActions = newBulkActions;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!this.config.menu) {
|
|
160
|
+
errors.push('No config.menu defined');
|
|
161
|
+
}
|
|
162
|
+
// check if there is only one homepage: true in menu, recursivly
|
|
163
|
+
let homepages = 0;
|
|
164
|
+
const browseMenu = (menu) => {
|
|
165
|
+
menu.forEach((item) => {
|
|
166
|
+
if (item.component && item.resourceId) {
|
|
167
|
+
errors.push(`Menu item cannot have both component and resourceId: ${JSON.stringify(item)}`);
|
|
168
|
+
}
|
|
169
|
+
if (item.component && !item.path) {
|
|
170
|
+
errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
|
|
171
|
+
}
|
|
172
|
+
// make sure component starts with @@
|
|
173
|
+
if (item.component) {
|
|
174
|
+
if (!item.component.startsWith('@@')) {
|
|
175
|
+
errors.push(`Menu item component must start with @@ : ${JSON.stringify(item)}`);
|
|
176
|
+
}
|
|
177
|
+
const path = item.component.replace('@@', this.config.customization.customComponentsDir);
|
|
178
|
+
if (!fs.existsSync(path)) {
|
|
179
|
+
errors.push(`Menu item component "${item.component.replace('@@', '')}" does not exist in "${this.config.customization.customComponentsDir}"`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (item.homepage) {
|
|
183
|
+
homepages++;
|
|
184
|
+
if (homepages > 1) {
|
|
185
|
+
errors.push('There must be only one homepage: true in menu, found second one in ' + JSON.stringify(item));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (item.children) {
|
|
189
|
+
browseMenu(item.children);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
browseMenu(this.config.menu);
|
|
194
|
+
}
|
|
195
|
+
// check for duplicate resourceIds and show which ones are duplicated
|
|
196
|
+
const resourceIds = this.config.resources.map((res) => res.resourceId);
|
|
197
|
+
const uniqueResourceIds = new Set(resourceIds);
|
|
198
|
+
if (uniqueResourceIds.size != resourceIds.length) {
|
|
199
|
+
const duplicates = resourceIds.filter((item, index) => resourceIds.indexOf(item) != index);
|
|
200
|
+
errors.push(`Duplicate fields "resourceId" or "table": ${duplicates.join(', ')}`);
|
|
201
|
+
}
|
|
202
|
+
//add ids for onSelectedAllActions for each resource
|
|
203
|
+
if (errors.length > 0) {
|
|
204
|
+
throw new Error(`Invalid AdminForth config: ${errors.join(', ')}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
postProcessAfterDiscover(resource) {
|
|
208
|
+
resource.columns.forEach((column) => {
|
|
209
|
+
// if db/user says column is required in boolean, exapd
|
|
210
|
+
if (typeof column.required === 'boolean') {
|
|
211
|
+
column.required = { create: column.required, edit: column.required };
|
|
212
|
+
}
|
|
213
|
+
// same for editingNote
|
|
214
|
+
if (typeof column.editingNote === 'string') {
|
|
215
|
+
column.editingNote = { create: column.editingNote, edit: column.editingNote };
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
resource.dataSourceColumns = resource.columns.filter((col) => !col.virtual);
|
|
219
|
+
}
|
|
220
|
+
discoverDatabases() {
|
|
221
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
222
|
+
this.statuses.dbDiscover = 'running';
|
|
223
|
+
this.connectorClasses = {
|
|
224
|
+
'sqlite': SQLiteConnector,
|
|
225
|
+
'postgres': PostgresConnector,
|
|
226
|
+
'mongodb': MongoConnector,
|
|
227
|
+
};
|
|
228
|
+
if (!this.config.databaseConnectors) {
|
|
229
|
+
this.config.databaseConnectors = Object.assign({}, this.connectorClasses);
|
|
230
|
+
}
|
|
231
|
+
this.config.dataSources.forEach((ds) => {
|
|
232
|
+
const dbType = ds.url.split(':')[0];
|
|
233
|
+
if (!this.config.databaseConnectors[dbType]) {
|
|
234
|
+
throw new Error(`Database type ${dbType} is not supported, consider using databaseConnectors in AdminForth config`);
|
|
235
|
+
}
|
|
236
|
+
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({ url: ds.url, fieldtypesByTable: ds.fieldtypesByTable });
|
|
237
|
+
});
|
|
238
|
+
yield Promise.all(this.config.resources.map((res) => __awaiter(this, void 0, void 0, function* () {
|
|
239
|
+
if (!this.connectors[res.dataSource]) {
|
|
240
|
+
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
|
|
241
|
+
}
|
|
242
|
+
const fieldTypes = yield this.connectors[res.dataSource].discoverFields(res);
|
|
243
|
+
if (!Object.keys(fieldTypes).length) {
|
|
244
|
+
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
245
|
+
}
|
|
246
|
+
if (!res.columns) {
|
|
247
|
+
res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
|
|
248
|
+
}
|
|
249
|
+
res.columns.forEach((col, i) => {
|
|
250
|
+
if (!fieldTypes[col.name] && !col.virtual) {
|
|
251
|
+
throw new Error(`Resource '${res.table}' has no column '${col.name}'`);
|
|
252
|
+
}
|
|
253
|
+
// first find discovered values, but allow override
|
|
254
|
+
res.columns[i] = Object.assign(Object.assign({}, fieldTypes[col.name]), col);
|
|
255
|
+
});
|
|
256
|
+
this.postProcessAfterDiscover(res);
|
|
257
|
+
// check if primaryKey column is present
|
|
258
|
+
if (!res.columns.some((col) => col.primaryKey)) {
|
|
259
|
+
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`);
|
|
260
|
+
}
|
|
261
|
+
})));
|
|
262
|
+
this.statuses.dbDiscover = 'done';
|
|
263
|
+
// console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
init() {
|
|
267
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
268
|
+
console.log('AdminForth init');
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
bundleNow(_b) {
|
|
272
|
+
return __awaiter(this, arguments, void 0, function* ({ hotReload = false, verbose = false }) {
|
|
273
|
+
this.codeInjector.bundleNow({ hotReload, verbose });
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
setupEndpoints(server) {
|
|
277
|
+
server.endpoint({
|
|
278
|
+
noAuth: true,
|
|
279
|
+
method: 'POST',
|
|
280
|
+
path: '/login',
|
|
281
|
+
handler: (_b) => __awaiter(this, [_b], void 0, function* ({ body, response }) {
|
|
282
|
+
const INVALID_MESSAGE = 'Invalid username or password';
|
|
283
|
+
const { username, password } = body;
|
|
284
|
+
let token;
|
|
285
|
+
if (username === this.config.rootUser.username && password === this.config.rootUser.password) {
|
|
286
|
+
token = this.auth.issueJWT({ username, pk: null });
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// get resource from db
|
|
290
|
+
if (!this.config.auth) {
|
|
291
|
+
throw new Error('No config.auth defined');
|
|
292
|
+
}
|
|
293
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
294
|
+
const userRecord = yield this.connectors[userResource.dataSource].getData({
|
|
295
|
+
resource: userResource,
|
|
296
|
+
filters: [
|
|
297
|
+
{ field: this.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
|
|
298
|
+
],
|
|
299
|
+
limit: 1,
|
|
300
|
+
offset: 0,
|
|
301
|
+
sort: [],
|
|
302
|
+
}).data[0];
|
|
303
|
+
if (!userRecord) {
|
|
304
|
+
return { error: 'User not found' };
|
|
305
|
+
}
|
|
306
|
+
const passwordHash = userRecord[this.config.auth.passwordHashField];
|
|
307
|
+
console.log('User record', userRecord, passwordHash); // why does it has no hash?
|
|
308
|
+
const valid = yield Auth.verifyPassword(password, passwordHash);
|
|
309
|
+
if (valid) {
|
|
310
|
+
token = this.auth.issueJWT({
|
|
311
|
+
username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return { error: INVALID_MESSAGE };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
319
|
+
return { ok: true };
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
server.endpoint({
|
|
323
|
+
noAuth: true,
|
|
324
|
+
method: 'POST',
|
|
325
|
+
path: '/logout',
|
|
326
|
+
handler: (_c) => __awaiter(this, [_c], void 0, function* ({ response }) {
|
|
327
|
+
response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
|
|
328
|
+
return { ok: true };
|
|
329
|
+
}),
|
|
330
|
+
});
|
|
331
|
+
server.endpoint({
|
|
332
|
+
noAuth: true,
|
|
333
|
+
method: 'GET',
|
|
334
|
+
path: '/get_public_config',
|
|
335
|
+
handler: (_d) => __awaiter(this, [_d], void 0, function* ({ body }) {
|
|
336
|
+
// find resource
|
|
337
|
+
if (!this.config.auth) {
|
|
338
|
+
throw new Error('No config.auth defined');
|
|
339
|
+
}
|
|
340
|
+
const usernameField = this.config.auth.usernameField;
|
|
341
|
+
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
342
|
+
const usernameColumn = resource.columns.find((col) => col.name === usernameField);
|
|
343
|
+
return {
|
|
344
|
+
brandName: this.config.brandName,
|
|
345
|
+
usernameFieldName: usernameColumn.label,
|
|
346
|
+
loginBackgroundImage: this.config.auth.loginBackgroundImage,
|
|
347
|
+
};
|
|
348
|
+
}),
|
|
349
|
+
});
|
|
350
|
+
server.endpoint({
|
|
351
|
+
method: 'GET',
|
|
352
|
+
path: '/get_base_config',
|
|
353
|
+
handler: (_e) => __awaiter(this, [_e], void 0, function* ({ input, adminUser, cookies }) {
|
|
354
|
+
const cookieParsed = this.auth.verify(cookies['adminforth_jwt']);
|
|
355
|
+
let username = '';
|
|
356
|
+
let userFullName = '';
|
|
357
|
+
if (cookieParsed['pk'] == null) {
|
|
358
|
+
username = this.config.rootUser.username;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
362
|
+
const user = yield this.connectors[userResource.dataSource].getData({
|
|
363
|
+
resource: userResource,
|
|
364
|
+
filters: [
|
|
365
|
+
{ field: userResource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: cookieParsed['pk'] },
|
|
366
|
+
],
|
|
367
|
+
limit: 1,
|
|
368
|
+
offset: 0,
|
|
369
|
+
sort: [],
|
|
370
|
+
});
|
|
371
|
+
if (!user.data.length) {
|
|
372
|
+
return { error: 'Unauthorized' };
|
|
373
|
+
}
|
|
374
|
+
username = user.data[0][this.config.auth.usernameField];
|
|
375
|
+
userFullName = user.data[0][this.config.auth.userFullName];
|
|
376
|
+
}
|
|
377
|
+
const userData = {
|
|
378
|
+
[this.config.auth.usernameField]: username,
|
|
379
|
+
[this.config.auth.userFullName]: userFullName
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
user: userData,
|
|
383
|
+
resources: this.config.resources.map((res) => ({
|
|
384
|
+
resourceId: res.resourceId,
|
|
385
|
+
label: res.label,
|
|
386
|
+
})),
|
|
387
|
+
menu: this.config.menu,
|
|
388
|
+
config: {
|
|
389
|
+
brandName: this.config.brandName,
|
|
390
|
+
datesFormat: this.config.datesFormat,
|
|
391
|
+
deleteConfirmation: this.config.deleteConfirmation,
|
|
392
|
+
auth: this.config.auth,
|
|
393
|
+
usernameField: this.config.auth.usernameField,
|
|
394
|
+
},
|
|
395
|
+
adminUser,
|
|
396
|
+
};
|
|
397
|
+
}),
|
|
398
|
+
});
|
|
399
|
+
server.endpoint({
|
|
400
|
+
method: 'POST',
|
|
401
|
+
path: '/get_resource_columns',
|
|
402
|
+
handler: (_f) => __awaiter(this, [_f], void 0, function* ({ body }) {
|
|
403
|
+
const { resourceId } = body;
|
|
404
|
+
if (!this.statuses.dbDiscover) {
|
|
405
|
+
return { error: 'Database discovery not started' };
|
|
406
|
+
}
|
|
407
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
408
|
+
return { error: 'Database discovery is still in progress, please try later' };
|
|
409
|
+
}
|
|
410
|
+
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
411
|
+
if (!resource) {
|
|
412
|
+
return { error: `Resource ${resourceId} not found` };
|
|
413
|
+
}
|
|
414
|
+
return { resource };
|
|
415
|
+
}),
|
|
416
|
+
});
|
|
417
|
+
server.endpoint({
|
|
418
|
+
method: 'POST',
|
|
419
|
+
path: '/get_resource_data',
|
|
420
|
+
handler: (_g) => __awaiter(this, [_g], void 0, function* ({ body }) {
|
|
421
|
+
const { resourceId, limit, offset, filters, sort } = body;
|
|
422
|
+
if (!this.statuses.dbDiscover) {
|
|
423
|
+
return { error: 'Database discovery not started' };
|
|
424
|
+
}
|
|
425
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
426
|
+
return { error: 'Database discovery is still in progress, please try later' };
|
|
427
|
+
}
|
|
428
|
+
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
429
|
+
if (!resource) {
|
|
430
|
+
return { error: `Resource ${resourceId} not found` };
|
|
431
|
+
}
|
|
432
|
+
const data = yield this.connectors[resource.dataSource].getData({
|
|
433
|
+
resource,
|
|
434
|
+
limit,
|
|
435
|
+
offset,
|
|
436
|
+
filters,
|
|
437
|
+
sort,
|
|
438
|
+
});
|
|
439
|
+
return Object.assign(Object.assign({}, data), { options: resource === null || resource === void 0 ? void 0 : resource.options });
|
|
440
|
+
}),
|
|
441
|
+
});
|
|
442
|
+
server.endpoint({
|
|
443
|
+
method: 'POST',
|
|
444
|
+
path: '/get_min_max_for_columns',
|
|
445
|
+
handler: (_h) => __awaiter(this, [_h], void 0, function* ({ body }) {
|
|
446
|
+
const { resourceId } = body;
|
|
447
|
+
if (!this.statuses.dbDiscover) {
|
|
448
|
+
return { error: 'Database discovery not started' };
|
|
449
|
+
}
|
|
450
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
451
|
+
return { error: 'Database discovery is still in progress, please try later' };
|
|
452
|
+
}
|
|
453
|
+
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
454
|
+
if (!resource) {
|
|
455
|
+
return { error: `Resource '${resourceId}' not found` };
|
|
456
|
+
}
|
|
457
|
+
const item = yield this.connectors[resource.dataSource].getMinMaxForColumns({
|
|
458
|
+
resource,
|
|
459
|
+
columns: resource.columns.filter((col) => [
|
|
460
|
+
AdminForthTypes.INTEGER,
|
|
461
|
+
AdminForthTypes.FLOAT,
|
|
462
|
+
AdminForthTypes.DATE,
|
|
463
|
+
AdminForthTypes.DATETIME,
|
|
464
|
+
AdminForthTypes.TIME,
|
|
465
|
+
AdminForthTypes.DECIMAL,
|
|
466
|
+
].includes(col.type) && col.allowMinMaxQuery === true),
|
|
467
|
+
});
|
|
468
|
+
return item;
|
|
469
|
+
}),
|
|
470
|
+
});
|
|
471
|
+
server.endpoint({
|
|
472
|
+
method: 'POST',
|
|
473
|
+
path: '/get_record',
|
|
474
|
+
handler: (_j) => __awaiter(this, [_j], void 0, function* ({ body, adminUser }) {
|
|
475
|
+
var _k, _l;
|
|
476
|
+
const { resourceId, primaryKey } = body;
|
|
477
|
+
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
478
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
479
|
+
const connector = this.connectors[resource.dataSource];
|
|
480
|
+
const record = yield connector.getRecordByPrimaryKey(resource, primaryKey);
|
|
481
|
+
if (!record) {
|
|
482
|
+
return { error: `Record with ${primaryKeyColumn.name} ${primaryKey} not found` };
|
|
483
|
+
}
|
|
484
|
+
// execute hook if needed
|
|
485
|
+
if ((_k = resource.hooks) === null || _k === void 0 ? void 0 : _k.show) {
|
|
486
|
+
const resp = yield ((_l = resource.hooks) === null || _l === void 0 ? void 0 : _l.show({ resource, record, adminUser }));
|
|
487
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
488
|
+
throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
|
|
489
|
+
}
|
|
490
|
+
if (resp.error) {
|
|
491
|
+
return { error: resp.error };
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const labler = resource.itemLabel || ((record) => `${resource.label} ${record[primaryKeyColumn.name]}`);
|
|
495
|
+
record._label = labler(record);
|
|
496
|
+
return record;
|
|
497
|
+
})
|
|
498
|
+
});
|
|
499
|
+
server.endpoint({
|
|
500
|
+
noAuth: true, // TODO
|
|
501
|
+
method: 'POST',
|
|
502
|
+
path: '/create_record',
|
|
503
|
+
handler: (_m) => __awaiter(this, [_m], void 0, function* ({ body, adminUser }) {
|
|
504
|
+
var _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
505
|
+
console.log('create_record', body, this.config.resources);
|
|
506
|
+
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
507
|
+
if (!resource) {
|
|
508
|
+
return { error: `Resource '${body['resourceId']}' not found` };
|
|
509
|
+
}
|
|
510
|
+
const record = body['record'];
|
|
511
|
+
// execute hook if needed
|
|
512
|
+
if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o.create) === null || _p === void 0 ? void 0 : _p.beforeSave) {
|
|
513
|
+
const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q.create) === null || _r === void 0 ? void 0 : _r.beforeSave({ resource, record, adminUser }));
|
|
514
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
515
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
516
|
+
}
|
|
517
|
+
if (resp.error) {
|
|
518
|
+
return { error: resp.error };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
for (const column of resource.columns) {
|
|
522
|
+
if (column.fillOnCreate) {
|
|
523
|
+
if (body['record'][column.name] === undefined) {
|
|
524
|
+
body['record'][column.name] = column.fillOnCreate({
|
|
525
|
+
initialRecord: body['record'], adminUser
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (((_s = column.required) === null || _s === void 0 ? void 0 : _s.create) && body['record'][column.name] === undefined) {
|
|
530
|
+
return { error: `Column '${column.name}' is required` };
|
|
531
|
+
}
|
|
532
|
+
if (column.isUnique) {
|
|
533
|
+
const existingRecord = yield this.connectors[resource.dataSource].getData({
|
|
534
|
+
resource,
|
|
535
|
+
filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: body['record'][column.name] }],
|
|
536
|
+
limit: 1,
|
|
537
|
+
sort: [],
|
|
538
|
+
offset: 0
|
|
539
|
+
});
|
|
540
|
+
if (existingRecord.data.length > 0) {
|
|
541
|
+
return { error: `Record with ${column.name} ${body['record'][column.name]} already exists` };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// remove virtual columns from record
|
|
546
|
+
for (const column of resource.columns.filter((col) => col.virtual)) {
|
|
547
|
+
if (record[column.name]) {
|
|
548
|
+
delete record[column.name];
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const connector = this.connectors[resource.dataSource];
|
|
552
|
+
yield connector.createRecord({ resource, record });
|
|
553
|
+
// execute hook if needed
|
|
554
|
+
if ((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t.create) === null || _u === void 0 ? void 0 : _u.afterSave) {
|
|
555
|
+
const resp = yield ((_w = (_v = resource.hooks) === null || _v === void 0 ? void 0 : _v.create) === null || _w === void 0 ? void 0 : _w.afterSave({ resource, record, adminUser }));
|
|
556
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
557
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
558
|
+
}
|
|
559
|
+
if (resp.error) {
|
|
560
|
+
return { error: resp.error };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
newRecordId: body['record'][connector.getPrimaryKey(resource)]
|
|
565
|
+
};
|
|
566
|
+
})
|
|
567
|
+
});
|
|
568
|
+
server.endpoint({
|
|
569
|
+
noAuth: true, // TODO
|
|
570
|
+
method: 'POST',
|
|
571
|
+
path: '/update_record',
|
|
572
|
+
handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
|
|
573
|
+
var _y, _z, _0, _1, _2, _3, _4, _5;
|
|
574
|
+
console.log('update_record', body);
|
|
575
|
+
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
576
|
+
if (!resource) {
|
|
577
|
+
return { error: `Resource '${body['resourceId']}' not found` };
|
|
578
|
+
}
|
|
579
|
+
const recordId = body['recordId'];
|
|
580
|
+
const connector = this.connectors[resource.dataSource];
|
|
581
|
+
const oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
582
|
+
if (!oldRecord) {
|
|
583
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
584
|
+
return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
|
|
585
|
+
}
|
|
586
|
+
const record = body['record'];
|
|
587
|
+
// execute hook if needed
|
|
588
|
+
if ((_z = (_y = resource.hooks) === null || _y === void 0 ? void 0 : _y.edit) === null || _z === void 0 ? void 0 : _z.beforeSave) {
|
|
589
|
+
const resp = yield ((_1 = (_0 = resource.hooks) === null || _0 === void 0 ? void 0 : _0.edit) === null || _1 === void 0 ? void 0 : _1.beforeSave({ resource, record, adminUser }));
|
|
590
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
591
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
592
|
+
}
|
|
593
|
+
if (resp.error) {
|
|
594
|
+
return { error: resp.error };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const newValues = {};
|
|
598
|
+
for (const col of resource.columns.filter((col) => !col.virtual)) {
|
|
599
|
+
if (record[col.name] !== oldRecord[col.name]) {
|
|
600
|
+
newValues[col.name] = connector.setFieldValue(col, record[col.name]);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (Object.keys(newValues).length > 0) {
|
|
604
|
+
yield connector.updateRecord({ resource, recordId, record, newValues });
|
|
605
|
+
}
|
|
606
|
+
// execute hook if needed
|
|
607
|
+
if ((_3 = (_2 = resource.hooks) === null || _2 === void 0 ? void 0 : _2.edit) === null || _3 === void 0 ? void 0 : _3.afterSave) {
|
|
608
|
+
const resp = yield ((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.edit) === null || _5 === void 0 ? void 0 : _5.afterSave({ resource, record, adminUser }));
|
|
609
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
610
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
611
|
+
}
|
|
612
|
+
if (resp.error) {
|
|
613
|
+
return { error: resp.error };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
newRecordId: recordId
|
|
618
|
+
};
|
|
619
|
+
})
|
|
620
|
+
});
|
|
621
|
+
server.endpoint({
|
|
622
|
+
noAuth: true, // TODO
|
|
623
|
+
method: 'POST',
|
|
624
|
+
path: '/delete_record',
|
|
625
|
+
handler: (_6) => __awaiter(this, [_6], void 0, function* ({ body, adminUser }) {
|
|
626
|
+
var _7, _8, _9, _10, _11, _12, _13, _14;
|
|
627
|
+
const resource = this.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
628
|
+
const record = yield this.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
|
|
629
|
+
if (!resource) {
|
|
630
|
+
return { error: `Resource '${body['resourceId']}' not found` };
|
|
631
|
+
}
|
|
632
|
+
// execute hook if needed
|
|
633
|
+
if ((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.delete) === null || _8 === void 0 ? void 0 : _8.beforeSave) {
|
|
634
|
+
const resp = yield ((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.delete) === null || _10 === void 0 ? void 0 : _10.beforeSave({ resource, record, adminUser }));
|
|
635
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
636
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
637
|
+
}
|
|
638
|
+
if (resp.error) {
|
|
639
|
+
return { error: resp.error };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const connector = this.connectors[resource.dataSource];
|
|
643
|
+
yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
|
|
644
|
+
// execute hook if needed
|
|
645
|
+
if ((_12 = (_11 = resource.hooks) === null || _11 === void 0 ? void 0 : _11.delete) === null || _12 === void 0 ? void 0 : _12.afterSave) {
|
|
646
|
+
const resp = yield ((_14 = (_13 = resource.hooks) === null || _13 === void 0 ? void 0 : _13.delete) === null || _14 === void 0 ? void 0 : _14.afterSave({ resource, record, adminUser }));
|
|
647
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
648
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
649
|
+
}
|
|
650
|
+
if (resp.error) {
|
|
651
|
+
return { error: resp.error };
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
recordId: body['primaryKey']
|
|
656
|
+
};
|
|
657
|
+
})
|
|
658
|
+
});
|
|
659
|
+
server.endpoint({
|
|
660
|
+
noAuth: true, // TODO
|
|
661
|
+
method: 'POST',
|
|
662
|
+
path: '/start_bulk_action',
|
|
663
|
+
handler: (_15) => __awaiter(this, [_15], void 0, function* ({ body }) {
|
|
664
|
+
const { resourceId, actionId, recordIds } = body;
|
|
665
|
+
const resource = this.config.resources.find((res) => res.resourceId == resourceId);
|
|
666
|
+
if (!resource) {
|
|
667
|
+
return { error: `Resource '${resourceId}' not found` };
|
|
668
|
+
}
|
|
669
|
+
const action = resource.options.bulkActions.find((act) => act.id == actionId);
|
|
670
|
+
if (!action) {
|
|
671
|
+
return { error: `Action '${actionId}' not found` };
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
yield action.action({ selectedIds: recordIds });
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
actionId,
|
|
678
|
+
recordIds,
|
|
679
|
+
resourceId,
|
|
680
|
+
status: 'success'
|
|
681
|
+
};
|
|
682
|
+
})
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
_a = AdminForth, _AdminForth_defaultConfig = new WeakMap();
|
|
687
|
+
AdminForth.Types = AdminForthTypes;
|
|
688
|
+
AdminForth.Utils = {
|
|
689
|
+
generatePasswordHash: (password) => __awaiter(void 0, void 0, void 0, function* () {
|
|
690
|
+
return yield Auth.generatePasswordHash(password);
|
|
691
|
+
})
|
|
692
|
+
};
|
|
693
|
+
export default AdminForth;
|