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.
Files changed (36) hide show
  1. package/auth.ts +6 -5
  2. package/dataConnectors/baseConnector.ts +91 -21
  3. package/dataConnectors/clickhouse.ts +58 -37
  4. package/dataConnectors/mongo.ts +44 -14
  5. package/dataConnectors/postgres.ts +53 -36
  6. package/dataConnectors/sqlite.ts +44 -35
  7. package/dist/auth.js +6 -6
  8. package/dist/dataConnectors/baseConnector.js +75 -17
  9. package/dist/dataConnectors/clickhouse.js +59 -48
  10. package/dist/dataConnectors/mongo.js +28 -12
  11. package/dist/dataConnectors/postgres.js +62 -52
  12. package/dist/dataConnectors/sqlite.js +62 -45
  13. package/dist/index.js +52 -33
  14. package/dist/modules/configValidator.js +25 -16
  15. package/dist/modules/operationalResource.js +88 -0
  16. package/dist/modules/restApi.js +106 -96
  17. package/dist/modules/utils.js +16 -0
  18. package/dist/types/AdminForthConfig.js +37 -0
  19. package/index.ts +76 -43
  20. package/modules/configValidator.ts +26 -21
  21. package/modules/operationalResource.ts +91 -0
  22. package/modules/restApi.ts +91 -80
  23. package/modules/utils.ts +20 -0
  24. package/package.json +3 -2
  25. package/spa/src/App.vue +12 -9
  26. package/spa/src/components/Filters.vue +1 -1
  27. package/spa/src/components/ResourceForm.vue +7 -4
  28. package/spa/src/components/ResourceListTable.vue +124 -112
  29. package/spa/src/components/SkeleteLoader.vue +4 -4
  30. package/spa/src/components/ValueRenderer.vue +1 -1
  31. package/spa/src/router/index.ts +0 -8
  32. package/spa/src/spa_types/core.ts +1 -0
  33. package/spa/src/stores/filters.ts +3 -2
  34. package/spa/src/views/ListView.vue +16 -25
  35. package/spa/src/views/LoginView.vue +26 -9
  36. package/types/AdminForthConfig.ts +140 -38
package/auth.ts CHANGED
@@ -88,12 +88,13 @@ class AdminForthAuth {
88
88
  console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
89
89
  return null;
90
90
  }
91
- if (pk === null) {
92
- decoded.isRoot = true;
93
- } else {
94
- const dbUser = await this.adminforth.getUserByPk(pk);
95
- decoded.dbUser = dbUser;
91
+ const dbUser = await this.adminforth.getUserByPk(pk);
92
+ if (!dbUser) {
93
+ console.error(`User with pk ${pk} not found in database`);
94
+ // will logout user which was deleted
95
+ return null;
96
96
  }
97
+ decoded.dbUser = dbUser;
97
98
  return decoded;
98
99
  }
99
100
 
@@ -1,4 +1,6 @@
1
- import { AdminForthResource, IAdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn } from "../types/AdminForthConfig.js";
1
+ import { get } from "http";
2
+ import { AdminForthResource, IAdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn, IAdminForthSort, IAdminForthFilter } from "../types/AdminForthConfig.js";
3
+ import { suggestIfTypo } from "../modules/utils.js";
2
4
 
3
5
 
4
6
  export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
@@ -17,19 +19,21 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
17
19
  offset: 0,
18
20
  sort: [],
19
21
  filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
20
- getTotals: false
21
22
  });
22
- return data.data.length > 0 ? data.data[0] : null;
23
+ return data.length > 0 ? data[0] : null;
23
24
  }
24
25
 
25
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }: {
26
+ getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
26
27
  resource: AdminForthResource,
27
28
  limit: number,
28
29
  offset: number,
29
- sort: { field: string, direction: AdminForthSortDirections }[],
30
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
31
- getTotals?: boolean
32
- }): Promise<{ data: any[], total: number }> {
30
+ sort: IAdminForthSort[],
31
+ filters: IAdminForthFilter[],
32
+ }): Promise<any[]> {
33
+ throw new Error('Method not implemented.');
34
+ }
35
+
36
+ getCount({ resource, filters }: { resource: AdminForthResource; filters: { field: string; operator: AdminForthFilterOperators; value: any; }[]; }): Promise<number> {
33
37
  throw new Error('Method not implemented.');
34
38
  }
