adminforth 1.3.27 → 1.3.28
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/baseConnector.ts +19 -7
- package/dataConnectors/clickhouse.ts +1 -2
- package/dataConnectors/mongo.ts +1 -1
- package/dataConnectors/postgres.ts +1 -1
- package/dataConnectors/sqlite.ts +3 -3
- package/dist/dataConnectors/baseConnector.js +16 -4
- package/dist/dataConnectors/clickhouse.js +1 -2
- package/dist/dataConnectors/mongo.js +1 -1
- package/dist/dataConnectors/postgres.js +1 -1
- package/dist/dataConnectors/sqlite.js +3 -3
- package/dist/index.js +113 -21
- package/dist/modules/codeInjector.js +8 -8
- package/dist/modules/restApi.js +23 -88
- package/index.ts +128 -24
- package/modules/codeInjector.ts +8 -8
- package/modules/operationalResource.ts +1 -1
- package/modules/restApi.ts +23 -92
- package/package.json +1 -1
- package/spa/src/components/ResourceForm.vue +6 -16
- package/spa/src/utils.ts +34 -1
- package/types/AdminForthConfig.ts +37 -10
|
@@ -67,7 +67,6 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
|
|
|
67
67
|
offset: 0,
|
|
68
68
|
getTotals: false
|
|
69
69
|
});
|
|
70
|
-
process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 existingRecord|||', existingRecord);
|
|
71
70
|
|
|
72
71
|
return existingRecord.data.length > 0;
|
|
73
72
|
}
|
|
@@ -90,14 +89,11 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
|
|
|
90
89
|
}
|
|
91
90
|
recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
|
|
92
91
|
}
|
|
93
|
-
|
|
94
92
|
|
|
95
93
|
let error: string | null = null;
|
|
96
94
|
await Promise.all(
|
|
97
95
|
resource.dataSourceColumns.map(async (col) => {
|
|
98
|
-
|
|
99
96
|
if (col.isUnique && !col.virtual && !error) {
|
|
100
|
-
|
|
101
97
|
const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
|
|
102
98
|
if (exists) {
|
|
103
99
|
error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
|
|
@@ -105,12 +101,12 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
|
|
|
105
101
|
}
|
|
106
102
|
})
|
|
107
103
|
);
|
|
108
|
-
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 error', error);
|
|
109
104
|
if (error) {
|
|
105
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
|
|
110
106
|
return { error, ok: false };
|
|
111
107
|
}
|
|
112
108
|
|
|
113
|
-
process.env.HEAVY_DEBUG && console.log('
|
|
109
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record', recordWithOriginalValues);
|
|
114
110
|
await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
|
|
115
111
|
|
|
116
112
|
return {
|
|
@@ -119,10 +115,26 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
|
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
|
|
122
|
-
|
|
118
|
+
updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
|
|
123
119
|
throw new Error('Method not implemented.');
|
|
124
120
|
}
|
|
125
121
|
|
|
122
|
+
async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<{ error?: string; ok: boolean; }> {
|
|
123
|
+
// transform value using setFieldValue and call updateRecordOriginalValues
|
|
124
|
+
const recordWithOriginalValues = {...newValues};
|
|
125
|
+
|
|
126
|
+
for (const field of Object.keys(newValues)) {
|
|
127
|
+
const col = resource.dataSourceColumns.find((col) => col.name == field);
|
|
128
|
+
recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
|
|
132
|
+
|
|
133
|
+
await this.updateRecordOriginalValues({ resource, recordId, newValues: recordWithOriginalValues });
|
|
134
|
+
|
|
135
|
+
return { ok: true };
|
|
136
|
+
}
|
|
137
|
+
|
|
126
138
|
deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<boolean> {
|
|
127
139
|
throw new Error('Method not implemented.');
|
|
128
140
|
}
|
|
@@ -303,11 +303,10 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
async
|
|
306
|
+
async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
|
|
307
307
|
const columnsWithPlaceholders = Object.keys(newValues).map((col) => {
|
|
308
308
|
return `${col} = {${col}:${resource.dataSourceColumns.find((c) => c.name == col)._underlineType}}`
|
|
309
309
|
});
|
|
310
|
-
const values = [...Object.values(newValues), recordId];
|
|
311
310
|
|
|
312
311
|
await this.client.command(
|
|
313
312
|
{
|
package/dataConnectors/mongo.ts
CHANGED
|
@@ -182,7 +182,7 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
|
|
|
182
182
|
await collection.insertOne(newRecord);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
async
|
|
185
|
+
async updateRecordOriginalValues({ resource, recordId, newValues }) {
|
|
186
186
|
const collection = this.db.db().collection(resource.table);
|
|
187
187
|
await collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
|
|
188
188
|
}
|
|
@@ -287,7 +287,7 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
|
|
|
287
287
|
await this.db.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
async
|
|
290
|
+
async updateRecordOriginalValues({ resource, recordId, newValues }) {
|
|
291
291
|
const values = [...Object.values(newValues), recordId];
|
|
292
292
|
const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
|
293
293
|
await this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE "${this.getPrimaryKey(resource)}" = $${values.length}`, values);
|
package/dataConnectors/sqlite.ts
CHANGED
|
@@ -181,13 +181,12 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
|
|
|
181
181
|
|
|
182
182
|
const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
|
|
183
183
|
|
|
184
|
-
console.log('🪲 SQLITE Query', `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`, 'params:', [...filterValues, limit, offset]);
|
|
185
184
|
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
|
|
186
185
|
const stmt = this.db.prepare(q);
|
|
187
186
|
const d = [...filterValues, limit, offset];
|
|
188
187
|
|
|
189
188
|
if (process.env.HEAVY_DEBUG) {
|
|
190
|
-
console.log('
|
|
189
|
+
console.log('🪲📜 SQLITE Q', q, 'params:', d);
|
|
191
190
|
}
|
|
192
191
|
const rows = await stmt.all(d);
|
|
193
192
|
|
|
@@ -230,13 +229,14 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
|
|
|
230
229
|
await q.run(values);
|
|
231
230
|
}
|
|
232
231
|
|
|
233
|
-
async
|
|
232
|
+
async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
|
|
234
233
|
const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
|
|
235
234
|
const values = [...Object.values(newValues), recordId];
|
|
236
235
|
|
|
237
236
|
const q = this.db.prepare(
|
|
238
237
|
`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`
|
|
239
238
|
)
|
|
239
|
+
process.env.HEAVY_DEBUG && console.log('🪲 SQLITE Query', `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`, 'params:', values);
|
|
240
240
|
await q.run(values);
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -61,7 +61,6 @@ export default class AdminForthBaseConnector {
|
|
|
61
61
|
offset: 0,
|
|
62
62
|
getTotals: false
|
|
63
63
|
});
|
|
64
|
-
process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 existingRecord|||', existingRecord);
|
|
65
64
|
return existingRecord.data.length > 0;
|
|
66
65
|
});
|
|
67
66
|
}
|
|
@@ -90,11 +89,11 @@ export default class AdminForthBaseConnector {
|
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
})));
|
|
93
|
-
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 error', error);
|
|
94
92
|
if (error) {
|
|
93
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
|
|
95
94
|
return { error, ok: false };
|
|
96
95
|
}
|
|
97
|
-
process.env.HEAVY_DEBUG && console.log('
|
|
96
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record', recordWithOriginalValues);
|
|
98
97
|
yield this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
|
|
99
98
|
return {
|
|
100
99
|
ok: true,
|
|
@@ -102,9 +101,22 @@ export default class AdminForthBaseConnector {
|
|
|
102
101
|
};
|
|
103
102
|
});
|
|
104
103
|
}
|
|
105
|
-
|
|
104
|
+
updateRecordOriginalValues({ resource, recordId, newValues }) {
|
|
106
105
|
throw new Error('Method not implemented.');
|
|
107
106
|
}
|
|
107
|
+
updateRecord(_a) {
|
|
108
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
109
|
+
// transform value using setFieldValue and call updateRecordOriginalValues
|
|
110
|
+
const recordWithOriginalValues = Object.assign({}, newValues);
|
|
111
|
+
for (const field of Object.keys(newValues)) {
|
|
112
|
+
const col = resource.dataSourceColumns.find((col) => col.name == field);
|
|
113
|
+
recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
|
|
114
|
+
}
|
|
115
|
+
process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
|
|
116
|
+
yield this.updateRecordOriginalValues({ resource, recordId, newValues: recordWithOriginalValues });
|
|
117
|
+
return { ok: true };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
108
120
|
deleteRecord({ resource, recordId }) {
|
|
109
121
|
throw new Error('Method not implemented.');
|
|
110
122
|
}
|
|
@@ -285,12 +285,11 @@ class ClickhouseConnector extends AdminForthBaseConnector {
|
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
287
|
}
|
|
288
|
-
|
|
288
|
+
updateRecordOriginalValues(_a) {
|
|
289
289
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
290
290
|
const columnsWithPlaceholders = Object.keys(newValues).map((col) => {
|
|
291
291
|
return `${col} = {${col}:${resource.dataSourceColumns.find((c) => c.name == col)._underlineType}}`;
|
|
292
292
|
});
|
|
293
|
-
const values = [...Object.values(newValues), recordId];
|
|
294
293
|
yield this.client.command({
|
|
295
294
|
query: `ALTER TABLE ${this.dbName}.${resource.table} UPDATE ${columnsWithPlaceholders.join(', ')} WHERE ${this.getPrimaryKey(resource)} = {recordId:${resource.dataSourceColumns.find((c) => c.primaryKey)._underlineType}}`,
|
|
296
295
|
query_params: Object.assign(Object.assign({}, newValues), { recordId }),
|
|
@@ -171,7 +171,7 @@ class MongoConnector extends AdminForthBaseConnector {
|
|
|
171
171
|
yield collection.insertOne(newRecord);
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
updateRecordOriginalValues(_a) {
|
|
175
175
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
176
176
|
const collection = this.db.db().collection(resource.table);
|
|
177
177
|
yield collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
|
|
@@ -283,7 +283,7 @@ class PostgresConnector extends AdminForthBaseConnector {
|
|
|
283
283
|
yield this.db.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values);
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
updateRecordOriginalValues(_a) {
|
|
287
287
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
288
288
|
const values = [...Object.values(newValues), recordId];
|
|
289
289
|
const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
|
@@ -196,12 +196,11 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
196
196
|
const where = this.whereClause(filters);
|
|
197
197
|
const filterValues = this.whereParams(filters);
|
|
198
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]);
|
|
200
199
|
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
|
|
201
200
|
const stmt = this.db.prepare(q);
|
|
202
201
|
const d = [...filterValues, limit, offset];
|
|
203
202
|
if (process.env.HEAVY_DEBUG) {
|
|
204
|
-
console.log('
|
|
203
|
+
console.log('🪲📜 SQLITE Q', q, 'params:', d);
|
|
205
204
|
}
|
|
206
205
|
const rows = yield stmt.all(d);
|
|
207
206
|
return rows.map((row) => {
|
|
@@ -246,11 +245,12 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
246
245
|
yield q.run(values);
|
|
247
246
|
});
|
|
248
247
|
}
|
|
249
|
-
|
|
248
|
+
updateRecordOriginalValues(_a) {
|
|
250
249
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
251
250
|
const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
|
|
252
251
|
const values = [...Object.values(newValues), recordId];
|
|
253
252
|
const q = this.db.prepare(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
253
|
+
process.env.HEAVY_DEBUG && console.log('🪲 SQLITE Query', `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`, 'params:', values);
|
|
254
254
|
yield q.run(values);
|
|
255
255
|
});
|
|
256
256
|
}
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
|
20
20
|
import CodeInjector from './modules/codeInjector.js';
|
|
21
21
|
import ExpressServer from './servers/express.js';
|
|
22
22
|
import { ADMINFORTH_VERSION, listify, suggestIfTypo } from './modules/utils.js';
|
|
23
|
-
import { AdminForthFilterOperators, AdminForthDataTypes,
|
|
23
|
+
import { AdminForthFilterOperators, AdminForthDataTypes, } 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';
|
|
@@ -157,23 +157,15 @@ class AdminForth {
|
|
|
157
157
|
}
|
|
158
158
|
createResourceRecord(_b) {
|
|
159
159
|
return __awaiter(this, arguments, void 0, function* ({ resource, record, adminUser }) {
|
|
160
|
-
var _c, _d, _e, _f
|
|
161
|
-
for (const column of resource.columns) {
|
|
162
|
-
// TODO: assuming specifity for AdminForthResourcePages.create better to move it to api for this button
|
|
163
|
-
if (((_c = column.required) === null || _c === void 0 ? void 0 : _c.create) &&
|
|
164
|
-
record[column.name] === undefined &&
|
|
165
|
-
column.showIn.includes(AdminForthResourcePages.create)) {
|
|
166
|
-
return { error: `Column '${column.name}' is required`, ok: false };
|
|
167
|
-
}
|
|
168
|
-
}
|
|
160
|
+
var _c, _d, _e, _f;
|
|
169
161
|
// execute hook if needed
|
|
170
|
-
for (const hook of listify((
|
|
162
|
+
for (const hook of listify((_d = (_c = resource.hooks) === null || _c === void 0 ? void 0 : _c.create) === null || _d === void 0 ? void 0 : _d.beforeSave)) {
|
|
171
163
|
const resp = yield hook({ recordId: undefined, resource, record, adminUser });
|
|
172
164
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
173
165
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
174
166
|
}
|
|
175
167
|
if (resp.error) {
|
|
176
|
-
return { error: resp.error
|
|
168
|
+
return { error: resp.error };
|
|
177
169
|
}
|
|
178
170
|
}
|
|
179
171
|
// remove virtual columns from record
|
|
@@ -183,15 +175,15 @@ class AdminForth {
|
|
|
183
175
|
}
|
|
184
176
|
}
|
|
185
177
|
const connector = this.connectors[resource.dataSource];
|
|
186
|
-
process.env.HEAVY_DEBUG && console.log('
|
|
187
|
-
const {
|
|
188
|
-
if (
|
|
189
|
-
return {
|
|
178
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record createResourceRecord', record);
|
|
179
|
+
const { error, createdRecord } = yield connector.createRecord({ resource, record, adminUser });
|
|
180
|
+
if (error) {
|
|
181
|
+
return { error };
|
|
190
182
|
}
|
|
191
183
|
const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
|
|
192
184
|
// execute hook if needed
|
|
193
|
-
for (const hook of listify((
|
|
194
|
-
console.log('Hook afterSave', hook);
|
|
185
|
+
for (const hook of listify((_f = (_e = resource.hooks) === null || _e === void 0 ? void 0 : _e.create) === null || _f === void 0 ? void 0 : _f.afterSave)) {
|
|
186
|
+
process.env.HEAVY_DEBUG && console.log('🪲 Hook afterSave', hook);
|
|
195
187
|
const resp = yield hook({
|
|
196
188
|
recordId: primaryKey,
|
|
197
189
|
resource,
|
|
@@ -202,10 +194,100 @@ class AdminForth {
|
|
|
202
194
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
203
195
|
}
|
|
204
196
|
if (resp.error) {
|
|
205
|
-
return { error: resp.error
|
|
197
|
+
return { error: resp.error };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { error, createdRecord };
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* record is partial record with only changed fields
|
|
205
|
+
*/
|
|
206
|
+
updateResourceRecord(_b) {
|
|
207
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, record, oldRecord, adminUser }) {
|
|
208
|
+
var _c, _d, _e, _f;
|
|
209
|
+
// execute hook if needed
|
|
210
|
+
for (const hook of listify((_d = (_c = resource.hooks) === null || _c === void 0 ? void 0 : _c.edit) === null || _d === void 0 ? void 0 : _d.beforeSave)) {
|
|
211
|
+
const resp = yield hook({
|
|
212
|
+
recordId,
|
|
213
|
+
resource,
|
|
214
|
+
record,
|
|
215
|
+
oldRecord,
|
|
216
|
+
adminUser
|
|
217
|
+
});
|
|
218
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
219
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
220
|
+
}
|
|
221
|
+
if (resp.error) {
|
|
222
|
+
return { error: resp.error };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const newValues = {};
|
|
226
|
+
const connector = this.connectors[resource.dataSource];
|
|
227
|
+
for (const recordField in record) {
|
|
228
|
+
if (record[recordField] !== oldRecord[recordField]) {
|
|
229
|
+
// leave only changed fields to reduce data transfer/modifications in db
|
|
230
|
+
const column = resource.columns.find((col) => col.name === recordField);
|
|
231
|
+
if (!column || !column.virtual) {
|
|
232
|
+
// exclude virtual columns
|
|
233
|
+
newValues[recordField] = record[recordField];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Object.keys(newValues).length > 0) {
|
|
238
|
+
yield connector.updateRecord({ resource, recordId, newValues });
|
|
239
|
+
}
|
|
240
|
+
// execute hook if needed
|
|
241
|
+
for (const hook of listify((_f = (_e = resource.hooks) === null || _e === void 0 ? void 0 : _e.edit) === null || _f === void 0 ? void 0 : _f.afterSave)) {
|
|
242
|
+
const resp = yield hook({
|
|
243
|
+
resource,
|
|
244
|
+
record,
|
|
245
|
+
adminUser,
|
|
246
|
+
oldRecord,
|
|
247
|
+
recordId,
|
|
248
|
+
});
|
|
249
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
250
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
251
|
+
}
|
|
252
|
+
return { error: resp.error };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
deleteResourceRecord(_b) {
|
|
257
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, adminUser, record }) {
|
|
258
|
+
var _c, _d, _e, _f;
|
|
259
|
+
// execute hook if needed
|
|
260
|
+
for (const hook of listify((_d = (_c = resource.hooks) === null || _c === void 0 ? void 0 : _c.delete) === null || _d === void 0 ? void 0 : _d.beforeSave)) {
|
|
261
|
+
const resp = yield hook({
|
|
262
|
+
resource,
|
|
263
|
+
record,
|
|
264
|
+
adminUser,
|
|
265
|
+
recordId,
|
|
266
|
+
});
|
|
267
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
268
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
269
|
+
}
|
|
270
|
+
if (resp.error) {
|
|
271
|
+
return { error: resp.error };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const connector = this.connectors[resource.dataSource];
|
|
275
|
+
yield connector.deleteRecord({ resource, recordId });
|
|
276
|
+
// execute hook if needed
|
|
277
|
+
for (const hook of listify((_f = (_e = resource.hooks) === null || _e === void 0 ? void 0 : _e.delete) === null || _f === void 0 ? void 0 : _f.afterSave)) {
|
|
278
|
+
const resp = yield hook({
|
|
279
|
+
resource,
|
|
280
|
+
record,
|
|
281
|
+
adminUser,
|
|
282
|
+
recordId,
|
|
283
|
+
});
|
|
284
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
285
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
286
|
+
}
|
|
287
|
+
if (resp.error) {
|
|
288
|
+
return { error: resp.error };
|
|
206
289
|
}
|
|
207
290
|
}
|
|
208
|
-
return { ok, error, createdRecord };
|
|
209
291
|
});
|
|
210
292
|
}
|
|
211
293
|
resource(resourceId) {
|
|
@@ -234,6 +316,16 @@ AdminForth.Types = AdminForthDataTypes;
|
|
|
234
316
|
AdminForth.Utils = {
|
|
235
317
|
generatePasswordHash: (password) => __awaiter(void 0, void 0, void 0, function* () {
|
|
236
318
|
return yield AdminForthAuth.generatePasswordHash(password);
|
|
237
|
-
})
|
|
319
|
+
}),
|
|
320
|
+
PASSWORD_VALIDATORS: {
|
|
321
|
+
UP_LOW_NUM_SPECIAL: {
|
|
322
|
+
regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*\\(\\)\\-_=\\+\\[\\]\\{\\}\\|;:\',\\.<>\\/\\?]).+$',
|
|
323
|
+
message: 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character'
|
|
324
|
+
},
|
|
325
|
+
UP_LOW_NUM: {
|
|
326
|
+
regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).+$',
|
|
327
|
+
message: 'Password must include at least one uppercase letter, one lowercase letter, and one number'
|
|
328
|
+
},
|
|
329
|
+
}
|
|
238
330
|
};
|
|
239
331
|
export default AdminForth;
|
|
@@ -217,13 +217,13 @@ class CodeInjector {
|
|
|
217
217
|
dereference: true, // needed to dereference types
|
|
218
218
|
});
|
|
219
219
|
if (process.env.HEAVY_DEBUG) {
|
|
220
|
-
console.log('
|
|
220
|
+
console.log('🪲⚙️ fsExtra.copy copy single file', src, dest);
|
|
221
221
|
}
|
|
222
222
|
})));
|
|
223
223
|
}
|
|
224
224
|
else {
|
|
225
225
|
if (process.env.HEAVY_DEBUG) {
|
|
226
|
-
console.log(
|
|
226
|
+
console.log(`🪲⚙️ fsExtra.copy from ${path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa')}, -> ${CodeInjector.SPA_TMP_PATH}`);
|
|
227
227
|
}
|
|
228
228
|
// try to rm SPA_TMP_PATH/src/types directory
|
|
229
229
|
try {
|
|
@@ -237,7 +237,7 @@ class CodeInjector {
|
|
|
237
237
|
filter: (src) => {
|
|
238
238
|
const filterPasses = !src.includes('/adminforth/spa/node_modules') && !src.includes('/adminforth/spa/dist');
|
|
239
239
|
if (process.env.HEAVY_DEBUG && !filterPasses) {
|
|
240
|
-
console.log('
|
|
240
|
+
console.log('🪲⚙️ fsExtra.copy filtered out', src);
|
|
241
241
|
}
|
|
242
242
|
return filterPasses;
|
|
243
243
|
},
|
|
@@ -261,7 +261,7 @@ class CodeInjector {
|
|
|
261
261
|
for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
|
|
262
262
|
const to = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', dest);
|
|
263
263
|
if (process.env.HEAVY_DEBUG) {
|
|
264
|
-
console.log(
|
|
264
|
+
console.log(`🪲⚙️ fsExtra.copy from ${src}, ${to}`);
|
|
265
265
|
}
|
|
266
266
|
yield fsExtra.copy(src, to, {
|
|
267
267
|
recursive: true,
|
|
@@ -465,7 +465,7 @@ class CodeInjector {
|
|
|
465
465
|
});
|
|
466
466
|
yield collectDirectories(spaPath);
|
|
467
467
|
if (process.env.HEAVY_DEBUG) {
|
|
468
|
-
console.log('
|
|
468
|
+
console.log('🪲🔎 Watch for:', directories.join(','));
|
|
469
469
|
}
|
|
470
470
|
const watcher = filewatcher();
|
|
471
471
|
directories.forEach((dir) => {
|
|
@@ -514,11 +514,11 @@ class CodeInjector {
|
|
|
514
514
|
yield collectDirectories(customComponentsDir);
|
|
515
515
|
const watcher = filewatcher();
|
|
516
516
|
files.forEach((file) => {
|
|
517
|
-
process.env.HEAVY_DEBUG && console.log(
|
|
517
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for file ${file}`);
|
|
518
518
|
watcher.add(file);
|
|
519
519
|
});
|
|
520
520
|
if (process.env.HEAVY_DEBUG) {
|
|
521
|
-
console.log('
|
|
521
|
+
console.log('🪲🔎 Watch for:', directories.join(','));
|
|
522
522
|
}
|
|
523
523
|
watcher.on('change', (fileOrDir) => __awaiter(this, void 0, void 0, function* () {
|
|
524
524
|
// copy one file
|
|
@@ -568,7 +568,7 @@ class CodeInjector {
|
|
|
568
568
|
}
|
|
569
569
|
else {
|
|
570
570
|
const command = 'run dev';
|
|
571
|
-
console.log(
|
|
571
|
+
console.log(`🪲⚙️ spawn: npm ${command}...`);
|
|
572
572
|
const nodeBinary = process.execPath;
|
|
573
573
|
const npmPath = path.join(path.dirname(nodeBinary), 'npm');
|
|
574
574
|
const env = Object.assign({ VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl, FORCE_COLOR: '1' }, process.env);
|
package/dist/modules/restApi.js
CHANGED
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthFilterOperators, AdminForthDataTypes, AllowedActionsEnum, ActionCheckSource } from "../types/AdminForthConfig.js";
|
|
10
|
+
import { AdminForthFilterOperators, AdminForthDataTypes, AllowedActionsEnum, ActionCheckSource, AdminForthResourcePages } from "../types/AdminForthConfig.js";
|
|
11
11
|
import { ADMINFORTH_VERSION, listify } from './utils.js';
|
|
12
12
|
import AdminForthAuth from "../auth.js";
|
|
13
13
|
export function interpretResource(adminUser, resource, meta, source) {
|
|
@@ -19,7 +19,7 @@ export function interpretResource(adminUser, resource, meta, source) {
|
|
|
19
19
|
const allowedActions = {};
|
|
20
20
|
yield Promise.all(Object.entries(((_a = resource.options) === null || _a === void 0 ? void 0 : _a.allowedActions) || {}).map((_b) => __awaiter(this, [_b], void 0, function* ([key, value]) {
|
|
21
21
|
if (process.env.HEAVY_DEBUG) {
|
|
22
|
-
console.log(
|
|
22
|
+
console.log(`🪲🚥check allowed ${key}, ${value}`);
|
|
23
23
|
}
|
|
24
24
|
// if callable then call
|
|
25
25
|
if (typeof value === 'function') {
|
|
@@ -485,6 +485,7 @@ export default class AdminForthRestAPI {
|
|
|
485
485
|
method: 'POST',
|
|
486
486
|
path: '/create_record',
|
|
487
487
|
handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body, adminUser }) {
|
|
488
|
+
var _3;
|
|
488
489
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
489
490
|
if (!resource) {
|
|
490
491
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -495,6 +496,13 @@ export default class AdminForthRestAPI {
|
|
|
495
496
|
return { error };
|
|
496
497
|
}
|
|
497
498
|
const { record } = body;
|
|
499
|
+
for (const column of resource.columns) {
|
|
500
|
+
if (((_3 = column.required) === null || _3 === void 0 ? void 0 : _3.create) &&
|
|
501
|
+
record[column.name] === undefined &&
|
|
502
|
+
column.showIn.includes(AdminForthResourcePages.create)) {
|
|
503
|
+
return { error: `Column '${column.name}' is required`, ok: false };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
498
506
|
const response = yield this.adminforth.createResourceRecord({ resource, record, adminUser });
|
|
499
507
|
if (response.error) {
|
|
500
508
|
return { error: response.error, ok: false };
|
|
@@ -509,8 +517,7 @@ export default class AdminForthRestAPI {
|
|
|
509
517
|
server.endpoint({
|
|
510
518
|
method: 'POST',
|
|
511
519
|
path: '/update_record',
|
|
512
|
-
handler: (
|
|
513
|
-
var _4, _5, _6, _7;
|
|
520
|
+
handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
|
|
514
521
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
515
522
|
if (!resource) {
|
|
516
523
|
return { error: `Resource '${body['resourceId']}' not found` };
|
|
@@ -524,68 +531,23 @@ export default class AdminForthRestAPI {
|
|
|
524
531
|
}
|
|
525
532
|
const record = body['record'];
|
|
526
533
|
const { allowedActions } = yield interpretResource(adminUser, resource, { requestBody: body, newRecord: record, oldRecord }, ActionCheckSource.EditRequest);
|
|
527
|
-
const { allowed, error } = checkAccess(AllowedActionsEnum.edit, allowedActions);
|
|
534
|
+
const { allowed, error: allowedError } = checkAccess(AllowedActionsEnum.edit, allowedActions);
|
|
528
535
|
if (!allowed) {
|
|
529
|
-
return {
|
|
530
|
-
}
|
|
531
|
-
// execute hook if needed
|
|
532
|
-
for (const hook of listify((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.edit) === null || _5 === void 0 ? void 0 : _5.beforeSave)) {
|
|
533
|
-
const resp = yield hook({
|
|
534
|
-
recordId,
|
|
535
|
-
resource,
|
|
536
|
-
record,
|
|
537
|
-
adminUser
|
|
538
|
-
});
|
|
539
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
540
|
-
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
541
|
-
}
|
|
542
|
-
if (resp.error) {
|
|
543
|
-
return { error: resp.error };
|
|
544
|
-
}
|
|
536
|
+
return { allowedError };
|
|
545
537
|
}
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const column = resource.columns.find((col) => col.name === recordField);
|
|
550
|
-
if (column) {
|
|
551
|
-
if (!column.virtual) {
|
|
552
|
-
newValues[recordField] = connector.setFieldValue(column, record[recordField]);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
newValues[recordField] = record[recordField];
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
if (Object.keys(newValues).length > 0) {
|
|
561
|
-
yield connector.updateRecord({ resource, recordId, newValues });
|
|
562
|
-
}
|
|
563
|
-
// execute hook if needed
|
|
564
|
-
for (const hook of listify((_7 = (_6 = resource.hooks) === null || _6 === void 0 ? void 0 : _6.edit) === null || _7 === void 0 ? void 0 : _7.afterSave)) {
|
|
565
|
-
const resp = yield hook({
|
|
566
|
-
resource,
|
|
567
|
-
record,
|
|
568
|
-
adminUser,
|
|
569
|
-
oldRecord,
|
|
570
|
-
recordId,
|
|
571
|
-
});
|
|
572
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
573
|
-
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
574
|
-
}
|
|
575
|
-
if (resp.error) {
|
|
576
|
-
return { error: resp.error };
|
|
577
|
-
}
|
|
538
|
+
const { error } = yield this.adminforth.updateResourceRecord({ resource, record, adminUser, oldRecord, recordId });
|
|
539
|
+
if (error) {
|
|
540
|
+
return { error };
|
|
578
541
|
}
|
|
579
542
|
return {
|
|
580
|
-
|
|
543
|
+
ok: true
|
|
581
544
|
};
|
|
582
545
|
})
|
|
583
546
|
});
|
|
584
547
|
server.endpoint({
|
|
585
548
|
method: 'POST',
|
|
586
549
|
path: '/delete_record',
|
|
587
|
-
handler: (
|
|
588
|
-
var _9, _10, _11, _12;
|
|
550
|
+
handler: (_5) => __awaiter(this, [_5], void 0, function* ({ body, adminUser }) {
|
|
589
551
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
|
|
590
552
|
const record = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
|
|
591
553
|
if (!resource) {
|
|
@@ -602,39 +564,12 @@ export default class AdminForthRestAPI {
|
|
|
602
564
|
if (!allowed) {
|
|
603
565
|
return { error };
|
|
604
566
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
resource,
|
|
609
|
-
record,
|
|
610
|
-
adminUser,
|
|
611
|
-
recordId: body['primaryKey']
|
|
612
|
-
});
|
|
613
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
614
|
-
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
615
|
-
}
|
|
616
|
-
if (resp.error) {
|
|
617
|
-
return { error: resp.error };
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
const connector = this.adminforth.connectors[resource.dataSource];
|
|
621
|
-
yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
|
|
622
|
-
// execute hook if needed
|
|
623
|
-
for (const hook of listify((_12 = (_11 = resource.hooks) === null || _11 === void 0 ? void 0 : _11.delete) === null || _12 === void 0 ? void 0 : _12.afterSave)) {
|
|
624
|
-
const resp = yield hook({
|
|
625
|
-
resource,
|
|
626
|
-
record,
|
|
627
|
-
adminUser,
|
|
628
|
-
recordId: body['primaryKey']
|
|
629
|
-
});
|
|
630
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
631
|
-
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
632
|
-
}
|
|
633
|
-
if (resp.error) {
|
|
634
|
-
return { error: resp.error };
|
|
635
|
-
}
|
|
567
|
+
const { error: deleteError } = yield this.adminforth.deleteResourceRecord({ resource, record, adminUser, recordId: body['primaryKey'] });
|
|
568
|
+
if (deleteError) {
|
|
569
|
+
return { error: deleteError };
|
|
636
570
|
}
|
|
637
571
|
return {
|
|
572
|
+
ok: true,
|
|
638
573
|
recordId: body['primaryKey']
|
|
639
574
|
};
|
|
640
575
|
})
|
|
@@ -642,7 +577,7 @@ export default class AdminForthRestAPI {
|
|
|
642
577
|
server.endpoint({
|
|
643
578
|
method: 'POST',
|
|
644
579
|
path: '/start_bulk_action',
|
|
645
|
-
handler: (
|
|
580
|
+
handler: (_6) => __awaiter(this, [_6], void 0, function* ({ body, adminUser }) {
|
|
646
581
|
const { resourceId, actionId, recordIds } = body;
|
|
647
582
|
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
|
|
648
583
|
if (!resource) {
|
package/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
AfterSaveFunction,
|
|
18
18
|
AdminUser,
|
|
19
19
|
AdminForthResource,
|
|
20
|
+
IAdminForthDataSourceConnectorBase,
|
|
20
21
|
} from './types/AdminForthConfig.js';
|
|
21
22
|
import AdminForthPlugin from './basePlugin.js';
|
|
22
23
|
import ConfigValidator from './modules/configValidator.js';
|
|
@@ -38,6 +39,17 @@ class AdminForth implements IAdminForth {
|
|
|
38
39
|
static Utils = {
|
|
39
40
|
generatePasswordHash: async (password) => {
|
|
40
41
|
return await AdminForthAuth.generatePasswordHash(password);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
PASSWORD_VALIDATORS: {
|
|
45
|
+
UP_LOW_NUM_SPECIAL: {
|
|
46
|
+
regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*\\(\\)\\-_=\\+\\[\\]\\{\\}\\|;:\',\\.<>\\/\\?]).+$',
|
|
47
|
+
message: 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character'
|
|
48
|
+
},
|
|
49
|
+
UP_LOW_NUM: {
|
|
50
|
+
regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).+$',
|
|
51
|
+
message: 'Password must include at least one uppercase letter, one lowercase letter, and one number'
|
|
52
|
+
},
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
|
|
@@ -49,7 +61,9 @@ class AdminForth implements IAdminForth {
|
|
|
49
61
|
express: ExpressServer;
|
|
50
62
|
auth: AdminForthAuth;
|
|
51
63
|
codeInjector: CodeInjector;
|
|
52
|
-
connectors
|
|
64
|
+
connectors: {
|
|
65
|
+
[dataSourceId: string]: IAdminForthDataSourceConnectorBase,
|
|
66
|
+
};
|
|
53
67
|
connectorClasses: any;
|
|
54
68
|
runningHotReload: boolean;
|
|
55
69
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
@@ -202,18 +216,7 @@ class AdminForth implements IAdminForth {
|
|
|
202
216
|
async createResourceRecord(
|
|
203
217
|
{ resource, record, adminUser }:
|
|
204
218
|
{ resource: AdminForthResource, record: any, adminUser: AdminUser }
|
|
205
|
-
): Promise<{
|
|
206
|
-
|
|
207
|
-
for (const column of resource.columns) {
|
|
208
|
-
// TODO: assuming specifity for AdminForthResourcePages.create better to move it to api for this button
|
|
209
|
-
if (
|
|
210
|
-
(column.required as {create?: boolean, edit?: boolean}) ?.create &&
|
|
211
|
-
record[column.name] === undefined &&
|
|
212
|
-
column.showIn.includes(AdminForthResourcePages.create)
|
|
213
|
-
) {
|
|
214
|
-
return { error: `Column '${column.name}' is required`, ok: false };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
219
|
+
): Promise<{ error?: string, createdRecord?: any }> {
|
|
217
220
|
|
|
218
221
|
// execute hook if needed
|
|
219
222
|
for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
|
|
@@ -223,28 +226,28 @@ class AdminForth implements IAdminForth {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
if (resp.error) {
|
|
226
|
-
return { error: resp.error
|
|
229
|
+
return { error: resp.error };
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
// remove virtual columns from record
|
|
231
234
|
for (const column of resource.columns.filter((col) => col.virtual)) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
if (record[column.name]) {
|
|
236
|
+
delete record[column.name];
|
|
237
|
+
}
|
|
235
238
|
}
|
|
236
239
|
const connector = this.connectors[resource.dataSource];
|
|
237
|
-
process.env.HEAVY_DEBUG && console.log('
|
|
238
|
-
const {
|
|
239
|
-
if (
|
|
240
|
-
return {
|
|
240
|
+
process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record createResourceRecord', record);
|
|
241
|
+
const { error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
|
|
242
|
+
if ( error ) {
|
|
243
|
+
return { error };
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
|
|
244
247
|
|
|
245
248
|
// execute hook if needed
|
|
246
249
|
for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
|
|
247
|
-
console.log('Hook afterSave', hook);
|
|
250
|
+
process.env.HEAVY_DEBUG && console.log('🪲 Hook afterSave', hook);
|
|
248
251
|
const resp = await hook({
|
|
249
252
|
recordId: primaryKey,
|
|
250
253
|
resource,
|
|
@@ -257,11 +260,112 @@ class AdminForth implements IAdminForth {
|
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
if (resp.error) {
|
|
260
|
-
return { error: resp.error
|
|
263
|
+
return { error: resp.error };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { error, createdRecord };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* record is partial record with only changed fields
|
|
272
|
+
*/
|
|
273
|
+
async updateResourceRecord(
|
|
274
|
+
{ resource, recordId, record, oldRecord, adminUser }:
|
|
275
|
+
{ resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser }
|
|
276
|
+
): Promise<{ error?: string }> {
|
|
277
|
+
|
|
278
|
+
// execute hook if needed
|
|
279
|
+
for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
|
|
280
|
+
const resp = await hook({
|
|
281
|
+
recordId,
|
|
282
|
+
resource,
|
|
283
|
+
record,
|
|
284
|
+
oldRecord,
|
|
285
|
+
adminUser
|
|
286
|
+
});
|
|
287
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
288
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
289
|
+
}
|
|
290
|
+
if (resp.error) {
|
|
291
|
+
return { error: resp.error };
|
|
261
292
|
}
|
|
262
293
|
}
|
|
294
|
+
const newValues = {};
|
|
295
|
+
const connector = this.connectors[resource.dataSource];
|
|
296
|
+
|
|
297
|
+
for (const recordField in record) {
|
|
298
|
+
if (record[recordField] !== oldRecord[recordField]) {
|
|
299
|
+
// leave only changed fields to reduce data transfer/modifications in db
|
|
300
|
+
const column = resource.columns.find((col) => col.name === recordField);
|
|
301
|
+
if (!column || !column.virtual) {
|
|
302
|
+
// exclude virtual columns
|
|
303
|
+
newValues[recordField] = record[recordField];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
263
307
|
|
|
264
|
-
|
|
308
|
+
if (Object.keys(newValues).length > 0) {
|
|
309
|
+
await connector.updateRecord({ resource, recordId, newValues });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// execute hook if needed
|
|
313
|
+
for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
|
|
314
|
+
const resp = await hook({
|
|
315
|
+
resource,
|
|
316
|
+
record,
|
|
317
|
+
adminUser,
|
|
318
|
+
oldRecord,
|
|
319
|
+
recordId,
|
|
320
|
+
});
|
|
321
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
322
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { error: resp.error };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async deleteResourceRecord(
|
|
330
|
+
{ resource, recordId, adminUser, record }:
|
|
331
|
+
{ resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any }
|
|
332
|
+
): Promise<{ error?: string }> {
|
|
333
|
+
// execute hook if needed
|
|
334
|
+
for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
|
|
335
|
+
const resp = await hook({
|
|
336
|
+
resource,
|
|
337
|
+
record,
|
|
338
|
+
adminUser,
|
|
339
|
+
recordId,
|
|
340
|
+
});
|
|
341
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
342
|
+
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (resp.error) {
|
|
346
|
+
return { error: resp.error };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const connector = this.connectors[resource.dataSource];
|
|
351
|
+
await connector.deleteRecord({ resource, recordId});
|
|
352
|
+
|
|
353
|
+
// execute hook if needed
|
|
354
|
+
for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
|
|
355
|
+
const resp = await hook({
|
|
356
|
+
resource,
|
|
357
|
+
record,
|
|
358
|
+
adminUser,
|
|
359
|
+
recordId,
|
|
360
|
+
});
|
|
361
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
362
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (resp.error) {
|
|
366
|
+
return { error: resp.error };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
265
369
|
}
|
|
266
370
|
|
|
267
371
|
resource(resourceId: string): IOperationalResource {
|
package/modules/codeInjector.ts
CHANGED
|
@@ -236,13 +236,13 @@ class CodeInjector implements ICodeInjector {
|
|
|
236
236
|
dereference: true, // needed to dereference types
|
|
237
237
|
});
|
|
238
238
|
if (process.env.HEAVY_DEBUG) {
|
|
239
|
-
console.log('
|
|
239
|
+
console.log('🪲⚙️ fsExtra.copy copy single file', src, dest);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
}));
|
|
243
243
|
} else {
|
|
244
244
|
if (process.env.HEAVY_DEBUG) {
|
|
245
|
-
console.log(
|
|
245
|
+
console.log(`🪲⚙️ fsExtra.copy from ${path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa')}, -> ${CodeInjector.SPA_TMP_PATH}`);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// try to rm SPA_TMP_PATH/src/types directory
|
|
@@ -257,7 +257,7 @@ class CodeInjector implements ICodeInjector {
|
|
|
257
257
|
filter: (src) => {
|
|
258
258
|
const filterPasses = !src.includes('/adminforth/spa/node_modules') && !src.includes('/adminforth/spa/dist')
|
|
259
259
|
if (process.env.HEAVY_DEBUG && !filterPasses) {
|
|
260
|
-
console.log('
|
|
260
|
+
console.log('🪲⚙️ fsExtra.copy filtered out', src);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
return filterPasses
|
|
@@ -287,7 +287,7 @@ class CodeInjector implements ICodeInjector {
|
|
|
287
287
|
for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
|
|
288
288
|
const to = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', dest);
|
|
289
289
|
if (process.env.HEAVY_DEBUG) {
|
|
290
|
-
console.log(
|
|
290
|
+
console.log(`🪲⚙️ fsExtra.copy from ${src}, ${to}`);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
await fsExtra.copy(src, to, {
|
|
@@ -534,7 +534,7 @@ class CodeInjector implements ICodeInjector {
|
|
|
534
534
|
await collectDirectories(spaPath);
|
|
535
535
|
|
|
536
536
|
if (process.env.HEAVY_DEBUG) {
|
|
537
|
-
console.log('
|
|
537
|
+
console.log('🪲🔎 Watch for:', directories.join(','));
|
|
538
538
|
}
|
|
539
539
|
|
|
540
540
|
const watcher = filewatcher();
|
|
@@ -593,12 +593,12 @@ class CodeInjector implements ICodeInjector {
|
|
|
593
593
|
|
|
594
594
|
const watcher = filewatcher();
|
|
595
595
|
files.forEach((file) => {
|
|
596
|
-
process.env.HEAVY_DEBUG && console.log(
|
|
596
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for file ${file}`);
|
|
597
597
|
watcher.add(file);
|
|
598
598
|
});
|
|
599
599
|
|
|
600
600
|
if (process.env.HEAVY_DEBUG) {
|
|
601
|
-
console.log('
|
|
601
|
+
console.log('🪲🔎 Watch for:', directories.join(','));
|
|
602
602
|
}
|
|
603
603
|
|
|
604
604
|
watcher.on(
|
|
@@ -657,7 +657,7 @@ class CodeInjector implements ICodeInjector {
|
|
|
657
657
|
await this.runNpmShell({command: 'run build-only', cwd});
|
|
658
658
|
} else {
|
|
659
659
|
const command = 'run dev';
|
|
660
|
-
console.log(
|
|
660
|
+
console.log(`🪲⚙️ spawn: npm ${command}...`);
|
|
661
661
|
const nodeBinary = process.execPath;
|
|
662
662
|
const npmPath = path.join(path.dirname(nodeBinary), 'npm');
|
|
663
663
|
const env = {
|
package/modules/restApi.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
AfterDataSourceResponseFunction,
|
|
14
14
|
BeforeDataSourceRequestFunction,
|
|
15
15
|
AfterSaveFunction,
|
|
16
|
-
AllowedActionsResolved
|
|
16
|
+
AllowedActionsResolved,
|
|
17
|
+
AdminForthResourcePages
|
|
17
18
|
|
|
18
19
|
} from "../types/AdminForthConfig.js";
|
|
19
20
|
|
|
@@ -32,7 +33,7 @@ export async function interpretResource(adminUser: AdminUser, resource: AdminFor
|
|
|
32
33
|
Object.entries(resource.options?.allowedActions || {}).map(
|
|
33
34
|
async ([key, value]: [string, AllowedActionValue]) => {
|
|
34
35
|
if (process.env.HEAVY_DEBUG) {
|
|
35
|
-
console.log(
|
|
36
|
+
console.log(`🪲🚥check allowed ${key}, ${value}`)
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// if callable then call
|
|
@@ -575,6 +576,16 @@ export default class AdminForthRestAPI {
|
|
|
575
576
|
|
|
576
577
|
const { record } = body;
|
|
577
578
|
|
|
579
|
+
for (const column of resource.columns) {
|
|
580
|
+
if (
|
|
581
|
+
(column.required as {create?: boolean, edit?: boolean})?.create &&
|
|
582
|
+
record[column.name] === undefined &&
|
|
583
|
+
column.showIn.includes(AdminForthResourcePages.create)
|
|
584
|
+
) {
|
|
585
|
+
return { error: `Column '${column.name}' is required`, ok: false };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
578
589
|
const response = await this.adminforth.createResourceRecord({ resource, record, adminUser });
|
|
579
590
|
if (response.error) {
|
|
580
591
|
return { error: response.error, ok: false };
|
|
@@ -607,66 +618,17 @@ export default class AdminForthRestAPI {
|
|
|
607
618
|
|
|
608
619
|
const { allowedActions } = await interpretResource(adminUser, resource, { requestBody: body, newRecord: record, oldRecord}, ActionCheckSource.EditRequest);
|
|
609
620
|
|
|
610
|
-
const { allowed, error } = checkAccess(AllowedActionsEnum.edit, allowedActions);
|
|
621
|
+
const { allowed, error: allowedError } = checkAccess(AllowedActionsEnum.edit, allowedActions);
|
|
611
622
|
if (!allowed) {
|
|
612
|
-
return {
|
|
623
|
+
return { allowedError };
|
|
613
624
|
}
|
|
614
625
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
recordId,
|
|
619
|
-
resource,
|
|
620
|
-
record,
|
|
621
|
-
adminUser
|
|
622
|
-
});
|
|
623
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
624
|
-
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
if (resp.error) {
|
|
628
|
-
return { error: resp.error };
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
const newValues = {};
|
|
632
|
-
|
|
633
|
-
for (const recordField in record) {
|
|
634
|
-
if (record[recordField] !== oldRecord[recordField]) {
|
|
635
|
-
const column = resource.columns.find((col) => col.name === recordField);
|
|
636
|
-
if (column) {
|
|
637
|
-
if (!column.virtual) {
|
|
638
|
-
newValues[recordField] = connector.setFieldValue(column, record[recordField]);
|
|
639
|
-
}
|
|
640
|
-
} else {
|
|
641
|
-
newValues[recordField] = record[recordField];
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (Object.keys(newValues).length > 0) {
|
|
647
|
-
await connector.updateRecord({ resource, recordId, newValues});
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// execute hook if needed
|
|
651
|
-
for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
|
|
652
|
-
const resp = await hook({
|
|
653
|
-
resource,
|
|
654
|
-
record,
|
|
655
|
-
adminUser,
|
|
656
|
-
oldRecord,
|
|
657
|
-
recordId,
|
|
658
|
-
});
|
|
659
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
660
|
-
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
if (resp.error) {
|
|
664
|
-
return { error: resp.error };
|
|
665
|
-
}
|
|
626
|
+
const { error } = await this.adminforth.updateResourceRecord({ resource, record, adminUser, oldRecord, recordId });
|
|
627
|
+
if (error) {
|
|
628
|
+
return { error };
|
|
666
629
|
}
|
|
667
|
-
|
|
668
630
|
return {
|
|
669
|
-
|
|
631
|
+
ok: true
|
|
670
632
|
}
|
|
671
633
|
}
|
|
672
634
|
});
|
|
@@ -693,43 +655,12 @@ export default class AdminForthRestAPI {
|
|
|
693
655
|
return { error };
|
|
694
656
|
}
|
|
695
657
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
resource,
|
|
700
|
-
record,
|
|
701
|
-
adminUser,
|
|
702
|
-
recordId: body['primaryKey']
|
|
703
|
-
});
|
|
704
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
705
|
-
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (resp.error) {
|
|
709
|
-
return { error: resp.error };
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const connector = this.adminforth.connectors[resource.dataSource];
|
|
714
|
-
await connector.deleteRecord({ resource, recordId: body['primaryKey']});
|
|
715
|
-
|
|
716
|
-
// execute hook if needed
|
|
717
|
-
for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
|
|
718
|
-
const resp = await hook({
|
|
719
|
-
resource,
|
|
720
|
-
record,
|
|
721
|
-
adminUser,
|
|
722
|
-
recordId: body['primaryKey']
|
|
723
|
-
});
|
|
724
|
-
if (!resp || (!resp.ok && !resp.error)) {
|
|
725
|
-
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (resp.error) {
|
|
729
|
-
return { error: resp.error };
|
|
730
|
-
}
|
|
658
|
+
const { error: deleteError } = await this.adminforth.deleteResourceRecord({ resource, record, adminUser, recordId: body['primaryKey'] });
|
|
659
|
+
if (deleteError) {
|
|
660
|
+
return { error: deleteError };
|
|
731
661
|
}
|
|
732
662
|
return {
|
|
663
|
+
ok: true,
|
|
733
664
|
recordId: body['primaryKey']
|
|
734
665
|
}
|
|
735
666
|
}
|
package/package.json
CHANGED
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
|
|
146
146
|
import CustomDatePicker from "@/components/CustomDatePicker.vue";
|
|
147
147
|
import Dropdown from '@/components/Dropdown.vue';
|
|
148
|
-
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
148
|
+
import { applyRegexValidation, callAdminForthApi, getCustomComponent } from '@/utils';
|
|
149
149
|
import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
|
|
150
150
|
import { computedAsync } from '@vueuse/core';
|
|
151
151
|
import { initFlowbite } from 'flowbite';
|
|
@@ -172,7 +172,7 @@ const currentValues = ref(null);
|
|
|
172
172
|
|
|
173
173
|
const customComponentsInValidity = ref({});
|
|
174
174
|
const customComponentsEmptiness = ref({});
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
|
|
177
177
|
const columnError = (column) => {
|
|
178
178
|
const val = computed(() => {
|
|
@@ -212,22 +212,12 @@ const columnError = (column) => {
|
|
|
212
212
|
return `This field must be less than ${column.maxValue}`;
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
if ( column.validation && column.validation.length ) {
|
|
216
|
-
const validationArray = column.validation;
|
|
217
|
-
for (let i = 0; i < validationArray.length; i++) {
|
|
218
|
-
if (validationArray[i].regExp) {
|
|
219
|
-
const regExp = new RegExp(validationArray[i].regExp);
|
|
220
|
-
let value = currentValues.value[column.name];
|
|
221
|
-
if (value === undefined || value === null) {
|
|
222
|
-
value = '';
|
|
223
|
-
}
|
|
224
|
-
if (!regExp.test(value)) {
|
|
225
|
-
return validationArray[i].message;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
215
|
|
|
216
|
+
const error = applyRegexValidation(currentValues.value[column.name], column.validation);
|
|
217
|
+
if (error) {
|
|
218
|
+
return error;
|
|
230
219
|
}
|
|
220
|
+
|
|
231
221
|
return null;
|
|
232
222
|
});
|
|
233
223
|
return val.value;
|
package/spa/src/utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { onMounted, ref, resolveComponent } from 'vue';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CoreConfig } from './spa_types/core';
|
|
3
|
+
import type { ValidationObject } from './types/AdminForthConfig';
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
import router from "./router";
|
|
5
7
|
import { useCoreStore } from './stores/core';
|
|
@@ -113,4 +115,35 @@ export function initThreeDotsDropdown() {
|
|
|
113
115
|
dd.hide();
|
|
114
116
|
}
|
|
115
117
|
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function applyRegexValidation(value: any, validation: ValidationObject[] | undefined) {
|
|
121
|
+
|
|
122
|
+
if ( validation?.length ) {
|
|
123
|
+
const validationArray = validation;
|
|
124
|
+
for (let i = 0; i < validationArray.length; i++) {
|
|
125
|
+
if (validationArray[i].regExp) {
|
|
126
|
+
let flags = '';
|
|
127
|
+
if (validationArray[i].caseSensitive) {
|
|
128
|
+
flags += 'i';
|
|
129
|
+
}
|
|
130
|
+
if (validationArray[i].multiline) {
|
|
131
|
+
flags += 'm';
|
|
132
|
+
}
|
|
133
|
+
if (validationArray[i].global) {
|
|
134
|
+
flags += 'g';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const regExp = new RegExp(validationArray[i].regExp, flags);
|
|
138
|
+
if (value === undefined || value === null) {
|
|
139
|
+
value = '';
|
|
140
|
+
}
|
|
141
|
+
let valueS = `${value}`;
|
|
142
|
+
|
|
143
|
+
if (!regExp.test(valueS)) {
|
|
144
|
+
return validationArray[i].message;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
116
149
|
}
|
|
@@ -189,14 +189,12 @@ export interface IAdminForthDataSourceConnector {
|
|
|
189
189
|
createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }): Promise<void>;
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
192
|
+
* Update record in database. newValues might have not all fields in record, but only changed ones.
|
|
193
193
|
* recordId is value of field which is marked as {@link AdminForthResourceColumn.primaryKey}
|
|
194
|
-
* newValues is array of fields which should be updated (might be not all fields in record, but only changed fields).
|
|
195
194
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void>;
|
|
196
|
+
|
|
197
|
+
|
|
200
198
|
/**
|
|
201
199
|
* Used to delete record in database.
|
|
202
200
|
*/
|
|
@@ -228,12 +226,18 @@ export interface IAdminForthDataSourceConnectorBase extends IAdminForthDataSourc
|
|
|
228
226
|
adminUser: AdminUser
|
|
229
227
|
}): Promise<{ok: boolean, error?: string, createdRecord?: any}>;
|
|
230
228
|
|
|
229
|
+
updateRecord({ resource, recordId, newValues }: {
|
|
230
|
+
resource: AdminForthResource,
|
|
231
|
+
recordId: string,
|
|
232
|
+
newValues: any,
|
|
233
|
+
}): Promise<{ok: boolean, error?: string}>;
|
|
234
|
+
|
|
231
235
|
getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }>;
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
|
|
235
239
|
export interface IAdminForthDataSourceConnectorConstructor {
|
|
236
|
-
new ({ url }: { url: string }):
|
|
240
|
+
new ({ url }: { url: string }): IAdminForthDataSourceConnectorBase;
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
export interface IAdminForthAuth {
|
|
@@ -266,7 +270,15 @@ export interface IAdminForth {
|
|
|
266
270
|
|
|
267
271
|
createResourceRecord(
|
|
268
272
|
params: { resource: AdminForthResource, record: any, adminUser: AdminUser }
|
|
269
|
-
): Promise<{
|
|
273
|
+
): Promise<{ error?: string, createdRecord?: any }>;
|
|
274
|
+
|
|
275
|
+
updateResourceRecord(
|
|
276
|
+
params: { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser }
|
|
277
|
+
): Promise<{ error?: string }>;
|
|
278
|
+
|
|
279
|
+
deleteResourceRecord(
|
|
280
|
+
params: { resource: AdminForthResource, recordId: string, adminUser: AdminUser, record: any }
|
|
281
|
+
): Promise<{ error?: string }>;
|
|
270
282
|
|
|
271
283
|
auth: IAdminForthAuth;
|
|
272
284
|
|
|
@@ -701,13 +713,13 @@ export type AfterDataSourceResponseFunction = (params: {resource: AdminForthReso
|
|
|
701
713
|
* Modify record to change how data is saved to database.
|
|
702
714
|
* Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
|
|
703
715
|
*/
|
|
704
|
-
export type BeforeSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any}) => Promise<{ok: boolean, error?: string}>;
|
|
716
|
+
export type BeforeSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any, oldRecord?: any}) => Promise<{ok: boolean, error?: string}>;
|
|
705
717
|
|
|
706
718
|
/**
|
|
707
719
|
* Modify record to change how data is saved to database.
|
|
708
720
|
* Return ok: false and error: string to stop execution and show error message to user. Return ok: true to continue execution.
|
|
709
721
|
*/
|
|
710
|
-
export type AfterSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any}) => Promise<{ok: boolean, error?: string}>;
|
|
722
|
+
export type AfterSaveFunction = (params: {resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any, oldRecord?: any}) => Promise<{ok: boolean, error?: string}>;
|
|
711
723
|
|
|
712
724
|
/**
|
|
713
725
|
* Allow to get user data before login confirmation, will triger when user try to login.
|
|
@@ -1508,6 +1520,21 @@ export type ValidationObject = {
|
|
|
1508
1520
|
* Example: "Invalid email format"
|
|
1509
1521
|
*/
|
|
1510
1522
|
message: string,
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* Whether to check case sensitivity (i flag)
|
|
1526
|
+
*/
|
|
1527
|
+
caseSensitive: boolean,
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Whether to check Multiline strings (m flag)
|
|
1531
|
+
*/
|
|
1532
|
+
multiline: boolean,
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Whether to check global strings (g flag)
|
|
1536
|
+
*/
|
|
1537
|
+
global: boolean
|
|
1511
1538
|
}
|
|
1512
1539
|
|
|
1513
1540
|
export type AdminForthComponentDeclarationFull = {
|