adminforth 1.2.99 → 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 +65 -7
- package/dataConnectors/clickhouse.ts +1 -0
- package/dataConnectors/mongo.ts +3 -4
- package/dataConnectors/sqlite.ts +7 -0
- package/dist/auth.js +6 -6
- package/dist/dataConnectors/baseConnector.js +59 -10
- package/dist/dataConnectors/clickhouse.js +1 -0
- package/dist/dataConnectors/mongo.js +3 -4
- package/dist/dataConnectors/sqlite.js +10 -0
- package/dist/index.js +50 -34
- package/dist/modules/configValidator.js +25 -16
- package/dist/modules/operationalResource.js +25 -25
- package/dist/modules/restApi.js +106 -96
- package/dist/modules/utils.js +16 -0
- package/index.ts +68 -43
- package/modules/configValidator.ts +26 -21
- package/modules/operationalResource.ts +39 -45
- 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 +54 -33
package/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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,
|
|
@@ -22,11 +22,14 @@ import AdminForthPlugin from './basePlugin.js';
|
|
|
22
22
|
import ConfigValidator from './modules/configValidator.js';
|
|
23
23
|
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
24
24
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
25
|
+
import OperationalResource from './modules/operationalResource.js';
|
|
26
|
+
import { error } from 'console';
|
|
25
27
|
|
|
26
28
|
// exports
|
|
27
29
|
export * from './types/AdminForthConfig.js';
|
|
28
30
|
export { interpretResource };
|
|
29
31
|
export { AdminForthPlugin };
|
|
32
|
+
export { suggestIfTypo };
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class AdminForth implements IAdminForth {
|
|
@@ -52,7 +55,7 @@ class AdminForth implements IAdminForth {
|
|
|
52
55
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
53
56
|
configValidator: IConfigValidator;
|
|
54
57
|
restApi: AdminForthRestAPI;
|
|
55
|
-
|
|
58
|
+
operationalResources: {
|
|
56
59
|
[resourceId: string]: IOperationalResource,
|
|
57
60
|
}
|
|
58
61
|
baseUrlSlashed: string;
|
|
@@ -120,21 +123,24 @@ class AdminForth implements IAdminForth {
|
|
|
120
123
|
this.config.dataSources.forEach((ds) => {
|
|
121
124
|
const dbType = ds.url.split(':')[0];
|
|
122
125
|
if (!this.config.databaseConnectors[dbType]) {
|
|
123
|
-
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`);
|
|
124
127
|
}
|
|
125
128
|
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
|
|
126
129
|
});
|
|
127
130
|
|
|
128
131
|
await Promise.all(this.config.resources.map(async (res) => {
|
|
129
132
|
if (!this.connectors[res.dataSource]) {
|
|
130
|
-
|
|
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
|
+
);
|
|
131
137
|
}
|
|
132
138
|
const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
|
|
133
139
|
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
134
140
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
135
141
|
}
|
|
136
142
|
if (fieldTypes === null) {
|
|
137
|
-
console.error(
|
|
143
|
+
console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
138
144
|
return;
|
|
139
145
|
}
|
|
140
146
|
if (!res.columns) {
|
|
@@ -143,7 +149,8 @@ class AdminForth implements IAdminForth {
|
|
|
143
149
|
|
|
144
150
|
res.columns.forEach((col, i) => {
|
|
145
151
|
if (!fieldTypes[col.name] && !col.virtual) {
|
|
146
|
-
|
|
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}'?` : ''}`);
|
|
147
154
|
}
|
|
148
155
|
// first find discovered values, but allow override
|
|
149
156
|
res.columns[i] = { ...fieldTypes[col.name], ...col };
|
|
@@ -160,6 +167,11 @@ class AdminForth implements IAdminForth {
|
|
|
160
167
|
|
|
161
168
|
this.statuses.dbDiscover = 'done';
|
|
162
169
|
|
|
170
|
+
this.operationalResources = {};
|
|
171
|
+
this.config.resources.forEach((resource) => {
|
|
172
|
+
this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
|
|
173
|
+
});
|
|
174
|
+
|
|
163
175
|
// console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
|
|
164
176
|
}
|
|
165
177
|
|
|
@@ -168,9 +180,12 @@ class AdminForth implements IAdminForth {
|
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
async getUserByPk(pk: string) {
|
|
171
|
-
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);
|
|
172
184
|
if (!resource) {
|
|
173
|
-
|
|
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
|
+
);
|
|
174
189
|
}
|
|
175
190
|
const users = await this.connectors[resource.dataSource].getData({
|
|
176
191
|
resource,
|
|
@@ -184,46 +199,31 @@ class AdminForth implements IAdminForth {
|
|
|
184
199
|
return users.data[0] || null;
|
|
185
200
|
}
|
|
186
201
|
|
|
187
|
-
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
|
+
|
|
188
207
|
for (const column of resource.columns) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
(column.required as {create?: boolean, edit?: boolean}) ?.create &&
|
|
198
|
-
record[column.name] === undefined &&
|
|
199
|
-
column.showIn.includes(AdminForthResourcePages.create)
|
|
200
|
-
) {
|
|
201
|
-
return { error: `Column '${column.name}' is required` };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (column.isUnique) {
|
|
205
|
-
const existingRecord = await this.connectors[resource.dataSource].getData({
|
|
206
|
-
resource,
|
|
207
|
-
filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value: record[column.name] }],
|
|
208
|
-
limit: 1,
|
|
209
|
-
sort: [],
|
|
210
|
-
offset: 0
|
|
211
|
-
});
|
|
212
|
-
if (existingRecord.data.length > 0) {
|
|
213
|
-
return { error: `Record with ${column.name} ${record[column.name]} already exists` };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
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
216
|
}
|
|
217
217
|
|
|
218
218
|
// execute hook if needed
|
|
219
219
|
for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
|
|
220
|
-
const resp = await hook({ resource, record, adminUser });
|
|
220
|
+
const resp = await hook({ recordId: undefined, resource, record, adminUser });
|
|
221
221
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
222
222
|
throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
if (resp.error) {
|
|
226
|
-
return { error: resp.error };
|
|
226
|
+
return { error: resp.error, ok: false };
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -235,25 +235,50 @@ class AdminForth implements IAdminForth {
|
|
|
235
235
|
}
|
|
236
236
|
const connector = this.connectors[resource.dataSource];
|
|
237
237
|
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
|
|
238
|
-
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
|
+
|
|
239
245
|
// execute hook if needed
|
|
240
246
|
for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
|
|
241
247
|
console.log('Hook afterSave', hook);
|
|
242
|
-
const resp = await hook({
|
|
248
|
+
const resp = await hook({
|
|
249
|
+
recordId: primaryKey,
|
|
250
|
+
resource,
|
|
251
|
+
record: createdRecord,
|
|
252
|
+
adminUser
|
|
253
|
+
});
|
|
254
|
+
|
|
243
255
|
if (!resp || (!resp.ok && !resp.error)) {
|
|
244
256
|
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
245
257
|
}
|
|
246
258
|
|
|
247
259
|
if (resp.error) {
|
|
248
|
-
return { error: resp.error };
|
|
260
|
+
return { error: resp.error, ok: false };
|
|
249
261
|
}
|
|
250
262
|
}
|
|
251
263
|
|
|
252
|
-
return { ok
|
|
264
|
+
return { ok, error, createdRecord };
|
|
253
265
|
}
|
|
254
266
|
|
|
255
267
|
resource(resourceId: string) {
|
|
256
|
-
|
|
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];
|
|
257
282
|
}
|
|
258
283
|
|
|
259
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
|
}
|
|
@@ -1,40 +1,19 @@
|
|
|
1
|
+
import { error } from 'console';
|
|
1
2
|
import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// count: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<number>;
|
|
10
|
-
|
|
11
|
-
// create: (record: any) => Promise<any>;
|
|
12
|
-
|
|
13
|
-
// update: (primaryKey: any, record: any) => Promise<any>;
|
|
14
|
-
|
|
15
|
-
// delete: (primaryKey: any) => Promise<boolean>;
|
|
16
|
-
|
|
17
|
-
// deleteMany: (primaryKeys: any[]) => Promise<boolean>;
|
|
18
|
-
// }
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// async getData({ resource, limit, offset, sort, filters }: {
|
|
22
|
-
// resource: AdminForthResource,
|
|
23
|
-
// limit: number,
|
|
24
|
-
// offset: number,
|
|
25
|
-
// sort: { field: string, direction: AdminForthSortDirections }[],
|
|
26
|
-
// filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
27
|
-
// }): Promise<{ data: any[], total: number }> {
|
|
28
|
-
|
|
29
|
-
function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[]): IAdminForthFilter[] {
|
|
30
|
-
return (typeof filter === 'object' ? [filter] : filter) as IAdminForthFilter[];
|
|
5
|
+
function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): IAdminForthFilter[] {
|
|
6
|
+
if (!filter) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
return (Array.isArray(filter) ? filter : [filter]) as IAdminForthFilter[];
|
|
31
10
|
}
|
|
32
11
|
|
|
33
12
|
function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
|
|
34
|
-
return (
|
|
13
|
+
return (Array.isArray(sort) ? sort : [sort]) as IAdminForthSort[];
|
|
35
14
|
}
|
|
36
15
|
|
|
37
|
-
export class OperationalResource implements IOperationalResource {
|
|
16
|
+
export default class OperationalResource implements IOperationalResource {
|
|
38
17
|
dataConnector: IAdminForthDataSourceConnectorBase;
|
|
39
18
|
resourceConfig: AdminForthResource;
|
|
40
19
|
|
|
@@ -43,42 +22,58 @@ export class OperationalResource implements IOperationalResource {
|
|
|
43
22
|
this.resourceConfig = resourceConfig;
|
|
44
23
|
}
|
|
45
24
|
|
|
46
|
-
async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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;
|
|
54
35
|
}
|
|
55
36
|
|
|
56
37
|
async list(
|
|
57
38
|
filter: IAdminForthFilter | IAdminForthFilter[],
|
|
58
|
-
limit: number,
|
|
59
|
-
offset: number,
|
|
39
|
+
limit: number | null,
|
|
40
|
+
offset: number | null,
|
|
60
41
|
sort: IAdminForthSort | IAdminForthSort[]
|
|
61
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
|
+
|
|
62
52
|
const { data } = await this.dataConnector.getData({
|
|
63
53
|
resource: this.resourceConfig,
|
|
64
54
|
filters: filtersIfFilter(filter),
|
|
65
|
-
limit,
|
|
66
|
-
offset,
|
|
55
|
+
limit: appliedLimit,
|
|
56
|
+
offset: appliedOffset,
|
|
67
57
|
sort: sortsIfSort(sort),
|
|
68
58
|
getTotals: false,
|
|
69
59
|
});
|
|
70
60
|
return data;
|
|
71
61
|
}
|
|
72
62
|
|
|
73
|
-
async count(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<number> {
|
|
63
|
+
async count(filter: IAdminForthFilter | IAdminForthFilter[] | undefined): Promise<number> {
|
|
74
64
|
return await this.dataConnector.getCount({
|
|
75
65
|
resource: this.resourceConfig,
|
|
76
66
|
filters: filtersIfFilter(filter),
|
|
77
67
|
});
|
|
78
68
|
}
|
|
79
69
|
|
|
80
|
-
async create(
|
|
81
|
-
|
|
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 };
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
async update(primaryKey: any, record: any): Promise<any> {
|
|
@@ -92,6 +87,5 @@ export class OperationalResource implements IOperationalResource {
|
|
|
92
87
|
async delete(primaryKey: any): Promise<boolean> {
|
|
93
88
|
return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
|
|
94
89
|
}
|
|
95
|
-
|
|
96
90
|
|
|
97
91
|
}
|