35
39
 
@@ -53,51 +57,117 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
53
57
  throw new Error('Method not implemented.');
54
58
  }
55
59
 
56
- createRecord({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
60
+ async checkUnique(resource: AdminForthResource, column: AdminForthResourceColumn, value: any) {
61
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 checkUnique|||', column, value);
62
+ const existingRecord = await this.getData({
63
+ resource,
64
+ filters: [{ field: column.name, operator: AdminForthFilterOperators.EQ, value }],
65
+ limit: 1,
66
+ sort: [],
67
+ offset: 0,
68
+ getTotals: false
69
+ });
70
+ process.env.HEAVY_DEBUG && console.log('☝️🪲🪲🪲🪲 existingRecord|||', existingRecord);
71
+
72
+ return existingRecord.data.length > 0;
73
+ }
74
+
75
+ async createRecord({ resource, record, adminUser }: {
76
+ resource: AdminForthResource; record: any; adminUser: any;
77
+ }): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
57
78
  // transform value using setFieldValue and call createRecordOriginalValues
58
- const newRecord = {...record};
79
+ const filledRecord = {...record};
80
+ const recordWithOriginalValues = {...record};
81
+
59
82
  for (const col of resource.dataSourceColumns) {
60
- newRecord[col.name] = this.setFieldValue(col, record[col.name]);
83
+ if (col.fillOnCreate) {
84
+ if (filledRecord[col.name] === undefined) {
85
+ filledRecord[col.name] = col.fillOnCreate({
86
+ initialRecord: record,
87
+ adminUser
88
+ });
89
+ }
90
+ }
91
+ recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
92
+ }
93
+
94
+
95
+ let error: string | null = null;
96
+ await Promise.all(
97
+ resource.dataSourceColumns.map(async (col) => {
98
+
99
+ if (col.isUnique && !col.virtual && !error) {
100
+
101
+ const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
102
+ if (exists) {
103
+ error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
104
+ }
105
+ }
106
+ })
107
+ );
108
+ process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 error', error);
109
+ if (error) {
110
+ return { error, ok: false };
111
+ }
112
+
113
+ process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', recordWithOriginalValues);
114
+ await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
115
+
116
+ return {
117
+ ok: true,
118
+ createdRecord: recordWithOriginalValues,
61
119
  }
62
- process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', newRecord);
63
- return this.createRecordOriginalValues({ resource, record: newRecord });
64
120
  }
65
121
 
66
122
  updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
67
123
  throw new Error('Method not implemented.');
68
124
  }
69
125
 
70
- deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<void> {
126
+ deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<boolean> {
71
127
  throw new Error('Method not implemented.');
72
128
  }
73
129
 
74
130
 
75
- async getData({ resource, limit, offset, sort, filters }: {
131
+ async getData({ resource, limit, offset, sort, filters, getTotals }: {
76
132
  resource: AdminForthResource,
77
133
  limit: number,
78
134
  offset: number,
79
135
  sort: { field: string, direction: AdminForthSortDirections }[],
80
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
136
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
137
+ getTotals: boolean,
81
138
  }): Promise<{ data: any[], total: number }> {
82
139
  if (filters) {
83
140
  filters.map((f) => {
141
+ const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
142
+ if (!fieldObj) {
143
+ const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
144
+ throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
145
+ }
84
146
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
85
- f.value = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
147
+ f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
86
148
  } else {
87
- f.value = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
149
+ f.value = this.setFieldValue(fieldObj, f.value);
88
150
  }
89
151
  });
90
152
  }
91
153
 
92
- const d = await this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals: true });
154
+ const promises: Promise<any>[] = [this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters })];
155
+ if (getTotals) {
156
+ promises.push(this.getCount({ resource, filters }));
157
+ } else {
158
+ promises.push(Promise.resolve(undefined));
159
+ }
160
+
161
+ const [data, total] = await Promise.all(promises);
162
+
93
163
  // call getFieldValue for each field
