adminforth 1.1.72 → 1.1.74
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 +107 -0
- package/dataConnectors/mongo.ts +6 -12
- package/dataConnectors/postgres.ts +17 -37
- package/dataConnectors/sqlite.ts +44 -55
- package/dist/dataConnectors/baseConnector.js +90 -0
- package/dist/dataConnectors/mongo.js +6 -12
- package/dist/dataConnectors/postgres.js +12 -33
- package/dist/dataConnectors/sqlite.js +67 -83
- package/dist/index.js +8 -1
- package/dist/servers/express.js +4 -4
- package/index.ts +9 -1
- package/package.json +1 -1
- package/servers/express.ts +5 -5
- package/types/AdminForthConfig.ts +142 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AdminForthResource, AdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn } from "../types/AdminForthConfig.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export default class AdminForthBaseConnector implements AdminForthDataSourceConnectorBase {
|
|
5
|
+
getPrimaryKey(resource: AdminForthResource): string {
|
|
6
|
+
for (const col of resource.dataSourceColumns) {
|
|
7
|
+
if (col.primaryKey) {
|
|
8
|
+
return col.name;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
|
|
14
|
+
throw new Error('Method not implemented.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
|
|
18
|
+
resource: AdminForthResource,
|
|
19
|
+
limit: number,
|
|
20
|
+
offset: number,
|
|
21
|
+
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
22
|
+
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
23
|
+
}): Promise<{ data: any[], total: number }> {
|
|
24
|
+
throw new Error('Method not implemented.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
discoverFields(resource: AdminForthResource): Promise<{ [key: string]: AdminForthResourceColumn; }> {
|
|
28
|
+
throw new Error('Method not implemented.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getFieldValue(field: AdminForthResourceColumn, value: any) {
|
|
32
|
+
throw new Error('Method not implemented.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setFieldValue(field: AdminForthResourceColumn, value: any) {
|
|
36
|
+
throw new Error('Method not implemented.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
|
|
40
|
+
throw new Error('Method not implemented.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createRecord({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
|
|
44
|
+
throw new Error('Method not implemented.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
|
|
48
|
+
throw new Error('Method not implemented.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<void> {
|
|
52
|
+
throw new Error('Method not implemented.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async getData({ resource, limit, offset, sort, filters }: {
|
|
57
|
+
resource: AdminForthResource,
|
|
58
|
+
limit: number,
|
|
59
|
+
offset: number,
|
|
60
|
+
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
61
|
+
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
62
|
+
}): Promise<{ data: any[], total: number }> {
|
|
63
|
+
if (filters) {
|
|
64
|
+
filters.map((f) => {
|
|
65
|
+
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
66
|
+
f.value = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
67
|
+
} else {
|
|
68
|
+
f.value = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const d = await this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters });
|
|
74
|
+
// call getFieldValue for each field
|
|
75
|
+
d.data.map((record) => {
|
|
76
|
+
for (const col of resource.dataSourceColumns) {
|
|
77
|
+
record[col.name] = this.getFieldValue(col, record[col.name]);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return d;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
|
|
85
|
+
const mm = await this.getMinMaxForColumnsWithOriginalTypes({ resource, columns });
|
|
86
|
+
const result = {};
|
|
87
|
+
for (const col of columns) {
|
|
88
|
+
result[col.name] = {
|
|
89
|
+
min: this.getFieldValue(col, mm[col.name].min),
|
|
90
|
+
max: this.getFieldValue(col, mm[col.name].max),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any> {
|
|
97
|
+
return this.getRecordByPrimaryKeyWithOriginalTypes(resource, recordId).then((record) => {
|
|
98
|
+
const newRecord = {};
|
|
99
|
+
for (const col of resource.dataSourceColumns) {
|
|
100
|
+
newRecord[col.name] = this.getFieldValue(col, record[col.name]);
|
|
101
|
+
}
|
|
102
|
+
return newRecord;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
}
|
package/dataConnectors/mongo.ts
CHANGED
|
@@ -83,7 +83,7 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
83
83
|
return value;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
async
|
|
86
|
+
async getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
|
|
87
87
|
const tableName = resource.table;
|
|
88
88
|
const collection = this.db.db().collection(tableName);
|
|
89
89
|
const row = await collection.findOne({ [this.getPrimaryKey(resource)]: key });
|
|
@@ -97,7 +97,7 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
97
97
|
continue; // should I continue or throw an error?
|
|
98
98
|
throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
|
|
99
99
|
}
|
|
100
|
-
newRow[key_1] =
|
|
100
|
+
newRow[key_1] = value;
|
|
101
101
|
}
|
|
102
102
|
console.log('newRow', newRow);
|
|
103
103
|
return newRow;
|
|
@@ -121,7 +121,7 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
121
121
|
return value;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
async
|
|
124
|
+
async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
|
|
125
125
|
// const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
|
|
126
126
|
const tableName = resource.table;
|
|
127
127
|
|
|
@@ -140,7 +140,7 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
140
140
|
return { data: result, total }
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
async
|
|
143
|
+
async getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
|
|
144
144
|
const tableName = resource.table;
|
|
145
145
|
const collection = this.db.db().collection(tableName);
|
|
146
146
|
const result = {};
|
|
@@ -160,18 +160,12 @@ class MongoConnector implements AdminForthDataSourceConnector {
|
|
|
160
160
|
const columns = Object.keys(record);
|
|
161
161
|
const newRecord = {};
|
|
162
162
|
for (const colName of columns) {
|
|
163
|
-
|
|
164
|
-
if (col) {
|
|
165
|
-
newRecord[colName] = this.setFieldValue(col, record[colName]);
|
|
166
|
-
} else {
|
|
167
|
-
newRecord[colName] = record[colName];
|
|
168
|
-
}
|
|
163
|
+
newRecord[colName] = record[colName];
|
|
169
164
|
}
|
|
170
|
-
|
|
171
165
|
await collection.insertOne(newRecord);
|
|
172
166
|
}
|
|
173
167
|
|
|
174
|
-
async updateRecord({ resource, recordId,
|
|
168
|
+
async updateRecord({ resource, recordId, newValues }) {
|
|
175
169
|
const collection = this.db.db().collection(resource.table);
|
|
176
170
|
await collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
|
|
177
171
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
2
|
import pkg from 'pg';
|
|
3
3
|
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
|
|
4
|
-
|
|
4
|
+
import AdminForthBaseConnector from './baseConnector.js';
|
|
5
5
|
const { Client } = pkg;
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class PostgresConnector implements AdminForthDataSourceConnector {
|
|
8
|
+
class PostgresConnector extends AdminForthBaseConnector implements AdminForthDataSourceConnector {
|
|
9
9
|
|
|
10
10
|
db: any;
|
|
11
11
|
|
|
12
12
|
constructor({ url }) {
|
|
13
|
+
super();
|
|
13
14
|
this.db = new Client({
|
|
14
15
|
connectionString: url
|
|
15
16
|
});
|
|
@@ -34,12 +35,12 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
34
35
|
[AdminForthFilterOperators.ILIKE]: 'ILIKE',
|
|
35
36
|
[AdminForthFilterOperators.IN]: 'IN',
|
|
36
37
|
[AdminForthFilterOperators.NIN]: 'NOT IN',
|
|
37
|
-
|
|
38
|
+
};
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
SortDirectionsMap = {
|
|
40
41
|
[AdminForthSortDirections.asc]: 'ASC',
|
|
41
42
|
[AdminForthSortDirections.desc]: 'DESC',
|
|
42
|
-
|
|
43
|
+
};
|
|
43
44
|
|
|
44
45
|
async discoverFields(resource) {
|
|
45
46
|
const tableName = resource.table;
|
|
@@ -125,8 +126,8 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
125
126
|
field.default = row.dflt_value;
|
|
126
127
|
field.required = row.notnull && !row.dflt_value;
|
|
127
128
|
fieldTypes[row.name] = field
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
});
|
|
130
|
+
return fieldTypes;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
getFieldValue(field, value) {
|
|
@@ -153,15 +154,8 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
153
154
|
return value;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
getPrimaryKey(resource) {
|
|
157
|
-
for (const col of resource.dataSourceColumns) {
|
|
158
|
-
if (col.primaryKey) {
|
|
159
|
-
return col.name;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
157
|
|
|
164
|
-
async
|
|
158
|
+
async getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
|
|
165
159
|
const tableName = resource.table;
|
|
166
160
|
const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
|
|
167
161
|
const stmt = await this.db.query(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = $1`, [key]);
|
|
@@ -171,7 +165,7 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
171
165
|
}
|
|
172
166
|
const newRow = {};
|
|
173
167
|
for (const [key_1, value] of Object.entries(row)) {
|
|
174
|
-
newRow[key_1] =
|
|
168
|
+
newRow[key_1] = value;
|
|
175
169
|
}
|
|
176
170
|
return newRow;
|
|
177
171
|
}
|
|
@@ -192,7 +186,7 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
192
186
|
return value;
|
|
193
187
|
}
|
|
194
188
|
|
|
195
|
-
async
|
|
189
|
+
async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
|
|
196
190
|
const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
|
|
197
191
|
const tableName = resource.table;
|
|
198
192
|
|
|
@@ -220,12 +214,7 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
220
214
|
const filterValues = [];
|
|
221
215
|
filters.length ? filters.forEach((f) => {
|
|
222
216
|
// for arrays do set in map
|
|
223
|
-
let v;
|
|
224
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
225
|
-
v = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
226
|
-
} else {
|
|
227
|
-
v = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
228
|
-
}
|
|
217
|
+
let v = f.value;
|
|
229
218
|
|
|
230
219
|
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
231
220
|
filterValues.push(`%${v}%`);
|
|
@@ -247,12 +236,11 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
247
236
|
const rows = stmt.rows;
|
|
248
237
|
|
|
249
238
|
const total = (await this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
|
|
250
|
-
// run all fields via getFieldValue
|
|
251
239
|
return {
|
|
252
240
|
data: rows.map((row) => {
|
|
253
241
|
const newRow = {};
|
|
254
242
|
for (const [key, value] of Object.entries(row)) {
|
|
255
|
-
newRow[key] =
|
|
243
|
+
newRow[key] = value;
|
|
256
244
|
}
|
|
257
245
|
return newRow;
|
|
258
246
|
}),
|
|
@@ -260,15 +248,14 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
260
248
|
};
|
|
261
249
|
}
|
|
262
250
|
|
|
263
|
-
async
|
|
251
|
+
async getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
|
|
264
252
|
const tableName = resource.table;
|
|
265
253
|
const result = {};
|
|
266
254
|
await Promise.all(columns.map(async (col) => {
|
|
267
255
|
const stmt = await this.db.query(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
|
|
268
256
|
const { min, max } = stmt.rows[0];
|
|
269
257
|
result[col.name] = {
|
|
270
|
-
min
|
|
271
|
-
max: this.getFieldValue(col, max),
|
|
258
|
+
min, max,
|
|
272
259
|
};
|
|
273
260
|
}))
|
|
274
261
|
return result;
|
|
@@ -278,21 +265,14 @@ class PostgresConnector implements AdminForthDataSourceConnector {
|
|
|
278
265
|
const tableName = resource.table;
|
|
279
266
|
const columns = Object.keys(record);
|
|
280
267
|
const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
|
|
281
|
-
const values = columns.map((colName) =>
|
|
282
|
-
const col = resource.dataSourceColumns.find((col) => col.name == colName);
|
|
283
|
-
if (col) {
|
|
284
|
-
return this.setFieldValue(col, record[colName])
|
|
285
|
-
} else {
|
|
286
|
-
return record[colName];
|
|
287
|
-
}
|
|
288
|
-
});
|
|
268
|
+
const values = columns.map((colName) => record[colName]);
|
|
289
269
|
for (let i = 0; i < columns.length; i++) {
|
|
290
270
|
columns[i] = `"${columns[i]}"`;
|
|
291
271
|
}
|
|
292
272
|
await this.db.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values);
|
|
293
273
|
}
|
|
294
274
|
|
|
295
|
-
async updateRecord({ resource, recordId,
|
|
275
|
+
async updateRecord({ resource, recordId, newValues }) {
|
|
296
276
|
const values = [...Object.values(newValues), recordId];
|
|
297
277
|
const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
|
298
278
|
await this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE "${this.getPrimaryKey(resource)}" = $${values.length}`, values);
|
package/dataConnectors/sqlite.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import betterSqlite3 from 'better-sqlite3';
|
|
2
|
-
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
|
|
3
|
-
|
|
2
|
+
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
|
|
3
|
+
import AdminForthBaseConnector from './baseConnector.js';
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
|
|
6
|
-
class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
6
|
+
class SQLiteConnector extends AdminForthBaseConnector implements AdminForthDataSourceConnector {
|
|
7
7
|
|
|
8
8
|
db: any;
|
|
9
9
|
|
|
10
|
-
constructor({ url }) {
|
|
10
|
+
constructor({ url }: { url: string }) {
|
|
11
|
+
super();
|
|
11
12
|
// create connection here
|
|
12
|
-
|
|
13
13
|
this.db = betterSqlite3(url.replace('sqlite://', ''));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async discoverFields(resource) {
|
|
16
|
+
async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
|
|
17
17
|
const tableName = resource.table;
|
|
18
18
|
const stmt = this.db.prepare(`PRAGMA table_info(${tableName})`);
|
|
19
19
|
const rows = await stmt.all();
|
|
@@ -59,15 +59,7 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
59
59
|
return fieldTypes;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
for (const col of resource.dataSourceColumns) {
|
|
64
|
-
if (col.primaryKey) {
|
|
65
|
-
return col.name;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
getFieldValue(field, value) {
|
|
62
|
+
getFieldValue(field: AdminForthResourceColumn, value: any): any {
|
|
71
63
|
if (field.type == AdminForthDataTypes.DATETIME) {
|
|
72
64
|
if (!value) {
|
|
73
65
|
return null;
|
|
@@ -93,7 +85,7 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
93
85
|
return value;
|
|
94
86
|
}
|
|
95
87
|
|
|
96
|
-
async
|
|
88
|
+
async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, key: any): Promise<any> {
|
|
97
89
|
const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
|
|
98
90
|
const tableName = resource.table;
|
|
99
91
|
const stmt = this.db.prepare(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
@@ -103,12 +95,12 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
103
95
|
}
|
|
104
96
|
const newRow = {};
|
|
105
97
|
for (const [key, value] of Object.entries(row)) {
|
|
106
|
-
newRow[key] =
|
|
98
|
+
newRow[key] = value;
|
|
107
99
|
}
|
|
108
100
|
return newRow;
|
|
109
101
|
}
|
|
110
102
|
|
|
111
|
-
setFieldValue(field, value) {
|
|
103
|
+
setFieldValue(field: AdminForthResourceColumn, value: any): any {
|
|
112
104
|
if (field.type == AdminForthDataTypes.DATETIME) {
|
|
113
105
|
if (!value) {
|
|
114
106
|
return null;
|
|
@@ -146,7 +138,13 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
146
138
|
};
|
|
147
139
|
|
|
148
140
|
|
|
149
|
-
|
|
141
|
+
async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
|
|
142
|
+
resource: AdminForthResource,
|
|
143
|
+
limit: number,
|
|
144
|
+
offset: number,
|
|
145
|
+
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
146
|
+
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
147
|
+
}): Promise<{ data: any[], total: number }> {
|
|
150
148
|
const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
|
|
151
149
|
const tableName = resource.table;
|
|
152
150
|
|
|
@@ -165,17 +163,11 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
165
163
|
return `${field} ${operator} ${placeholder}`
|
|
166
164
|
}).join(' AND ')}` : '';
|
|
167
165
|
|
|
168
|
-
|
|
169
166
|
const filterValues = [];
|
|
170
167
|
|
|
171
168
|
filters.length ? filters.forEach((f) => {
|
|
172
169
|
// for arrays do set in map
|
|
173
|
-
|
|
174
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
175
|
-
v = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
176
|
-
} else {
|
|
177
|
-
v = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
178
|
-
}
|
|
170
|
+
const v = f.value;
|
|
179
171
|
|
|
180
172
|
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
181
173
|
filterValues.push(`%${v}%`);
|
|
@@ -196,15 +188,17 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
196
188
|
if (process.env.HEAVY_DEBUG) {
|
|
197
189
|
console.log('🪲 SQLITE Query', q, 'params:', d);
|
|
198
190
|
}
|
|
199
|
-
const rows = stmt.all(d);
|
|
191
|
+
const rows = await stmt.all(d);
|
|
192
|
+
|
|
193
|
+
const total = (
|
|
194
|
+
await this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues])
|
|
195
|
+
)['COUNT(*)'];
|
|
200
196
|
|
|
201
|
-
const total = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues])['COUNT(*)'];
|
|
202
|
-
// run all fields via getFieldValue
|
|
203
197
|
return {
|
|
204
198
|
data: rows.map((row) => {
|
|
205
199
|
const newRow = {};
|
|
206
200
|
for (const [key, value] of Object.entries(row)) {
|
|
207
|
-
newRow[key] =
|
|
201
|
+
newRow[key] = value;
|
|
208
202
|
}
|
|
209
203
|
return newRow;
|
|
210
204
|
}),
|
|
@@ -212,50 +206,45 @@ class SQLiteConnector implements AdminForthDataSourceConnector {
|
|
|
212
206
|
};
|
|
213
207
|
}
|
|
214
208
|
|
|
215
|
-
async
|
|
209
|
+
async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
|
|
216
210
|
const tableName = resource.table;
|
|
217
211
|
const result = {};
|
|
218
212
|
await Promise.all(columns.map(async (col) => {
|
|
219
213
|
const stmt = await this.db.prepare(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
|
|
220
214
|
const { min, max } = stmt.get();
|
|
221
215
|
result[col.name] = {
|
|
222
|
-
min
|
|
223
|
-
max: this.getFieldValue(col, max),
|
|
216
|
+
min, max,
|
|
224
217
|
};
|
|
225
218
|
}))
|
|
226
219
|
return result;
|
|
227
220
|
}
|
|
228
221
|
|
|
229
|
-
async createRecord({ resource, record }) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return this.setFieldValue(col, record[colName])
|
|
237
|
-
} else {
|
|
238
|
-
return record[colName];
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
this.db.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`).run(values);
|
|
222
|
+
async createRecord({ resource, record }: { resource: AdminForthResource, record: any }) {
|
|
223
|
+
const tableName = resource.table;
|
|
224
|
+
const columns = Object.keys(record);
|
|
225
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
226
|
+
const values = columns.map((colName) => record[colName]);
|
|
227
|
+
const q = this.db.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`)
|
|
228
|
+
await q.run(values);
|
|
242
229
|
}
|
|
243
230
|
|
|
244
|
-
async updateRecord({ resource, recordId,
|
|
245
|
-
|
|
246
|
-
|
|
231
|
+
async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
|
|
232
|
+
const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
|
|
233
|
+
const values = [...Object.values(newValues), recordId];
|
|
247
234
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
235
|
+
const q = this.db.prepare(
|
|
236
|
+
`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`
|
|
237
|
+
)
|
|
238
|
+
await q.run(values);
|
|
251
239
|
}
|
|
252
240
|
|
|
253
|
-
async deleteRecord({ resource, recordId }) {
|
|
254
|
-
|
|
241
|
+
async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }) {
|
|
242
|
+
const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
243
|
+
await q.run(recordId);
|
|
255
244
|
}
|
|
256
245
|
|
|
257
246
|
close() {
|
|
258
|
-
|
|
247
|
+
this.db.close();
|
|
259
248
|
}
|
|
260
249
|
}
|
|
261
250
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AdminForthFilterOperators } from "../types/AdminForthConfig.js";
|
|
11
|
+
export default class AdminForthBaseConnector {
|
|
12
|
+
getPrimaryKey(resource) {
|
|
13
|
+
for (const col of resource.dataSourceColumns) {
|
|
14
|
+
if (col.primaryKey) {
|
|
15
|
+
return col.name;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource, id) {
|
|
20
|
+
throw new Error('Method not implemented.');
|
|
21
|
+
}
|
|
22
|
+
getDataWithOriginalTypes({ resource, limit, offset, sort, filters }) {
|
|
23
|
+
throw new Error('Method not implemented.');
|
|
24
|
+
}
|
|
25
|
+
discoverFields(resource) {
|
|
26
|
+
throw new Error('Method not implemented.');
|
|
27
|
+
}
|
|
28
|
+
getFieldValue(field, value) {
|
|
29
|
+
throw new Error('Method not implemented.');
|
|
30
|
+
}
|
|
31
|
+
setFieldValue(field, value) {
|
|
32
|
+
throw new Error('Method not implemented.');
|
|
33
|
+
}
|
|
34
|
+
getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
|
|
35
|
+
throw new Error('Method not implemented.');
|
|
36
|
+
}
|
|
37
|
+
createRecord({ resource, record }) {
|
|
38
|
+
throw new Error('Method not implemented.');
|
|
39
|
+
}
|
|
40
|
+
updateRecord({ resource, recordId, newValues }) {
|
|
41
|
+
throw new Error('Method not implemented.');
|
|
42
|
+
}
|
|
43
|
+
deleteRecord({ resource, recordId }) {
|
|
44
|
+
throw new Error('Method not implemented.');
|
|
45
|
+
}
|
|
46
|
+
getData(_a) {
|
|
47
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
|
|
48
|
+
if (filters) {
|
|
49
|
+
filters.map((f) => {
|
|
50
|
+
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
51
|
+
f.value = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
f.value = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const d = yield this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters });
|
|
59
|
+
// call getFieldValue for each field
|
|
60
|
+
d.data.map((record) => {
|
|
61
|
+
for (const col of resource.dataSourceColumns) {
|
|
62
|
+
record[col.name] = this.getFieldValue(col, record[col.name]);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return d;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
getMinMaxForColumns(_a) {
|
|
69
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
|
|
70
|
+
const mm = yield this.getMinMaxForColumnsWithOriginalTypes({ resource, columns });
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const col of columns) {
|
|
73
|
+
result[col.name] = {
|
|
74
|
+
min: this.getFieldValue(col, mm[col.name].min),
|
|
75
|
+
max: this.getFieldValue(col, mm[col.name].max),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
getRecordByPrimaryKey(resource, recordId) {
|
|
82
|
+
return this.getRecordByPrimaryKeyWithOriginalTypes(resource, recordId).then((record) => {
|
|
83
|
+
const newRecord = {};
|
|
84
|
+
for (const col of resource.dataSourceColumns) {
|
|
85
|
+
newRecord[col.name] = this.getFieldValue(col, record[col.name]);
|
|
86
|
+
}
|
|
87
|
+
return newRecord;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -84,7 +84,7 @@ class MongoConnector {
|
|
|
84
84
|
}
|
|
85
85
|
return value;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
|
|
88
88
|
return __awaiter(this, void 0, void 0, function* () {
|
|
89
89
|
const tableName = resource.table;
|
|
90
90
|
const collection = this.db.db().collection(tableName);
|
|
@@ -99,7 +99,7 @@ class MongoConnector {
|
|
|
99
99
|
continue; // should I continue or throw an error?
|
|
100
100
|
throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
|
|
101
101
|
}
|
|
102
|
-
newRow[key_1] =
|
|
102
|
+
newRow[key_1] = value;
|
|
103
103
|
}
|
|
104
104
|
console.log('newRow', newRow);
|
|
105
105
|
return newRow;
|
|
@@ -124,7 +124,7 @@ class MongoConnector {
|
|
|
124
124
|
}
|
|
125
125
|
return value;
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
getDataWithOriginalTypes(_a) {
|
|
128
128
|
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
|
|
129
129
|
// const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
|
|
130
130
|
const tableName = resource.table;
|
|
@@ -142,7 +142,7 @@ class MongoConnector {
|
|
|
142
142
|
return { data: result, total };
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
getMinMaxForColumnsWithOriginalTypes(_a) {
|
|
146
146
|
return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
|
|
147
147
|
const tableName = resource.table;
|
|
148
148
|
const collection = this.db.db().collection(tableName);
|
|
@@ -164,19 +164,13 @@ class MongoConnector {
|
|
|
164
164
|
const columns = Object.keys(record);
|
|
165
165
|
const newRecord = {};
|
|
166
166
|
for (const colName of columns) {
|
|
167
|
-
|
|
168
|
-
if (col) {
|
|
169
|
-
newRecord[colName] = this.setFieldValue(col, record[colName]);
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
newRecord[colName] = record[colName];
|
|
173
|
-
}
|
|
167
|
+
newRecord[colName] = record[colName];
|
|
174
168
|
}
|
|
175
169
|
yield collection.insertOne(newRecord);
|
|
176
170
|
});
|
|
177
171
|
}
|
|
178
172
|
updateRecord(_a) {
|
|
179
|
-
return __awaiter(this, arguments, void 0, function* ({ resource, recordId,
|
|
173
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
180
174
|
const collection = this.db.db().collection(resource.table);
|
|
181
175
|
yield collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
|
|
182
176
|
});
|
|
@@ -10,9 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import dayjs from 'dayjs';
|
|
11
11
|
import pkg from 'pg';
|
|
12
12
|
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
|
|
13
|
+
import AdminForthBaseConnector from './baseConnector.js';
|
|
13
14
|
const { Client } = pkg;
|
|
14
|
-
class PostgresConnector {
|
|
15
|
+
class PostgresConnector extends AdminForthBaseConnector {
|
|
15
16
|
constructor({ url }) {
|
|
17
|
+
super();
|
|
16
18
|
this.OperatorsMap = {
|
|
17
19
|
[AdminForthFilterOperators.EQ]: '=',
|
|
18
20
|
[AdminForthFilterOperators.NE]: '!=',
|
|
@@ -152,14 +154,7 @@ class PostgresConnector {
|
|
|
152
154
|
}
|
|
153
155
|
return value;
|
|
154
156
|
}
|
|
155
|
-
|
|
156
|
-
for (const col of resource.dataSourceColumns) {
|
|
157
|
-
if (col.primaryKey) {
|
|
158
|
-
return col.name;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
getRecordByPrimaryKey(resource, key) {
|
|
157
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
|
|
163
158
|
return __awaiter(this, void 0, void 0, function* () {
|
|
164
159
|
const tableName = resource.table;
|
|
165
160
|
const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
|
|
@@ -170,7 +165,7 @@ class PostgresConnector {
|
|
|
170
165
|
}
|
|
171
166
|
const newRow = {};
|
|
172
167
|
for (const [key_1, value] of Object.entries(row)) {
|
|
173
|
-
newRow[key_1] =
|
|
168
|
+
newRow[key_1] = value;
|
|
174
169
|
}
|
|
175
170
|
return newRow;
|
|
176
171
|
});
|
|
@@ -192,7 +187,7 @@ class PostgresConnector {
|
|
|
192
187
|
}
|
|
193
188
|
return value;
|
|
194
189
|
}
|
|
195
|
-
|
|
190
|
+
getDataWithOriginalTypes(_a) {
|
|
196
191
|
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
|
|
197
192
|
const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
|
|
198
193
|
const tableName = resource.table;
|
|
@@ -220,13 +215,7 @@ class PostgresConnector {
|
|
|
220
215
|
const filterValues = [];
|
|
221
216
|
filters.length ? filters.forEach((f) => {
|
|
222
217
|
// for arrays do set in map
|
|
223
|
-
let v;
|
|
224
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
225
|
-
v = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
v = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
229
|
-
}
|
|
218
|
+
let v = f.value;
|
|
230
219
|
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
231
220
|
filterValues.push(`%${v}%`);
|
|
232
221
|
}
|
|
@@ -247,12 +236,11 @@ class PostgresConnector {
|
|
|
247
236
|
const stmt = yield this.db.query(selectQuery, d);
|
|
248
237
|
const rows = stmt.rows;
|
|
249
238
|
const total = (yield this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
|
|
250
|
-
// run all fields via getFieldValue
|
|
251
239
|
return {
|
|
252
240
|
data: rows.map((row) => {
|
|
253
241
|
const newRow = {};
|
|
254
242
|
for (const [key, value] of Object.entries(row)) {
|
|
255
|
-
newRow[key] =
|
|
243
|
+
newRow[key] = value;
|
|
256
244
|
}
|
|
257
245
|
return newRow;
|
|
258
246
|
}),
|
|
@@ -260,7 +248,7 @@ class PostgresConnector {
|
|
|
260
248
|
};
|
|
261
249
|
});
|
|
262
250
|
}
|
|
263
|
-
|
|
251
|
+
getMinMaxForColumnsWithOriginalTypes(_a) {
|
|
264
252
|
return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
|
|
265
253
|
const tableName = resource.table;
|
|
266
254
|
const result = {};
|
|
@@ -268,8 +256,7 @@ class PostgresConnector {
|
|
|
268
256
|
const stmt = yield this.db.query(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
|
|
269
257
|
const { min, max } = stmt.rows[0];
|
|
270
258
|
result[col.name] = {
|
|
271
|
-
min
|
|
272
|
-
max: this.getFieldValue(col, max),
|
|
259
|
+
min, max,
|
|
273
260
|
};
|
|
274
261
|
})));
|
|
275
262
|
return result;
|
|
@@ -280,15 +267,7 @@ class PostgresConnector {
|
|
|
280
267
|
const tableName = resource.table;
|
|
281
268
|
const columns = Object.keys(record);
|
|
282
269
|
const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
|
|
283
|
-
const values = columns.map((colName) =>
|
|
284
|
-
const col = resource.dataSourceColumns.find((col) => col.name == colName);
|
|
285
|
-
if (col) {
|
|
286
|
-
return this.setFieldValue(col, record[colName]);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
return record[colName];
|
|
290
|
-
}
|
|
291
|
-
});
|
|
270
|
+
const values = columns.map((colName) => record[colName]);
|
|
292
271
|
for (let i = 0; i < columns.length; i++) {
|
|
293
272
|
columns[i] = `"${columns[i]}"`;
|
|
294
273
|
}
|
|
@@ -296,7 +275,7 @@ class PostgresConnector {
|
|
|
296
275
|
});
|
|
297
276
|
}
|
|
298
277
|
updateRecord(_a) {
|
|
299
|
-
return __awaiter(this, arguments, void 0, function* ({ resource, recordId,
|
|
278
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
300
279
|
const values = [...Object.values(newValues), recordId];
|
|
301
280
|
const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `"${col}" = $${i + 1}`).join(', ');
|
|
302
281
|
yield this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE "${this.getPrimaryKey(resource)}" = $${values.length}`, values);
|
|
@@ -9,10 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import betterSqlite3 from 'better-sqlite3';
|
|
11
11
|
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
|
|
12
|
+
import AdminForthBaseConnector from './baseConnector.js';
|
|
12
13
|
import dayjs from 'dayjs';
|
|
13
|
-
class SQLiteConnector {
|
|
14
|
+
class SQLiteConnector extends AdminForthBaseConnector {
|
|
14
15
|
constructor({ url }) {
|
|
15
|
-
|
|
16
|
+
super();
|
|
16
17
|
this.OperatorsMap = {
|
|
17
18
|
[AdminForthFilterOperators.EQ]: '=',
|
|
18
19
|
[AdminForthFilterOperators.NE]: '!=',
|
|
@@ -29,6 +30,7 @@ class SQLiteConnector {
|
|
|
29
30
|
[AdminForthSortDirections.asc]: 'ASC',
|
|
30
31
|
[AdminForthSortDirections.desc]: 'DESC',
|
|
31
32
|
};
|
|
33
|
+
// create connection here
|
|
32
34
|
this.db = betterSqlite3(url.replace('sqlite://', ''));
|
|
33
35
|
}
|
|
34
36
|
discoverFields(resource) {
|
|
@@ -85,13 +87,6 @@ class SQLiteConnector {
|
|
|
85
87
|
return fieldTypes;
|
|
86
88
|
});
|
|
87
89
|
}
|
|
88
|
-
getPrimaryKey(resource) {
|
|
89
|
-
for (const col of resource.dataSourceColumns) {
|
|
90
|
-
if (col.primaryKey) {
|
|
91
|
-
return col.name;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
90
|
getFieldValue(field, value) {
|
|
96
91
|
if (field.type == AdminForthDataTypes.DATETIME) {
|
|
97
92
|
if (!value) {
|
|
@@ -118,7 +113,7 @@ class SQLiteConnector {
|
|
|
118
113
|
}
|
|
119
114
|
return value;
|
|
120
115
|
}
|
|
121
|
-
|
|
116
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
|
|
122
117
|
return __awaiter(this, void 0, void 0, function* () {
|
|
123
118
|
const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
|
|
124
119
|
const tableName = resource.table;
|
|
@@ -129,7 +124,7 @@ class SQLiteConnector {
|
|
|
129
124
|
}
|
|
130
125
|
const newRow = {};
|
|
131
126
|
for (const [key, value] of Object.entries(row)) {
|
|
132
|
-
newRow[key] =
|
|
127
|
+
newRow[key] = value;
|
|
133
128
|
}
|
|
134
129
|
return newRow;
|
|
135
130
|
});
|
|
@@ -153,65 +148,60 @@ class SQLiteConnector {
|
|
|
153
148
|
}
|
|
154
149
|
return value;
|
|
155
150
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
else if (f.operator == AdminForthFilterOperators.ILIKE) {
|
|
167
|
-
placeholder = `LOWER(?)`;
|
|
168
|
-
field = `LOWER(${f.field})`;
|
|
169
|
-
operator = 'LIKE';
|
|
170
|
-
}
|
|
171
|
-
return `${field} ${operator} ${placeholder}`;
|
|
172
|
-
}).join(' AND ')}` : '';
|
|
173
|
-
const filterValues = [];
|
|
174
|
-
filters.length ? filters.forEach((f) => {
|
|
175
|
-
// for arrays do set in map
|
|
176
|
-
let v;
|
|
177
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
178
|
-
v = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
v = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
|
|
182
|
-
}
|
|
183
|
-
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
184
|
-
filterValues.push(`%${v}%`);
|
|
185
|
-
}
|
|
186
|
-
else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
187
|
-
filterValues.push(...v);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
filterValues.push(v);
|
|
191
|
-
}
|
|
192
|
-
}) : [];
|
|
193
|
-
const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
|
|
194
|
-
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
|
|
195
|
-
const stmt = this.db.prepare(q);
|
|
196
|
-
const d = [...filterValues, limit, offset];
|
|
197
|
-
if (process.env.HEAVY_DEBUG) {
|
|
198
|
-
console.log('🪲 SQLITE Query', q, 'params:', d);
|
|
199
|
-
}
|
|
200
|
-
const rows = stmt.all(d);
|
|
201
|
-
const total = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues])['COUNT(*)'];
|
|
202
|
-
// run all fields via getFieldValue
|
|
203
|
-
return {
|
|
204
|
-
data: rows.map((row) => {
|
|
205
|
-
const newRow = {};
|
|
206
|
-
for (const [key, value] of Object.entries(row)) {
|
|
207
|
-
newRow[key] = this.getFieldValue(resource.dataSourceColumns.find((col) => col.name == key), value);
|
|
151
|
+
getDataWithOriginalTypes(_a) {
|
|
152
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
|
|
153
|
+
const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
|
|
154
|
+
const tableName = resource.table;
|
|
155
|
+
const where = filters.length ? `WHERE ${filters.map((f, i) => {
|
|
156
|
+
let placeholder = '?';
|
|
157
|
+
let field = f.field;
|
|
158
|
+
let operator = this.OperatorsMap[f.operator];
|
|
159
|
+
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
160
|
+
placeholder = `(${f.value.map(() => '?').join(', ')})`;
|
|
208
161
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
162
|
+
else if (f.operator == AdminForthFilterOperators.ILIKE) {
|
|
163
|
+
placeholder = `LOWER(?)`;
|
|
164
|
+
field = `LOWER(${f.field})`;
|
|
165
|
+
operator = 'LIKE';
|
|
166
|
+
}
|
|
167
|
+
return `${field} ${operator} ${placeholder}`;
|
|
168
|
+
}).join(' AND ')}` : '';
|
|
169
|
+
const filterValues = [];
|
|
170
|
+
filters.length ? filters.forEach((f) => {
|
|
171
|
+
// for arrays do set in map
|
|
172
|
+
const v = f.value;
|
|
173
|
+
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
174
|
+
filterValues.push(`%${v}%`);
|
|
175
|
+
}
|
|
176
|
+
else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
177
|
+
filterValues.push(...v);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
filterValues.push(v);
|
|
181
|
+
}
|
|
182
|
+
}) : [];
|
|
183
|
+
const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
|
|
184
|
+
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
|
|
185
|
+
const stmt = this.db.prepare(q);
|
|
186
|
+
const d = [...filterValues, limit, offset];
|
|
187
|
+
if (process.env.HEAVY_DEBUG) {
|
|
188
|
+
console.log('🪲 SQLITE Query', q, 'params:', d);
|
|
189
|
+
}
|
|
190
|
+
const rows = yield stmt.all(d);
|
|
191
|
+
const total = (yield this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`).get([...filterValues]))['COUNT(*)'];
|
|
192
|
+
return {
|
|
193
|
+
data: rows.map((row) => {
|
|
194
|
+
const newRow = {};
|
|
195
|
+
for (const [key, value] of Object.entries(row)) {
|
|
196
|
+
newRow[key] = value;
|
|
197
|
+
}
|
|
198
|
+
return newRow;
|
|
199
|
+
}),
|
|
200
|
+
total,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
213
203
|
}
|
|
214
|
-
|
|
204
|
+
getMinMaxForColumnsWithOriginalTypes(_a) {
|
|
215
205
|
return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
|
|
216
206
|
const tableName = resource.table;
|
|
217
207
|
const result = {};
|
|
@@ -219,8 +209,7 @@ class SQLiteConnector {
|
|
|
219
209
|
const stmt = yield this.db.prepare(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
|
|
220
210
|
const { min, max } = stmt.get();
|
|
221
211
|
result[col.name] = {
|
|
222
|
-
min
|
|
223
|
-
max: this.getFieldValue(col, max),
|
|
212
|
+
min, max,
|
|
224
213
|
};
|
|
225
214
|
})));
|
|
226
215
|
return result;
|
|
@@ -231,28 +220,23 @@ class SQLiteConnector {
|
|
|
231
220
|
const tableName = resource.table;
|
|
232
221
|
const columns = Object.keys(record);
|
|
233
222
|
const placeholders = columns.map(() => '?').join(', ');
|
|
234
|
-
const values = columns.map((colName) =>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return this.setFieldValue(col, record[colName]);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
return record[colName];
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
this.db.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`).run(values);
|
|
223
|
+
const values = columns.map((colName) => record[colName]);
|
|
224
|
+
const q = this.db.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`);
|
|
225
|
+
yield q.run(values);
|
|
244
226
|
});
|
|
245
227
|
}
|
|
246
228
|
updateRecord(_a) {
|
|
247
|
-
return __awaiter(this, arguments, void 0, function* ({ resource, recordId,
|
|
229
|
+
return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
|
|
248
230
|
const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
|
|
249
231
|
const values = [...Object.values(newValues), recordId];
|
|
250
|
-
this.db.prepare(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`)
|
|
232
|
+
const q = this.db.prepare(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
233
|
+
yield q.run(values);
|
|
251
234
|
});
|
|
252
235
|
}
|
|
253
236
|
deleteRecord(_a) {
|
|
254
237
|
return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
|
|
255
|
-
this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`)
|
|
238
|
+
const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
|
|
239
|
+
yield q.run(recordId);
|
|
256
240
|
});
|
|
257
241
|
}
|
|
258
242
|
close() {
|
package/dist/index.js
CHANGED
|
@@ -968,7 +968,14 @@ class AdminForth {
|
|
|
968
968
|
if (!allowed) {
|
|
969
969
|
return { error };
|
|
970
970
|
}
|
|
971
|
-
|
|
971
|
+
const { record } = body;
|
|
972
|
+
// call setFieldValue for each column
|
|
973
|
+
for (const column of resource.columns) {
|
|
974
|
+
if (record[column.name] !== undefined) {
|
|
975
|
+
record[column.name] = this.connectors[resource.dataSource].setFieldValue(column, record[column.name]);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
yield this.createResourceRecord({ resource, record, adminUser });
|
|
972
979
|
const connector = this.connectors[resource.dataSource];
|
|
973
980
|
return {
|
|
974
981
|
newRecordId: body['record'][connector.getPrimaryKey(resource)]
|
package/dist/servers/express.js
CHANGED
|
@@ -180,11 +180,11 @@ class ExpressServer {
|
|
|
180
180
|
const headers = req.headers;
|
|
181
181
|
const cookies = yield parseExpressCookie(req);
|
|
182
182
|
const response = {
|
|
183
|
-
headers:
|
|
183
|
+
headers: [],
|
|
184
184
|
status: 200,
|
|
185
185
|
message: undefined,
|
|
186
186
|
setHeader(name, value) {
|
|
187
|
-
this.headers[name
|
|
187
|
+
this.headers.push([name, value]);
|
|
188
188
|
},
|
|
189
189
|
setStatus(code, message) {
|
|
190
190
|
this.status = code;
|
|
@@ -203,8 +203,8 @@ class ExpressServer {
|
|
|
203
203
|
res.status(500).send('Internal server error');
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
-
|
|
207
|
-
res.setHeader(name,
|
|
206
|
+
response.headers.forEach(([name, value]) => {
|
|
207
|
+
res.setHeader(name, value);
|
|
208
208
|
});
|
|
209
209
|
const resp = res.status(response.status);
|
|
210
210
|
if (response.message) {
|
package/index.ts
CHANGED
|
@@ -1102,7 +1102,15 @@ class AdminForth implements AdminForthClass {
|
|
|
1102
1102
|
return { error };
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
-
|
|
1105
|
+
const { record } = body;
|
|
1106
|
+
// call setFieldValue for each column
|
|
1107
|
+
for (const column of resource.columns) {
|
|
1108
|
+
if (record[column.name] !== undefined) {
|
|
1109
|
+
record[column.name] = this.connectors[resource.dataSource].setFieldValue(column, record[column.name]);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
await this.createResourceRecord({ resource, record, adminUser });
|
|
1106
1114
|
const connector = this.connectors[resource.dataSource];
|
|
1107
1115
|
|
|
1108
1116
|
return {
|
package/package.json
CHANGED
package/servers/express.ts
CHANGED
|
@@ -190,11 +190,11 @@ class ExpressServer implements ExpressHttpServer {
|
|
|
190
190
|
const cookies = await parseExpressCookie(req);
|
|
191
191
|
|
|
192
192
|
const response = {
|
|
193
|
-
headers:
|
|
193
|
+
headers: [],
|
|
194
194
|
status: 200,
|
|
195
195
|
message: undefined,
|
|
196
196
|
setHeader(name, value) {
|
|
197
|
-
this.headers[name
|
|
197
|
+
this.headers.push([name, value]);
|
|
198
198
|
},
|
|
199
199
|
setStatus(code, message) {
|
|
200
200
|
this.status = code;
|
|
@@ -214,9 +214,9 @@ class ExpressServer implements ExpressHttpServer {
|
|
|
214
214
|
res.status(500).send('Internal server error');
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
|
-
|
|
218
|
-
res.setHeader(name,
|
|
219
|
-
})
|
|
217
|
+
response.headers.forEach(([name, value]) => {
|
|
218
|
+
res.setHeader(name, value);
|
|
219
|
+
});
|
|
220
220
|
const resp = res.status(response.status);
|
|
221
221
|
if (response.message) {
|
|
222
222
|
resp.send(response.message);
|
|
@@ -82,9 +82,113 @@ export interface AdminForthDataSourceConnector {
|
|
|
82
82
|
/**
|
|
83
83
|
* Function which will be called to fetch record from database.
|
|
84
84
|
*/
|
|
85
|
+
getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, recordId: string): Promise<any>;
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Function should go over all columns of table defined in resource.table and try to guess
|
|
90
|
+
* data and constraints for each columns.
|
|
91
|
+
* Type should be saved to:
|
|
92
|
+
* - {@link AdminForthResourceColumn.type}
|
|
93
|
+
* Constraints:
|
|
94
|
+
* - {@link AdminForthResourceColumn.required}
|
|
95
|
+
* - {@link AdminForthResourceColumn.primaryKey}
|
|
96
|
+
* For string fields:
|
|
97
|
+
* - {@link AdminForthResourceColumn.maxLength}
|
|
98
|
+
* For numbers:
|
|
99
|
+
* - {@link AdminForthResourceColumn.min}
|
|
100
|
+
* - {@link AdminForthResourceColumn.max}
|
|
101
|
+
* - {@link AdminForthResourceColumn.minValue}, {@link AdminForthResourceColumn.maxValue}, {@link AdminForthResourceColumn.enum}, {@link AdminForthResourceColumn.foreignResource}, {@link AdminForthResourceColumn.sortable}, {@link AdminForthResourceColumn.backendOnly}, {@link AdminForthResourceColumn.masked}, {@link AdminForthResourceColumn.virtual}, {@link AdminForthResourceColumn.components}, {@link AdminForthResourceColumn.allowMinMaxQuery}, {@link AdminForthResourceColumn.editingNote}, {@link AdminForthResourceColumn.showIn}, {@link AdminForthResourceColumn.isUnique}, {@link AdminForthResourceColumn.validation})
|
|
102
|
+
* Also you can additionally save original column type to {@link AdminForthResourceColumn._underlineType}. This might be later used
|
|
103
|
+
* in {@link AdminForthDataSourceConnector.getFieldValue} and {@link AdminForthDataSourceConnector.setFieldValue} methods.
|
|
104
|
+
*
|
|
105
|
+
*
|
|
106
|
+
* @param resource
|
|
107
|
+
*/
|
|
108
|
+
discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}>;
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Used to transform record after fetching from database.
|
|
113
|
+
* According to AdminForth convention, if {@link AdminForthResourceColumn.type} is set to {@link AdminForthDataTypes.DATETIME} then it should be transformed to ISO string.
|
|
114
|
+
* @param field
|
|
115
|
+
* @param value
|
|
116
|
+
*/
|
|
117
|
+
getFieldValue(field: AdminForthResourceColumn, value: any): any;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Used to transform record before saving to database. Should perform operation inverse to {@link AdminForthDataSourceConnector.getFieldValue}
|
|
121
|
+
* @param field
|
|
122
|
+
* @param value
|
|
123
|
+
*/
|
|
124
|
+
setFieldValue(field: AdminForthResourceColumn, value: any): any;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Used to fetch data from database.
|
|
128
|
+
* This method is reused both to list records and show one record (by passing limit 1 and offset 0) .
|
|
129
|
+
*
|
|
130
|
+
* Fields are returned from db "as is" then {@link AdminForthBaseConnector.getData} will transform each field using {@link AdminForthDataSourceConnector.getFieldValue}
|
|
131
|
+
*/
|
|
132
|
+
getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
|
|
133
|
+
resource: AdminForthResource,
|
|
134
|
+
limit: number,
|
|
135
|
+
offset: number,
|
|
136
|
+
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
137
|
+
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
138
|
+
}): Promise<{data: Array<any>, total: number}>;
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Optional method which used to get min and max values for columns in resource.
|
|
143
|
+
* Called only for columns which have {@link AdminForthResourceColumn.allowMinMaxQuery} set to true.
|
|
144
|
+
*
|
|
145
|
+
* Internally should call {@link AdminForthDataSourceConnector.getFieldValue} for both min and max values.
|
|
146
|
+
*/
|
|
147
|
+
getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }>;
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Used to create record in database.
|
|
152
|
+
*/
|
|
153
|
+
createRecord({ resource, record }: { resource: AdminForthResource, record: any }): Promise<void>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Used to update record in database.
|
|
157
|
+
* recordId is value of field which is marked as {@link AdminForthResourceColumn.primaryKey}
|
|
158
|
+
* newValues is array of fields which should be updated (might be not all fields in record, but only changed fields).
|
|
159
|
+
*/
|
|
160
|
+
updateRecord({ resource, recordId, newValues }:
|
|
161
|
+
{ resource: AdminForthResource, recordId: string, newValues: any }
|
|
162
|
+
): Promise<void>;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Used to delete record in database.
|
|
166
|
+
*/
|
|
167
|
+
deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<void>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Interface that exposes methods to interact with AdminForth in standard way
|
|
173
|
+
*/
|
|
174
|
+
export interface AdminForthDataSourceConnectorBase extends AdminForthDataSourceConnector {
|
|
175
|
+
|
|
176
|
+
getPrimaryKey(resource: AdminForthResource): string;
|
|
177
|
+
|
|
178
|
+
getData({ resource, limit, offset, sort, filters }: {
|
|
179
|
+
resource: AdminForthResource,
|
|
180
|
+
limit: number,
|
|
181
|
+
offset: number,
|
|
182
|
+
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
183
|
+
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
184
|
+
}): Promise<{ data: Array<any>, total: number }>;
|
|
185
|
+
|
|
85
186
|
getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any>;
|
|
187
|
+
|
|
188
|
+
getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }>;
|
|
86
189
|
}
|
|
87
190
|
|
|
191
|
+
|
|
88
192
|
export interface AdminForthDataSourceConnectorConstructor {
|
|
89
193
|
new ({ url }: { url: string }): AdminForthDataSourceConnector;
|
|
90
194
|
}
|
|
@@ -95,7 +199,7 @@ export interface AdminForthClass {
|
|
|
95
199
|
express: GenericHttpServer;
|
|
96
200
|
|
|
97
201
|
connectors: {
|
|
98
|
-
[key: string]:
|
|
202
|
+
[key: string]: AdminForthDataSourceConnectorBase;
|
|
99
203
|
};
|
|
100
204
|
|
|
101
205
|
createResourceRecord(params: { resource: AdminForthResource, record: any, adminUser: AdminUser }): Promise<any>;
|
|
@@ -425,21 +529,57 @@ export type AdminForthResourceColumn = {
|
|
|
425
529
|
* Custom components which will be used to render this field in the admin panel.
|
|
426
530
|
*/
|
|
427
531
|
components?: AdminForthFieldComponents
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Maximum length of string that can be entered in this field.
|
|
535
|
+
*/
|
|
428
536
|
maxLength?: number,
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Minimum length of string that can be entered in this field.
|
|
540
|
+
*/
|
|
429
541
|
minLength?: number,
|
|
542
|
+
|
|
430
543
|
min?: number,
|
|
431
544
|
max?: number,
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Minimum value that can be entered in this field.
|
|
548
|
+
*/
|
|
432
549
|
minValue?: number,
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Maximum value that can be entered in this field.
|
|
553
|
+
*/
|
|
433
554
|
maxValue?: number,
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Enum of possible values for this field.
|
|
558
|
+
*/
|
|
434
559
|
enum?: Array<AdminForthColumnEnumItem>,
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Foreign resource which has pk column with values same that written in this column.
|
|
563
|
+
*/
|
|
435
564
|
foreignResource?:AdminForthForeignResource,
|
|
565
|
+
|
|
436
566
|
sortable?: boolean,
|
|
437
|
-
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* if true field will !not be passed to UI under no circumstances, but will be presented in hooks
|
|
570
|
+
*/
|
|
571
|
+
backendOnly?: boolean,
|
|
438
572
|
|
|
439
573
|
/**
|
|
440
574
|
* Masked fields will be displayed as `*****` on Edit and Create pages.
|
|
441
575
|
*/
|
|
442
576
|
masked?: boolean,
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Internal type which indicates original type of column in database.
|
|
581
|
+
*/
|
|
582
|
+
_underlineType?: string,
|
|
443
583
|
}
|
|
444
584
|
|
|
445
585
|
|