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
package/index.ts
CHANGED
|
@@ -5,12 +5,14 @@ import PostgresConnector from './dataConnectors/postgres.js';
|
|
|
5
5
|
import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
6
6
|
import CodeInjector from './modules/codeInjector.js';
|
|
7
7
|
import ExpressServer from './servers/express.js';
|
|
8
|
-
import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
|
|
8
|
+
import { ADMINFORTH_VERSION, listify, suggestIfTypo } from './modules/utils.js';
|
|
9
9
|
import {
|
|
10
10
|
type AdminForthConfig,
|
|
11
11
|
type IAdminForth,
|
|
12
12
|
type IConfigValidator,
|
|
13
|
-
|
|
13
|
+
IOperationalResource,
|
|
14
|
+
AdminForthFilterOperators,
|
|
15
|
+
AdminForthDataTypes, AdminForthResourcePages, IHttpServer,
|
|
14
16
|
BeforeSaveFunction,
|
|
15
17
|
AfterSaveFunction,
|
|
16
18
|
AdminUser,
|
|
@@ -20,11 +22,14 @@ import AdminForthPlugin from './basePlugin.js';
|
|
|
20
22
|
import ConfigValidator from './modules/configValidator.js';
|
|
21
23
|
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
22
24
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
25
|
+
import OperationalResource from './modules/operationalResource.js';
|
|
26
|
+
import { error } from 'console';
|
|
23
27
|
|
|
24
28
|
// exports
|
|
25
29
|
export * from './types/AdminForthConfig.js';
|
|
26
30
|
export { interpretResource };
|
|
27
31
|
export { AdminForthPlugin };
|
|
32
|
+
export { suggestIfTypo };
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
class AdminForth implements IAdminForth {
|
|
@@ -50,7 +55,9 @@ class AdminForth implements IAdminForth {
|
|
|
50
55
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
51
56
|
configValidator: IConfigValidator;
|
|
52
57
|
restApi: AdminForthRestAPI;
|
|
53
|
-
|
|
58
|
+
operationalResources: {
|
|
59
|
+
[resourceId: string]: IOperationalResource,
|
|
60
|
+
}
|
|
54
61
|
baseUrlSlashed: string;
|
|
55
62
|
|
|
56
63
|
statuses: {
|
|
@@ -116,21 +123,24 @@ class AdminForth implements IAdminForth {
|
|
|
116
123
|
this.config.dataSources.forEach((ds) => {
|
|
117
124
|
const dbType = ds.url.split(':')[0];
|
|
118
125
|
if (!this.config.databaseConnectors[dbType]) {
|
|
119
|
-
throw new Error(`Database type ${dbType} is not supported, consider using
|
|
126
|
+
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`);
|
|
120
127
|
}
|
|
121
128
|
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
|
|
122
129
|
});
|
|
123
130
|
|
|
124
131
|
await Promise.all(this.config.resources.map(async (res) => {
|
|
125
132
|
if (!this.connectors[res.dataSource]) {
|
|
126
|
-
|
|
133
|
+
const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
|
|
134
|
+
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
|
|
135
|
+
? `. Did you mean '${similar}'?` : 'Available dataSources: '+Object.keys(this.connectors).join(', ')}`
|
|
136
|
+
);
|
|
127
137
|
}
|
|
128
138
|
const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
|
|
129
139
|
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
130
140
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
131
141
|
}
|
|
132
142
|
if (fieldTypes === null) {
|
|
133
|
-
console.error(
|
|
143
|
+
console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
134
144
|
return;
|
|
135
145
|
}
|
|
136
146
|
if (!res.columns) {
|
|
@@ -139,7 +149,8 @@ class AdminForth implements IAdminForth {
|
|
|
139
149
|
|
|
140
150
|
res.columns.forEach((col, i) => {
|
|
141
151
|
if (!fieldTypes[col.name] && !col.virtual) {
|
|
142
|
-
|
|
152
|
+
const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
|
|
153
|
+
throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
143
154
|
}
|
|
144
155
|
// first find discovered values, but allow override
|
|
145
156
|
res.columns[i] = { ...fieldTypes[col.name], ...col };
|
|
@@ -156,6 +167,11 @@ class AdminForth implements IAdminForth {
|
|
|
156
167
|
|
|
157
168
|
this.statuses.dbDiscover = 'done';
|
|
158
169
|
|
|
170
|
+
this.operationalResources = {};
|
|
171
|
+
this.config.resources.forEach((resource) => {
|
|
172
|
+
this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
|
|
173
|
+
});
|
|
174
|
+
|
|
159
175
|
// console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
|
|
160
176
|
}
|
|
161
177
|
|
|
@@ -164,9 +180,12 @@ class AdminForth implements IAdminForth {
|
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
async getUserByPk(pk: string) {
|
|
167
|
-
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
183
|
+
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
168
184
|
if (!resource) {
|
|
169
|
-
|
|
185
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
|
|
186
|
+
throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
|
|
187
|
+
`Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`
|
|
188
|
+
);
|
|
170
189
|
}
|
|
171
190
|
const users = await this.connectors[resource.dataSource].getData({
|
|
172
191
|
resource,
|
|
@@ -180,46 +199,31 @@ class AdminForth implements IAdminForth {
|
|
|
180
199
|
return users.data[0] || null;
|
|
181
200
|
}
|
|
182
201
|
|
|
183
|
-
async createResourceRecord(
|
|
202
|
+
async createResourceRecord(
|
|
203
|
+
{ resource, record, adminUser }:
|
|
204
|
+
{ resource: AdminForthResource, record: any, adminUser: AdminUser }
|
|
205
|
+
): Promise<{ ok: boolean, error?: string, createdRecord?: any }> {
|
|
206
|
+
|
|
184
207
|
for (const column of resource.columns) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
(column.required as {create?: boolean, edit?: boolean}) ?.create &&
|
|
194
|
-
record[column.name] === undefined &&
|
|
195
|
-
column.showIn.includes(AdminForthResourcePages.create)
|
|
196
|
-
) {
|
|
197
|
-
return { error: `Column '${column.name}' is required` };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (column.isUnique) {
|
|
201
|
-
const existingRecord = await this.connectors[resource.dataSource].getData({
|
|
202
|
-
resource,
|
|
203
|
-
filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: record[column.name] }],
|
|
204
|
-
limit: 1,
|
|
205
|
-
sort: [],
|
|
206
|
-
offset: 0
|
|
207
|
-
});
|
|
208
|
-
if (existingRecord.data.length > 0) {
|
|
209
|
-
return { error: `Record with ${column.name} ${record[column.name]} already exists` };
|
|
210
|
-
}
|
|
211
|
-
}
|
|
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
|
+
}
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
// execute hook if needed
|
|
215
219
|
for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
|
|
216
|
-
const resp = await hook({ resource, record, adminUser });
|
|
220
|
+
const resp = await hook({ recordId: undefined, resource, record, adminUser });
|
|
217
221
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
218
222
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
219
223
|
}
|
|
220
224
|
|
|
221
225
|
if (resp.error) {
|
|
222
|
-
return { error: resp.error };
|
|
226
|
+
return { error: resp.error, ok: false };
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
|
|
@@ -231,21 +235,50 @@ class AdminForth implements IAdminForth {
|
|
|
231
235
|
}
|
|
232
236
|
const connector = this.connectors[resource.dataSource];
|
|
233
237
|
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
|
|
234
|
-
await connector.createRecord({ resource, record });
|
|
238
|
+
const { ok, error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
|
|
239
|
+
if (!ok) {
|
|
240
|
+
return { ok, error };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
|
|
244
|
+
|
|
235
245
|
// execute hook if needed
|
|
236
246
|
for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
|
|
237
247
|
console.log('Hook afterSave', hook);
|
|
238
|
-
const resp = await hook({
|
|
248
|
+
const resp = await hook({
|
|
249
|
+
recordId: primaryKey,
|
|
250
|
+
resource,
|
|
251
|
+
record: createdRecord,
|
|
252
|
+
adminUser
|
|
253
|
+
});
|
|
254
|
+
|
|
239
255
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
240
256
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
241
257
|
}
|
|
242
258
|
|
|
243
259
|
if (resp.error) {
|
|
244
|
-
return { error: resp.error };
|
|
260
|
+
return { error: resp.error, ok: false };
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
|
|
248
|
-
return { ok
|
|
264
|
+
return { ok, error, createdRecord };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
resource(resourceId: string) {
|
|
268
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
269
|
+
if (this.statuses.dbDiscover === 'running') {
|
|
270
|
+
throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
|
|
271
|
+
'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
|
|
272
|
+
} else {
|
|
273
|
+
throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
|
|
274
|
+
'Call admin.discoverDatabases() first and await it before using data API');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!this.operationalResources[resourceId]) {
|
|
278
|
+
const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
|
|
279
|
+
throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
|
|
280
|
+
}
|
|
281
|
+
return this.operationalResources[resourceId];
|
|
249
282
|
}
|
|
250
283
|
|
|
251
284
|
setupEndpoints(server: IHttpServer) {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
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
|
|
|
15
15
|
import crypto from 'crypto';
|
|
16
16
|
|
|
@@ -51,17 +51,6 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
51
51
|
validateConfig() {
|
|
52
52
|
const errors = [];
|
|
53
53
|
|
|
54
|
-
if (this.config.rootUser) {
|
|
55
|
-
if (!this.config.rootUser.username) {
|
|
56
|
-
throw new Error('rootUser.username is required');
|
|
57
|
-
}
|
|
58
|
-
if (!this.config.rootUser.password) {
|
|
59
|
-
throw new Error('rootUser.password is required');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
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');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
54
|
if (!this.config.customization.customComponentsDir) {
|
|
66
55
|
this.config.customization.customComponentsDir = './custom';
|
|
67
56
|
}
|
|
@@ -74,8 +63,13 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
74
63
|
}
|
|
75
64
|
|
|
76
65
|
if (this.config.auth) {
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
// TODO: remove in future releases
|
|
67
|
+
if (!this.config.auth.usersResourceId && this.config.auth.resourceId) {
|
|
68
|
+
this.config.auth.usersResourceId = this.config.auth.resourceId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.config.auth.usersResourceId) {
|
|
72
|
+
throw new Error('No config.auth.usersResourceId defined');
|
|
79
73
|
}
|
|
80
74
|
if (!this.config.auth.passwordHashField) {
|
|
81
75
|
throw new Error('No config.auth.passwordHashField defined');
|
|
@@ -86,9 +80,10 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
86
80
|
if (this.config.auth.loginBackgroundImage) {
|
|
87
81
|
errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
|
|
88
82
|
}
|
|
89
|
-
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
83
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
90
84
|
if (!userResource) {
|
|
91
|
-
|
|
85
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId || res.table), this.config.auth.usersResourceId);
|
|
86
|
+
throw new Error(`Resource with id "${this.config.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
if (!this.config.auth.beforeLoginConfirmation) {
|
|
@@ -135,7 +130,9 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
135
130
|
if (this.config.customization.brandLogo) {
|
|
136
131
|
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
137
132
|
}
|
|
138
|
-
|
|
133
|
+
if (this.config.customization.showBrandNameInSidebar === undefined) {
|
|
134
|
+
this.config.customization.showBrandNameInSidebar = true;
|
|
135
|
+
}
|
|
139
136
|
if (this.config.customization.favicon) {
|
|
140
137
|
errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
|
|
141
138
|
}
|
|
@@ -282,7 +279,12 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
282
279
|
await Promise.all(
|
|
283
280
|
(res.hooks.delete.beforeSave as AfterSaveFunction[]).map(
|
|
284
281
|
async (hook) => {
|
|
285
|
-
const resp = await hook({
|
|
282
|
+
const resp = await hook({
|
|
283
|
+
recordId: recordId,
|
|
284
|
+
resource: res,
|
|
285
|
+
record,
|
|
286
|
+
adminUser,
|
|
287
|
+
});
|
|
286
288
|
if (!error && resp.error) {
|
|
287
289
|
error = resp.error;
|
|
288
290
|
}
|
|
@@ -299,7 +301,12 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
299
301
|
await Promise.all(
|
|
300
302
|
(res.hooks.delete.afterSave as AfterSaveFunction[]).map(
|
|
301
303
|
async (hook) => {
|
|
302
|
-
await hook({
|
|
304
|
+
await hook({
|
|
305
|
+
resource: res,
|
|
306
|
+
record,
|
|
307
|
+
adminUser,
|
|
308
|
+
recordId: recordId
|
|
309
|
+
});
|
|
303
310
|
}
|
|
304
311
|
)
|
|
305
312
|
)
|
|
@@ -388,8 +395,6 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
388
395
|
}
|
|
389
396
|
});
|
|
390
397
|
|
|
391
|
-
|
|
392
|
-
|
|
393
398
|
if (!this.config.menu) {
|
|
394
399
|
errors.push('No config.menu defined');
|
|
395
400
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { error } from 'console';
|
|
2
|
+
import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): IAdminForthFilter[] {
|
|
6
|
+
if (!filter) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
return (Array.isArray(filter) ? filter : [filter]) as IAdminForthFilter[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
|
|
13
|
+
return (Array.isArray(sort) ? sort : [sort]) as IAdminForthSort[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default class OperationalResource implements IOperationalResource {
|
|
17
|
+
dataConnector: IAdminForthDataSourceConnectorBase;
|
|
18
|
+
resourceConfig: AdminForthResource;
|
|
19
|
+
|
|
20
|
+
constructor(dataConnector: IAdminForthDataSourceConnectorBase, resourceConfig: AdminForthResource) {
|
|
21
|
+
this.dataConnector = dataConnector;
|
|
22
|
+
this.resourceConfig = resourceConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any | null> {
|
|
26
|
+
return (
|
|
27
|
+
await this.dataConnector.getData({
|
|
28
|
+
resource: this.resourceConfig,
|
|
29
|
+
filters: filtersIfFilter(filter),
|
|
30
|
+
limit: 1,
|
|
31
|
+
offset: 0,
|
|
32
|
+
sort: [],
|
|
33
|
+
})
|
|
34
|
+
).data[0] || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async list(
|
|
38
|
+
filter: IAdminForthFilter | IAdminForthFilter[],
|
|
39
|
+
limit: number | null,
|
|
40
|
+
offset: number | null,
|
|
41
|
+
sort: IAdminForthSort | IAdminForthSort[]
|
|
42
|
+
): Promise<any[]> {
|
|
43
|
+
let appliedLimit = limit;
|
|
44
|
+
if (limit === null) {
|
|
45
|
+
appliedLimit = 1000000000;
|
|
46
|
+
}
|
|
47
|
+
let appliedOffset = offset;
|
|
48
|
+
if (offset === null) {
|
|
49
|
+
appliedOffset = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { data } = await this.dataConnector.getData({
|
|
53
|
+
resource: this.resourceConfig,
|
|
54
|
+
filters: filtersIfFilter(filter),
|
|
55
|
+
limit: appliedLimit,
|
|
56
|
+
offset: appliedOffset,
|
|
57
|
+
sort: sortsIfSort(sort),
|
|
58
|
+
getTotals: false,
|
|
59
|
+
});
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async count(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): Promise<number> {
|
|
64
|
+
return await this.dataConnector.getCount({
|
|
65
|
+
resource: this.resourceConfig,
|
|
66
|
+
filters: filtersIfFilter(filter),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async create(recordValues: any): Promise<{ ok: boolean; createdRecord: any; error?: string; }> {
|
|
71
|
+
const { ok, createdRecord, error } = await this.dataConnector.createRecord({
|
|
72
|
+
resource: this.resourceConfig,
|
|
73
|
+
record: recordValues,
|
|
74
|
+
adminUser: null
|
|
75
|
+
});
|
|
76
|
+
return { ok, createdRecord, error };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async update(primaryKey: any, record: any): Promise<any> {
|
|
80
|
+
return await this.dataConnector.updateRecord({
|
|
81
|
+
resource: this.resourceConfig,
|
|
82
|
+
recordId: primaryKey,
|
|
83
|
+
newValues: record
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async delete(primaryKey: any): Promise<boolean> {
|
|
88
|
+
return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
}
|