94
- d.data.map((record) => {
164
+ data.map((record) => {
95
165
  for (const col of resource.dataSourceColumns) {
96
166
  record[col.name] = this.getFieldValue(col, record[col.name]);
97
167
  }
98
168
  });
99
169
 
100
- return d;
170
+ return { data, total };
101
171
  }
102
172
 
103
173
  async getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
@@ -172,19 +172,11 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
172
172
  [AdminForthSortDirections.desc]: 'DESC',
173
173
  };
174
174
 
175
-
176
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }: {
177
- resource: AdminForthResource,
178
- limit: number,
179
- offset: number,
180
- sort: { field: string, direction: AdminForthSortDirections }[],
181
- filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
182
- getTotals?: boolean,
183
- }): Promise<{ data: any[], total: number }> {
184
- const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
185
- const tableName = resource.table;
186
-
187
- const where = filters.length ? `WHERE ${filters.map((f, i) => {
175
+ whereClause(
176
+ resource: AdminForthResource,
177
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
178
+ ): string {
179
+ return filters.length ? `WHERE ${filters.map((f, i) => {
188
180
  const column = resource.dataSourceColumns.find((col) => col.name == f.field);
189
181
  let placeholder = `{f${i}:${column._underlineType}}`;
190
182
  let field = f.field;
@@ -197,9 +189,12 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
197
189
 
198
190
  return `${field} ${operator} ${placeholder}`
199
191
  }).join(' AND ')}` : '';
192
+ }
200
193
 
194
+ whereParams(
195
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
196
+ ): any {
201
197
  const params = {};
202
-
203
198
  filters.length ? filters.forEach((f, i) => {
204
199
  // for arrays do set in map
205
200
  const v = f.value;
@@ -215,6 +210,24 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
215
210
  }
216
211
  }) : [];
217
212
 
213
+ return params;
214
+ }
215
+
216
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
217
+ resource: AdminForthResource,
218
+ limit: number,
219
+ offset: number,
220
+ sort: { field: string, direction: AdminForthSortDirections }[],
221
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
222
+ }): Promise<any[]> {
223
+ console.log('getDataWithOriginalTypes', resource, limit, offset, sort, filters);
224
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
225
+ const tableName = resource.table;
226
+
227
+ const where = this.whereClause(resource, filters);
228
+
229
+ const params = this.whereParams(filters);
230
+
218
231
  const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
219
232
 
220
233
 
@@ -233,27 +246,33 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
233
246
 
234
247
  const rows = await stmt.json();
235
248
 
236
- let total = 0;
237
- if (getTotals) {
238
- const countQ = await this.client.query({
239
- query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
240
- format: 'JSONEachRow',
241
- query_params: d,
242
- });
243
- const countResp = await countQ.json()
244
- total = countResp[0]['COUNT()'];
245
- }
249
+ return rows.map((row) => {
250
+ const newRow = {};
251
+ for (const [key, value] of Object.entries(row)) {
252
+ newRow[key] = value;
253
+ }
254
+ return newRow;
255
+ });
256
+ }
246
257
 
247
- return {
248
- data: rows.map((row) => {
249
- const newRow = {};
250
- for (const [key, value] of Object.entries(row)) {
251
- newRow[key] = value;
252
- }
253
- return newRow;
254
- }),
255
- total,
256
- };
258
+ async getCount({
259
+ resource,
260
+ filters,
261
+ }: {
262
+ resource: AdminForthResource;
263
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[];
264
+ }): Promise<number> {
265
+ const tableName = resource.table;
266
+ const where = this.whereClause(resource, filters);
267
+ const d = this.whereParams(filters);
268
+
269
+ const countQ = await this.client.query({
270
+ query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
271
+ format: 'JSONEachRow',
272
+ query_params: d,
273
+ });
274
+ const countResp = await countQ.json()
275
+ return countResp[0]['COUNT()'];
257
276
  }
258
277
 
259
278
  async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
@@ -298,16 +317,18 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
298
317
  );
299
318
  }
300
319
 
301
- async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }) {
320
+ async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<boolean> {
302
321
  const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
303
- await this.client.command(
322
+ const res = await this.client.command(
304
323
  {
305
324
  query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${
306
325
  pkColumn.name
307
326
  } = {recordId:${pkColumn._underlineType}}`,
308
327
  query_params: { recordId },
309
- }
328
+ }
310
329
  );
