adminforth 1.2.98 → 1.2.100
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/auth.ts +6 -5
- package/dataConnectors/baseConnector.ts +91 -21
- package/dataConnectors/clickhouse.ts +58 -37
- package/dataConnectors/mongo.ts +44 -14
- package/dataConnectors/postgres.ts +53 -36
- package/dataConnectors/sqlite.ts +44 -35
- package/dist/auth.js +6 -6
- package/dist/dataConnectors/baseConnector.js +75 -17
- package/dist/dataConnectors/clickhouse.js +59 -48
- package/dist/dataConnectors/mongo.js +28 -12
- package/dist/dataConnectors/postgres.js +62 -52
- package/dist/dataConnectors/sqlite.js +62 -45
- package/dist/index.js +52 -33
- package/dist/modules/configValidator.js +25 -16
- package/dist/modules/operationalResource.js +88 -0
- package/dist/modules/restApi.js +106 -96
- package/dist/modules/utils.js +16 -0
- package/dist/types/AdminForthConfig.js +37 -0
- package/index.ts +76 -43
- package/modules/configValidator.ts +26 -21
- package/modules/operationalResource.ts +91 -0
- package/modules/restApi.ts +91 -80
- package/modules/utils.ts +20 -0
- package/package.json +3 -2
- package/spa/src/App.vue +12 -9
- package/spa/src/components/Filters.vue +1 -1
- package/spa/src/components/ResourceForm.vue +7 -4
- package/spa/src/components/ResourceListTable.vue +124 -112
- package/spa/src/components/SkeleteLoader.vue +4 -4
- package/spa/src/components/ValueRenderer.vue +1 -1
- package/spa/src/router/index.ts +0 -8
- package/spa/src/spa_types/core.ts +1 -0
- package/spa/src/stores/filters.ts +3 -2
- package/spa/src/views/ListView.vue +16 -25
- package/spa/src/views/LoginView.vue +26 -9
- package/types/AdminForthConfig.ts +140 -38
|
@@ -75,6 +75,10 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
75
75
|
field.type = AdminForthDataTypes.BOOLEAN;
|
|
76
76
|
field._underlineType = 'boolean';
|
|
77
77
|
}
|
|
78
|
+
else if (baseType == 'datetime') {
|
|
79
|
+
field.type = AdminForthDataTypes.DATETIME;
|
|
80
|
+
field._underlineType = 'datetime';
|
|
81
|
+
}
|
|
78
82
|
else {
|
|
79
83
|
field.type = 'unknown';
|
|
80
84
|
}
|
|
@@ -98,6 +102,9 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
98
102
|
else if (field._underlineType == 'varchar') {
|
|
99
103
|
return dayjs(value).toISOString();
|
|
100
104
|
}
|
|
105
|
+
else if (field._underlineType == 'datetime') {
|
|
106
|
+
return dayjs(value).toISOString();
|
|
107
|
+
}
|
|
101
108
|
else {
|
|
102
109
|
throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps). Issue in field "${field.name}"`);
|
|
103
110
|
}
|
|
@@ -134,6 +141,9 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
134
141
|
// value is iso string now, convert to unix timestamp
|
|
135
142
|
return dayjs(value).toISOString();
|
|
136
143
|
}
|
|
144
|
+
else {
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
137
147
|
}
|
|
138
148
|
else if (field.type == AdminForthDataTypes.BOOLEAN) {
|
|
139
149
|
return value ? 1 : 0;
|
|
@@ -149,39 +159,44 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
149
159
|
}
|
|
150
160
|
return value;
|
|
151
161
|
}
|
|
162
|
+
whereClause(filters) {
|
|
163
|
+
return filters.length ? `WHERE ${filters.map((f, i) => {
|
|
164
|
+
let placeholder = '?';
|
|
165
|
+
let field = f.field;
|
|
166
|
+
let operator = this.OperatorsMap[f.operator];
|
|
167
|
+
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
168
|
+
placeholder = `(${f.value.map(() => '?').join(', ')})`;
|
|
169
|
+
}
|
|
170
|
+
else if (f.operator == AdminForthFilterOperators.ILIKE) {
|
|
171
|
+
placeholder = `LOWER(?)`;
|
|
172
|
+
field = `LOWER(${f.field})`;
|
|
173
|
+
operator = 'LIKE';
|
|
174
|
+
}
|
|
175
|
+
return `${field} ${operator} ${placeholder}`;
|
|
176
|
+
}).join(' AND ')}` : '';
|
|
177
|
+
}
|
|
178
|
+
whereParams(filters) {
|
|
179
|
+
return filters.reduce((acc, f) => {
|
|
180
|
+
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
181
|
+
acc.push(`%${f.value}%`);
|
|
182
|
+
}
|
|
183
|
+
else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
184
|
+
acc.push(...f.value);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
acc.push(f.value);
|
|
188
|
+
}
|
|
189
|
+
return acc;
|
|
190
|
+
}, []);
|
|
191
|
+
}
|
|
152
192
|
getDataWithOriginalTypes(_a) {
|
|
153
|
-
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters
|
|
193
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
|
|
154
194
|
const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
|
|
155
195
|
const tableName = resource.table;
|
|
156
|
-
const where =
|
|
157
|
-
|
|
158
|
-
let field = f.field;
|
|
159
|
-
let operator = this.OperatorsMap[f.operator];
|
|
160
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
161
|
-
placeholder = `(${f.value.map(() => '?').join(', ')})`;
|
|
162
|
-
}
|
|
163
|
-
else if (f.operator == AdminForthFilterOperators.ILIKE) {
|
|
164
|
-
placeholder = `LOWER(?)`;
|
|
165
|
-
field = `LOWER(${f.field})`;
|
|
166
|
-
operator = 'LIKE';
|
|
167
|
-
}
|
|
168
|
-
return `${field} ${operator} ${placeholder}`;
|
|
169
|
-
}).join(' AND ')}` : '';
|
|
170
|
-
const filterValues = [];
|
|
171
|
-
filters.length ? filters.forEach((f) => {
|
|
172
|
-
// for arrays do set in map
|
|
173
|
-
const v = f.value;
|
|
174
|
-
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
175
|
-
filterValues.push(`%${v}%`);
|
|
176
|
-
}
|
|
177
|
-
else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
178
|
-
filterValues.push(...v);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
filterValues.push(v);
|
|
182
|
-
}
|
|
183
|
-
}) : [];
|
|
196
|
+
const where = this.whereClause(filters);
|
|
197
|
+
const filterValues = this.whereParams(filters);
|
|
184
198
|
const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
|
|
199
|
+
console.log('🪲 SQLITE Query', `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`, 'params:', [...filterValues, limit, offset]);
|
|
185
200
|
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
|
|
186
201
|
const stmt = this.db.prepare(q);
|
|
187
202
|
const d = [...filterValues, limit, offset];
|
|
@@ -189,21 +204,22 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
189
204
|
console.log('🪲 SQLITE Query', q, 'params:', d);
|
|
190
205
|
}
|
|
191
206
|
const rows = yield stmt.all(d);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
};
|
|
207
|
+
return rows.map((row) => {
|
|
208
|
+
const newRow = {};
|
|
209
|
+
for (const [key, value] of Object.entries(row)) {
|
|
210
|
+
newRow[key] = value;
|
|
211
|
+
}
|
|
212
|
+
return newRow;
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
getCount(_a) {
|
|
217
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, filters }) {
|
|
218
|
+
const tableName = resource.table;
|
|
219
|
+
const where = this.whereClause(filters);
|
|
220
|
+
const filterValues = this.whereParams(filters);
|
|
221
|
+
const totalStmt = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`);
|
|
222
|
+
return totalStmt.get([...filterValues])['COUNT(*)'];
|
|
207
223
|
});
|
|
208
224
|
}
|
|
209
225
|
getMinMaxForColumnsWithOriginalTypes(_a) {
|
|
@@ -241,7 +257,8 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
241
257
|
deleteRecord(_a) {
|
|
242
258
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
|
|
243
259
|
const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
244
|
-
yield q.run(recordId);
|
|
260
|
+
const res = yield q.run(recordId);
|
|
261
|
+
return res.changes > 0;
|
|
245
262
|
});
|
|
246
263
|
}
|
|
247
264
|
close() {
|
package/dist/index.js
CHANGED
|
@@ -19,16 +19,18 @@ import PostgresConnector from './dataConnectors/postgres.js';
|
|
|
19
19
|
import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
20
20
|
import CodeInjector from './modules/codeInjector.js';
|
|
21
21
|
import ExpressServer from './servers/express.js';
|
|
22
|
-
import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
|
|
22
|
+
import { ADMINFORTH_VERSION, listify, suggestIfTypo } from './modules/utils.js';
|
|
23
23
|
import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, } from './types/AdminForthConfig.js';
|
|
24
24
|
import AdminForthPlugin from './basePlugin.js';
|
|
25
25
|
import ConfigValidator from './modules/configValidator.js';
|
|
26
26
|
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
27
27
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
28
|
+
import OperationalResource from './modules/operationalResource.js';
|
|
28
29
|
// exports
|
|
29
30
|
export * from './types/AdminForthConfig.js';
|
|
30
31
|
export { interpretResource };
|
|
31
32
|
export { AdminForthPlugin };
|
|
33
|
+
export { suggestIfTypo };
|
|
32
34
|
class AdminForth {
|
|
33
35
|
constructor(config) {
|
|
34
36
|
_AdminForth_defaultConfig.set(this, {
|
|
@@ -85,20 +87,22 @@ class AdminForth {
|
|
|
85
87
|
this.config.dataSources.forEach((ds) => {
|
|
86
88
|
const dbType = ds.url.split(':')[0];
|
|
87
89
|
if (!this.config.databaseConnectors[dbType]) {
|
|
88
|
-
throw new Error(`Database type ${dbType} is not supported, consider using
|
|
90
|
+
throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
|
|
89
91
|
}
|
|
90
92
|
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({ url: ds.url });
|
|
91
93
|
});
|
|
92
94
|
yield Promise.all(this.config.resources.map((res) => __awaiter(this, void 0, void 0, function* () {
|
|
93
95
|
if (!this.connectors[res.dataSource]) {
|
|
94
|
-
|
|
96
|
+
const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
|
|
97
|
+
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
|
|
98
|
+
? `. Did you mean '${similar}'?` : 'Available dataSources: ' + Object.keys(this.connectors).join(', ')}`);
|
|
95
99
|
}
|
|
96
100
|
const fieldTypes = yield this.connectors[res.dataSource].discoverFields(res);
|
|
97
101
|
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
98
102
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
99
103
|
}
|
|
100
104
|
if (fieldTypes === null) {
|
|
101
|
-
console.error(
|
|
105
|
+
console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
108
|
if (!res.columns) {
|
|
@@ -106,7 +110,8 @@ class AdminForth {
|
|
|
106
110
|
}
|
|
107
111
|
res.columns.forEach((col, i) => {
|
|
108
112
|
if (!fieldTypes[col.name] && !col.virtual) {
|
|
109
|
-
|
|
113
|
+
const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
|
|
114
|
+
throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
110
115
|
}
|
|
111
116
|
// first find discovered values, but allow override
|
|
112
117
|
res.columns[i] = Object.assign(Object.assign({}, fieldTypes[col.name]), col);
|
|
@@ -118,6 +123,10 @@ class AdminForth {
|
|
|
118
123
|
}
|
|
119
124
|
})));
|
|
120
125
|
this.statuses.dbDiscover = 'done';
|
|
126
|
+
this.operationalResources = {};
|
|
127
|
+
this.config.resources.forEach((resource) => {
|
|
128
|
+
this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
|
|
129
|
+
});
|
|
121
130
|
// console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
|
|
122
131
|
});
|
|
123
132
|
}
|
|
@@ -128,9 +137,11 @@ class AdminForth {
|
|
|
128
137
|
}
|
|
129
138
|
getUserByPk(pk) {
|
|
130
139
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
|
-
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
140
|
+
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
132
141
|
if (!resource) {
|
|
133
|
-
|
|
142
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
|
|
143
|
+
throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
|
|
144
|
+
`Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`);
|
|
134
145
|
}
|
|
135
146
|
const users = yield this.connectors[resource.dataSource].getData({
|
|
136
147
|
resource,
|
|
@@ -148,39 +159,21 @@ class AdminForth {
|
|
|
148
159
|
return __awaiter(this, arguments, void 0, function* ({ resource, record, adminUser }) {
|
|
149
160
|
var _c, _d, _e, _f, _g;
|
|
150
161
|
for (const column of resource.columns) {
|
|
151
|
-
|
|
152
|
-
if (record[column.name] === undefined) {
|
|
153
|
-
record[column.name] = column.fillOnCreate({
|
|
154
|
-
initialRecord: record, adminUser
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
162
|
+
// TODO: assuming specifity for AdminForthResourcePages.create better to move it to api for this button
|
|
158
163
|
if (((_c = column.required) === null || _c === void 0 ? void 0 : _c.create) &&
|
|
159
164
|
record[column.name] === undefined &&
|
|
160
165
|
column.showIn.includes(AdminForthResourcePages.create)) {
|
|
161
|
-
return { error: `Column '${column.name}' is required
|
|
162
|
-
}
|
|
163
|
-
if (column.isUnique) {
|
|
164
|
-
const existingRecord = yield this.connectors[resource.dataSource].getData({
|
|
165
|
-
resource,
|
|
166
|
-
filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: record[column.name] }],
|
|
167
|
-
limit: 1,
|
|
168
|
-
sort: [],
|
|
169
|
-
offset: 0
|
|
170
|
-
});
|
|
171
|
-
if (existingRecord.data.length > 0) {
|
|
172
|
-
return { error: `Record with ${column.name} ${record[column.name]} already exists` };
|
|
173
|
-
}
|
|
166
|
+
return { error: `Column '${column.name}' is required`, ok: false };
|
|
174
167
|
}
|
|
175
168
|
}
|
|
176
169
|
// execute hook if needed
|
|
177
170
|
for (const hook of listify((_e = (_d = resource.hooks) === null || _d === void 0 ? void 0 : _d.create) === null || _e === void 0 ? void 0 : _e.beforeSave)) {
|
|
178
|
-
const resp = yield hook({ resource, record, adminUser });
|
|
171
|
+
const resp = yield hook({ recordId: undefined, resource, record, adminUser });
|
|
179
172
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
180
173
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
181
174
|
}
|
|
182
175
|
if (resp.error) {
|
|
183
|
-
return { error: resp.error };
|
|
176
|
+
return { error: resp.error, ok: false };
|
|
184
177
|
}
|
|
185
178
|
}
|
|
186
179
|
// remove virtual columns from record
|
|
@@ -191,21 +184,47 @@ class AdminForth {
|
|
|
191
184
|
}
|
|
192
185
|
const connector = this.connectors[resource.dataSource];
|
|
193
186
|
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
|
|
194
|
-
yield connector.createRecord({ resource, record });
|
|
187
|
+
const { ok, error, createdRecord } = yield connector.createRecord({ resource, record, adminUser });
|
|
188
|
+
if (!ok) {
|
|
189
|
+
return { ok, error };
|
|
190
|
+
}
|
|
191
|
+
const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
|
|
195
192
|
// execute hook if needed
|
|
196
193
|
for (const hook of listify((_g = (_f = resource.hooks) === null || _f === void 0 ? void 0 : _f.create) === null || _g === void 0 ? void 0 : _g.afterSave)) {
|
|
197
194
|
console.log('Hook afterSave', hook);
|
|
198
|
-
const resp = yield hook({
|
|
195
|
+
const resp = yield hook({
|
|
196
|
+
recordId: primaryKey,
|
|
197
|
+
resource,
|
|
198
|
+
record: createdRecord,
|
|
199
|
+
adminUser
|
|
200
|
+
});
|
|
199
201
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
200
202
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
201
203
|
}
|
|
202
204
|
if (resp.error) {
|
|
203
|
-
return { error: resp.error };
|
|
205
|
+
return { error: resp.error, ok: false };
|
|
204
206
|
}
|
|
205
207
|
}
|
|
206
|
-
return { ok
|
|
208
|
+
return { ok, error, createdRecord };
|
|
207
209
|
});
|
|
208
210
|
}
|
|
211
|
+
resource(resourceId) {
|
|
212
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
213
|
+
if (this.statuses.dbDiscover === 'running') {
|
|
214
|
+
throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n' +
|
|
215
|
+
'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n' +
|
|
219
|
+
'Call admin.discoverDatabases() first and await it before using data API');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!this.operationalResources[resourceId]) {
|
|
223
|
+
const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
|
|
224
|
+
throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
|
|
225
|
+
}
|
|
226
|
+
return this.operationalResources[resourceId];
|
|
227
|
+
}
|
|
209
228
|
setupEndpoints(server) {
|
|
210
229
|
this.restApi.registerEndpoints(server);
|
|
211
230
|
}
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { AdminForthResourcePages, AllowedActionsEnum, } from "../types/AdminForthConfig.js";
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
|
-
import { guessLabelFromName } from './utils.js';
|
|
13
|
+
import { guessLabelFromName, suggestIfTypo } from './utils.js';
|
|
14
14
|
import crypto from 'crypto';
|
|
15
15
|
export default class ConfigValidator {
|
|
16
16
|
constructor(adminforth, config) {
|
|
@@ -47,15 +47,6 @@ export default class ConfigValidator {
|
|
|
47
47
|
validateConfig() {
|
|
48
48
|
var _a;
|
|
49
49
|
const errors = [];
|
|
50
|
-
if (this.config.rootUser) {
|
|
51
|
-
if (!this.config.rootUser.username) {
|
|
52
|
-
throw new Error('rootUser.username is required');
|
|
53
|
-
}
|
|
54
|
-
if (!this.config.rootUser.password) {
|
|
55
|
-
throw new Error('rootUser.password is required');
|
|
56
|
-
}
|
|
57
|
-
console.log('\n ☝️☝️☝️ [INSECURE ALERT] config.rootUser is set, please create a new user to login in backoffice and remove config.rootUser from config ASAP when you are in production\n');
|
|
58
|
-
}
|
|
59
50
|
if (!this.config.customization.customComponentsDir) {
|
|
60
51
|
this.config.customization.customComponentsDir = './custom';
|
|
61
52
|
}
|
|
@@ -67,8 +58,12 @@ export default class ConfigValidator {
|
|
|
67
58
|
this.config.customization.customComponentsDir = undefined;
|
|
68
59
|
}
|
|
69
60
|
if (this.config.auth) {
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
// TODO: remove in future releases
|
|
62
|
+
if (!this.config.auth.usersResourceId && this.config.auth.resourceId) {
|
|
63
|
+
this.config.auth.usersResourceId = this.config.auth.resourceId;
|
|
64
|
+
}
|
|
65
|
+
if (!this.config.auth.usersResourceId) {
|
|
66
|
+
throw new Error('No config.auth.usersResourceId defined');
|
|
72
67
|
}
|
|
73
68
|
if (!this.config.auth.passwordHashField) {
|
|
74
69
|
throw new Error('No config.auth.passwordHashField defined');
|
|
@@ -79,9 +74,10 @@ export default class ConfigValidator {
|
|
|
79
74
|
if (this.config.auth.loginBackgroundImage) {
|
|
80
75
|
errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
|
|
81
76
|
}
|
|
82
|
-
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
77
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
83
78
|
if (!userResource) {
|
|
84
|
-
|
|
79
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId || res.table), this.config.auth.usersResourceId);
|
|
80
|
+
throw new Error(`Resource with id "${this.config.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
85
81
|
}
|
|
86
82
|
if (!this.config.auth.beforeLoginConfirmation) {
|
|
87
83
|
this.config.auth.beforeLoginConfirmation = [];
|
|
@@ -126,6 +122,9 @@ export default class ConfigValidator {
|
|
|
126
122
|
if (this.config.customization.brandLogo) {
|
|
127
123
|
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
128
124
|
}
|
|
125
|
+
if (this.config.customization.showBrandNameInSidebar === undefined) {
|
|
126
|
+
this.config.customization.showBrandNameInSidebar = true;
|
|
127
|
+
}
|
|
129
128
|
if (this.config.customization.favicon) {
|
|
130
129
|
errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
|
|
131
130
|
}
|
|
@@ -254,7 +253,12 @@ export default class ConfigValidator {
|
|
|
254
253
|
yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
255
254
|
const record = yield connector.getRecordByPrimaryKey(res, recordId);
|
|
256
255
|
yield Promise.all(res.hooks.delete.beforeSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
|
|
257
|
-
const resp = yield hook({
|
|
256
|
+
const resp = yield hook({
|
|
257
|
+
recordId: recordId,
|
|
258
|
+
resource: res,
|
|
259
|
+
record,
|
|
260
|
+
adminUser,
|
|
261
|
+
});
|
|
258
262
|
if (!error && resp.error) {
|
|
259
263
|
error = resp.error;
|
|
260
264
|
}
|
|
@@ -265,7 +269,12 @@ export default class ConfigValidator {
|
|
|
265
269
|
yield connector.deleteRecord({ resource: res, recordId });
|
|
266
270
|
// call afterDelete hook
|
|
267
271
|
yield Promise.all(res.hooks.delete.afterSave.map((hook) => __awaiter(this, void 0, void 0, function* () {
|
|
268
|
-
yield hook({
|
|
272
|
+
yield hook({
|
|
273
|
+
resource: res,
|
|
274
|
+
record,
|
|
275
|
+
adminUser,
|
|
276
|
+
recordId: recordId
|
|
277
|
+
});
|
|
269
278
|
})));
|
|
270
279
|
})));
|
|
271
280
|
if (error) {
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
function filtersIfFilter(filter) {
|
|
11
|
+
if (!filter) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return (Array.isArray(filter) ? filter : [filter]);
|
|
15
|
+
}
|
|
16
|
+
function sortsIfSort(sort) {
|
|
17
|
+
return (Array.isArray(sort) ? sort : [sort]);
|
|
18
|
+
}
|
|
19
|
+
export default class OperationalResource {
|
|
20
|
+
constructor(dataConnector, resourceConfig) {
|
|
21
|
+
this.dataConnector = dataConnector;
|
|
22
|
+
this.resourceConfig = resourceConfig;
|
|
23
|
+
}
|
|
24
|
+
get(filter) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
return (yield this.dataConnector.getData({
|
|
27
|
+
resource: this.resourceConfig,
|
|
28
|
+
filters: filtersIfFilter(filter),
|
|
29
|
+
limit: 1,
|
|
30
|
+
offset: 0,
|
|
31
|
+
sort: [],
|
|
32
|
+
})).data[0] || null;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
list(filter, limit, offset, sort) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
let appliedLimit = limit;
|
|
38
|
+
if (limit === null) {
|
|
39
|
+
appliedLimit = 1000000000;
|
|
40
|
+
}
|
|
41
|
+
let appliedOffset = offset;
|
|
42
|
+
if (offset === null) {
|
|
43
|
+
appliedOffset = 0;
|
|
44
|
+
}
|
|
45
|
+
const { data } = yield this.dataConnector.getData({
|
|
46
|
+
resource: this.resourceConfig,
|
|
47
|
+
filters: filtersIfFilter(filter),
|
|
48
|
+
limit: appliedLimit,
|
|
49
|
+
offset: appliedOffset,
|
|
50
|
+
sort: sortsIfSort(sort),
|
|
51
|
+
getTotals: false,
|
|
52
|
+
});
|
|
53
|
+
return data;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
count(filter) {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
return yield this.dataConnector.getCount({
|
|
59
|
+
resource: this.resourceConfig,
|
|
60
|
+
filters: filtersIfFilter(filter),
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
create(recordValues) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const { ok, createdRecord, error } = yield this.dataConnector.createRecord({
|
|
67
|
+
resource: this.resourceConfig,
|
|
68
|
+
record: recordValues,
|
|
69
|
+
adminUser: null
|
|
70
|
+
});
|
|
71
|
+
return { ok, createdRecord, error };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
update(primaryKey, record) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
return yield this.dataConnector.updateRecord({
|
|
77
|
+
resource: this.resourceConfig,
|
|
78
|
+
recordId: primaryKey,
|
|
79
|
+
newValues: record
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
delete(primaryKey) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
return yield this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|