330
+ // todo test what is in res
331
+ return res;
311
332
  }
312
333
 
313
334
  close() {
@@ -1,6 +1,7 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { MongoClient } from 'mongodb';
3
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
3
+ import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections,
4
+ IAdminForthDataSourceConnector, AdminForthResource } from '../types/AdminForthConfig.js';
4
5
  import AdminForthBaseConnector from './baseConnector.js';
5
6
 
6
7
  class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
@@ -104,28 +105,56 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
104
105
  return value ? 1 : 0;
105
106
  }
106
107
  return value;
107
- }
108
-
109
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }) {
110
- // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
111
- const tableName = resource.table;
108
+ }
112
109
 
113
- const collection = this.db.db().collection(tableName);
110
+ async genQuery({ filters }) {
114
111
  const query = {};
115
112
  for (const filter of filters) {
116
113
  query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
117
114
  }
118
- let total = 0;
119
- if (getTotals) {
120
- total = await collection.countDocuments(query);
115
+ return query;
116
+ }
117
+
118
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }:
119
+ {
120
+ resource: AdminForthResource,
121
+ limit: number,
122
+ offset: number,
123
+ sort: { field: string, direction: AdminForthSortDirections }[],
124
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
121
125
  }
126
+ ): Promise<any[]> {
127
+
128
+ // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
129
+ const tableName = resource.table;
130
+
131
+ const collection = this.db.db().collection(tableName);
132
+ const query = await this.genQuery({ filters });
133
+
134
+ const sortArray: any[] = sort.map((s) => {
135
+ return [s.field, this.SortDirectionsMap[s.direction]];
136
+ });
137
+
122
138
  const result = await collection.find(query)
123
- .sort(sort)
139
+ .sort(sortArray)
124
140
  .skip(offset)
125
141
  .limit(limit)
126
142
  .toArray();
127
143
 
128
- return { data: result, total }
144
+ return result
145
+ }
146
+
147
+ async getCount({ resource, filters }: {
148
+ resource: AdminForthResource,
149
+ filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
150
+ }): Promise<number> {
151
+
152
+ const collection = this.db.db().collection(resource.table);
153
+ const query = {};
154
+ for (const filter of filters) {
155
+ query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
156
+ }
157
+ return await collection.countDocuments(query);
129
158
  }
130
159
 
131
160
  async getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
@@ -158,10 +187,11 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
158
187
  await collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
159
188
  }
160
189
 
161
- async deleteRecord({ resource, recordId }) {
190
+ async deleteRecord({ resource, recordId }): Promise<boolean> {
162
191
  const primaryKey = this.getPrimaryKey(resource);
163
192
  const collection = this.db.db().collection(resource.table);
164
- await collection.deleteOne({ [primaryKey]: recordId });
193
+ const res = await collection.deleteOne({ [primaryKey]: recordId });
194
+ return res.deletedCount > 0;
165
195
  }
166
196
 
167
197
  async close() {
@@ -1,6 +1,6 @@
1
1
  import dayjs from 'dayjs';
2
2
  import pkg from 'pg';
3
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
3
+ import { AdminForthDataTypes, AdminForthResource, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector } from '../types/AdminForthConfig.js';
4
4
  import AdminForthBaseConnector from './baseConnector.js';
5
5
  const { Client } = pkg;
6
6
 
@@ -168,24 +168,26 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
168
168
 
169
169
 
170
170
  setFieldValue(field, value) {
171
- if (field.type == AdminForthDataTypes.DATETIME) {
172
- if (!value) {
173
- return null;
174
- }
175
- if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
176
- return dayjs(value);
177
- } else if (field._underlineType == 'varchar') {
178
- return dayjs(value).toISOString();
179
- }
180
- } else if (field.type == AdminForthDataTypes.BOOLEAN) {
181
- return value ? 1 : 0;
171
+ if (field.type == AdminForthDataTypes.DATETIME) {
172
+ if (!value) {
173
+ return null;
182
174
  }
183
- return value;
175
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
176
+ return dayjs(value);
177
+ } else if (field._underlineType == 'varchar') {
178
+ return dayjs(value).toISOString();
179
+ }
180
+ } else if (field.type == AdminForthDataTypes.BOOLEAN) {
181
+ return value ? 1 : 0;
184
182
  }
185
-
186
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, getTotals }) {
187
- const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
188
- const tableName = resource.table;
183
+ return value;
184
+ }
185
+
186
+ whereClauseAndValues(resource: AdminForthResource, filters: { field: string, operator: AdminForthFilterOperators, value: any }[]) : {
187
+ sql: string,
188
+ paramsCount: number,
189
+ values: any[],
190
+ } {
189
191
 
190
192
  let totalCounter = 1;
191
193
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
@@ -201,9 +203,9 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
201
203
  }
202
204
 
203
205
  if (fieldData._underlineType == 'uuid') {
204
- field = `cast("${field}" as text)`
206
+ field = `cast("${field}" as text)`
205
207
  } else {
206
- field = `"${field}"`
208
+ field = `"${field}"`
207
209
  }
208
210
  return `${field} ${operator} ${placeholder}`;
209
211
  }).join(' AND ')}` : '';
@@ -221,8 +223,22 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
221
223
  filterValues.push(v);
222
224
  }
223
225
  }) : [];
226
+ return {
227
+ sql: where,
228
+ paramsCount: totalCounter,
229
+ values: filterValues,
230
+ };
231
+
232
+ }
224
233
 
225
- const limitOffset = `LIMIT $${totalCounter} OFFSET $${totalCounter + 1}`;
234
+
235
+ async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }): Promise<any[]> {
236
+ const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
237
+ const tableName = resource.table;
238
+
239
+ const { sql: where, paramsCount, values: filterValues } = this.whereClauseAndValues(resource, filters);
240
+
241
+ const limitOffset = `LIMIT $${paramsCount} OFFSET $${paramsCount + 1}`;
226
242
  const d = [...filterValues, limit, offset];
227
243
  const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `"${s.field}" ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
228
244
  const selectQuery = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} ${limitOffset}`;
@@ -231,20 +247,20 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
231
247
  }
232
248
  const stmt = await this.db.query(selectQuery, d);
233
249
  const rows = stmt.rows;
234
- let total = 0;
235
- if (getTotals) {
236
- total = (await this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
237
- }
238
- return {
239
- data: rows.map((row) => {
240
- const newRow = {};
241
- for (const [key, value] of Object.entries(row)) {
242
- newRow[key] = value;
243
- }
244
- return newRow;
245
- }),
246
- total,
247
- };
250
+ return rows.map((row) => {
251
+ const newRow = {};
252
+ for (const [key, value] of Object.entries(row)) {
253
+ newRow[key] = value;
254
+ }
255
+ return newRow;
256
+ });
257
+ }
258
+
259
+ async getCount({ resource, filters }: { resource: AdminForthResource; filters: { field: string, operator: AdminForthFilterOperators, value: any }[]; }): Promise<number> {
260
+ const tableName = resource.table;
261
+ const { sql: where, values: filterValues } = this.whereClauseAndValues(resource, filters);
262
+ const stmt = await this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues);
263
+ return stmt.rows[0].count;
248
264
  }
249
265
 
250
266
  async getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
@@ -277,8 +293,9 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
277
293
  await this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE "${this.getPrimaryKey(resource)}" = $${values.length}`, values);
278
294
  }
279
295
 
280
- async deleteRecord({ resource, recordId }) {
281
- await this.db.query(`DELETE FROM ${resource.table} WHERE "${this.getPrimaryKey(resource)}" = $1`, [recordId]);
296
+ async deleteRecord({ resource, recordId }): Promise<boolean> {
297
+ const res = await this.db.query(`DELETE FROM ${resource.table} WHERE "${this.getPrimaryKey(resource)}" = $1`, [recordId]);
298
+ return res.rowCount > 0;
282
299
  }
283
300
 
284
301
  async close() {