adminforth 1.0.17 → 1.0.25

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 (35) hide show
  1. package/dataConnectors/{mongo.js → mongo.ts} +16 -11
  2. package/dataConnectors/{postgres.js → postgres.ts} +6 -2
  3. package/dataConnectors/{sqlite.js → sqlite.ts} +6 -2
  4. package/dist/auth.js +68 -0
  5. package/dist/dataConnectors/mongo.js +204 -0
  6. package/dist/dataConnectors/postgres.js +298 -0
  7. package/dist/dataConnectors/sqlite.js +261 -0
  8. package/dist/index.js +693 -0
  9. package/dist/modules/codeInjector.js +346 -0
  10. package/dist/modules/utils.js +12 -0
  11. package/dist/servers/express.js +210 -0
  12. package/dist/spa/src/main.js +16 -0
  13. package/dist/spa/src/router/index.js +79 -0
  14. package/dist/spa/src/stores/core.js +154 -0
  15. package/dist/spa/src/stores/modal.js +35 -0
  16. package/dist/spa/src/utils.js +59 -0
  17. package/dist/spa/vite.config.js +44 -0
  18. package/dist/spa_tmp/src/custom/custom/vueUses.js +10 -0
  19. package/dist/spa_tmp/src/main.js +29 -0
  20. package/dist/spa_tmp/src/router/index.js +83 -0
  21. package/dist/spa_tmp/src/stores/core.js +150 -0
  22. package/dist/spa_tmp/src/stores/modal.js +35 -0
  23. package/dist/spa_tmp/src/utils.js +59 -0
  24. package/dist/spa_tmp/vite.config.js +43 -0
  25. package/dist/types.js +30 -0
  26. package/{index.js → index.ts} +115 -6
  27. package/modules/{codeInjector.js → codeInjector.ts} +29 -13
  28. package/package.json +9 -3
  29. package/servers/{express.js → express.ts} +8 -2
  30. package/spa/package.json +2 -2
  31. package/spa/src/views/ListView.vue +1 -1
  32. package/tsconfig.json +112 -0
  33. package/{types.js → types.ts} +2 -0
  34. /package/{auth.js → auth.ts} +0 -0
  35. /package/modules/{utils.js → utils.ts} +0 -0
@@ -4,12 +4,10 @@ import { AdminForthFilterOperators, AdminForthSortDirections, AdminForthTypes }
4
4
 
5
5
 
6
6
  class MongoConnector {
7
- constructor({ url, fieldtypesByTable }) {
8
- this.db = new MongoClient(url, { useNewUrlParser: true, useUnifiedTopology: true });
9
- if (fieldtypesByTable == null) {
10
- throw new Error('fieldtypesByTable is required for MongoConnector');
11
- };
12
-
7
+ db: MongoClient
8
+
9
+ constructor({ url }) {
10
+ this.db = new MongoClient(url);
13
11
  (async () => {
14
12
  try {
15
13
  await this.db.connect();
@@ -21,8 +19,6 @@ class MongoConnector {
21
19
  console.error('ERROR: Failed to connect to Mongo', e);
22
20
  }
23
21
  })();
24
-
25
- this.fieldtypesByTable = fieldtypesByTable;
26
22
  }
27
23
 
28
24
  OperatorsMap = {
@@ -43,8 +39,17 @@ class MongoConnector {
43
39
  [AdminForthSortDirections.DESC]: -1,
44
40
  };
45
41
 
46
- async discoverFields(tableName) {
47
- return this.fieldtypesByTable[tableName];
42
+ async discoverFields(resource) {
43
+ return resource.columns.reduce((acc, col) => {
44
+ acc[col.name] = {
45
+ name: col.name,
46
+ type: col.type,
47
+ primaryKey: col.primaryKey,
48
+ virtual: col.virtual,
49
+ _underlineType: col._underlineType,
50
+ };
51
+ return acc;
52
+ }, {});
48
53
  }
49
54
 
50
55
  getPrimaryKey(resource) {
@@ -183,7 +188,7 @@ class MongoConnector {
183
188
  }
184
189
 
185
190
  async close() {
186
- await this.db.end();
191
+ await this.db.close()
187
192
  }
188
193
  }
189
194
 
@@ -5,6 +5,9 @@ const { Client } = pkg;
5
5
 
6
6
 
7
7
  class PostgresConnector {
8
+
9
+ db: any;
10
+
8
11
  constructor({ url }) {
9
12
  this.db = new Client({
10
13
  connectionString: url
@@ -37,7 +40,8 @@ class PostgresConnector {
37
40
  [AdminForthSortDirections.DESC]: 'DESC',
38
41
  };
39
42
 
40
- async discoverFields(tableName) {
43
+ async discoverFields(resource) {
44
+ const tableName = resource.table;
41
45
  const stmt = await this.db.query(`
42
46
  SELECT
43
47
  a.attname AS name,
@@ -65,7 +69,7 @@ class PostgresConnector {
65
69
  const fieldTypes = {};
66
70
 
67
71
  rows.forEach((row) => {
68
- const field = {};
72
+ const field: any = {};
69
73
  const baseType = row.type.toLowerCase();
70
74
  if (baseType == 'int') {
71
75
  field.type = AdminForthTypes.INTEGER;
@@ -3,18 +3,22 @@ import { AdminForthTypes, AdminForthFilterOperators, AdminForthSortDirections }
3
3
  import dayjs from 'dayjs';
4
4
 
5
5
  class SQLiteConnector {
6
+
7
+ db: any;
8
+
6
9
  constructor({ url }) {
7
10
  // create connection here
8
11
 
9
12
  this.db = betterSqlite3(url.replace('sqlite://', ''));
10
13
  }
11
14
 
12
- async discoverFields(tableName) {
15
+ async discoverFields(resource) {
16
+ const tableName = resource.table;
13
17
  const stmt = this.db.prepare(`PRAGMA table_info(${tableName})`);
14
18
  const rows = await stmt.all();
15
19
  const fieldTypes = {};
16
20
  rows.forEach((row) => {
17
- const field = {};
21
+ const field: any = {};
18
22
  const baseType = row.type.toLowerCase();
19
23
  if (baseType == 'int') {
20
24
  field.type = AdminForthTypes.INTEGER;
package/dist/auth.js ADDED
@@ -0,0 +1,68 @@
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 jwt from 'jsonwebtoken';
11
+ import crypto from 'crypto';
12
+ // Function to generate a password hash using PBKDF2
13
+ function calcPasswordHash(password, salt, iterations = 100000, keyLength = 64, digest = 'sha512') {
14
+ return new Promise((resolve, reject) => {
15
+ crypto.pbkdf2(password, salt, iterations, keyLength, digest, (err, derivedKey) => {
16
+ if (err)
17
+ reject(err);
18
+ resolve(derivedKey.toString('hex'));
19
+ });
20
+ });
21
+ }
22
+ // Function to generate a random salt
23
+ function generateSalt(length = 16) {
24
+ return crypto.randomBytes(length).toString('hex');
25
+ }
26
+ class AdminForthAuth {
27
+ issueJWT(payload) {
28
+ // read ADMINFORH_SECRET from environment if not drop error
29
+ const secret = process.env.ADMINFORTH_SECRET;
30
+ if (!secret) {
31
+ throw new Error('ADMINFORTH_SECRET environment not set');
32
+ }
33
+ // issue JWT token
34
+ const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
35
+ return jwt.sign(payload, secret, { expiresIn });
36
+ }
37
+ verify(jwtToken) {
38
+ // read ADMINFORH_SECRET from environment if not drop error
39
+ const secret = process.env.ADMINFORTH_SECRET;
40
+ if (!secret) {
41
+ throw new Error('ADMINFORTH_SECRET environment not set');
42
+ }
43
+ try {
44
+ // verify JWT token
45
+ const decoded = jwt.verify(jwtToken, secret);
46
+ return decoded;
47
+ }
48
+ catch (err) {
49
+ console.error('Failed to verify JWT token', err);
50
+ return null;
51
+ }
52
+ }
53
+ static generatePasswordHash(password) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ const salt = generateSalt();
56
+ const hashedPassword = yield calcPasswordHash(password, salt);
57
+ return `${salt}:${hashedPassword}`;
58
+ });
59
+ }
60
+ static verifyPassword(password, hashedPassword) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const [salt, hash] = hashedPassword.split(':');
63
+ const newHash = yield calcPasswordHash(password, salt);
64
+ return newHash === hash;
65
+ });
66
+ }
67
+ }
68
+ export default AdminForthAuth;
@@ -0,0 +1,204 @@
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 dayjs from 'dayjs';
11
+ import { MongoClient } from 'mongodb';
12
+ import { AdminForthFilterOperators, AdminForthSortDirections, AdminForthTypes } from '../types.js';
13
+ class MongoConnector {
14
+ constructor({ url }) {
15
+ this.OperatorsMap = {
16
+ [AdminForthFilterOperators.EQ]: (value) => value,
17
+ [AdminForthFilterOperators.NE]: (value) => ({ $ne: value }),
18
+ [AdminForthFilterOperators.GT]: (value) => ({ $gt: value }),
19
+ [AdminForthFilterOperators.LT]: (value) => ({ $lt: value }),
20
+ [AdminForthFilterOperators.GTE]: (value) => ({ $gte: value }),
21
+ [AdminForthFilterOperators.LTE]: (value) => ({ $lte: value }),
22
+ [AdminForthFilterOperators.LIKE]: (value) => ({ $regex: value }),
23
+ [AdminForthFilterOperators.ILIKE]: (value) => ({ $regex: value, $options: 'i' }),
24
+ [AdminForthFilterOperators.IN]: (value) => ({ $in: value }),
25
+ [AdminForthFilterOperators.NIN]: (value) => ({ $nin: value }),
26
+ };
27
+ this.SortDirectionsMap = {
28
+ [AdminForthSortDirections.ASC]: 1,
29
+ [AdminForthSortDirections.DESC]: -1,
30
+ };
31
+ this.db = new MongoClient(url);
32
+ (() => __awaiter(this, void 0, void 0, function* () {
33
+ try {
34
+ yield this.db.connect();
35
+ this.db.on('error', (err) => {
36
+ console.log('Mongo error: ', err.message);
37
+ });
38
+ console.log('Connected to Mongo');
39
+ }
40
+ catch (e) {
41
+ console.error('ERROR: Failed to connect to Mongo', e);
42
+ }
43
+ }))();
44
+ }
45
+ discoverFields(resource) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ return resource.columns.reduce((acc, col) => {
48
+ acc[col.name] = {
49
+ name: col.name,
50
+ type: col.type,
51
+ primaryKey: col.primaryKey,
52
+ virtual: col.virtual,
53
+ _underlineType: col._underlineType,
54
+ };
55
+ return acc;
56
+ }, {});
57
+ });
58
+ }
59
+ getPrimaryKey(resource) {
60
+ for (const col of resource.dataSourceColumns) {
61
+ if (col.primaryKey) {
62
+ return col.name;
63
+ }
64
+ }
65
+ }
66
+ getFieldValue(field, value) {
67
+ if (field.type == AdminForthTypes.DATETIME) {
68
+ if (!value) {
69
+ return null;
70
+ }
71
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
72
+ return dayjs.unix(+value).toISOString();
73
+ }
74
+ else if (field._underlineType == 'varchar') {
75
+ return dayjs.unix(+value).toISOString();
76
+ }
77
+ else {
78
+ throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps)`);
79
+ }
80
+ }
81
+ else if (field.type == AdminForthTypes.BOOLEAN) {
82
+ return !!value;
83
+ }
84
+ return value;
85
+ }
86
+ getRecordByPrimaryKey(resource, key) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ const tableName = resource.table;
89
+ const collection = this.db.db().collection(tableName);
90
+ const row = yield collection.findOne({ [this.getPrimaryKey(resource)]: key });
91
+ if (!row) {
92
+ return null;
93
+ }
94
+ const newRow = {};
95
+ for (const [key_1, value] of Object.entries(row)) {
96
+ const dbKey = resource.dataSourceColumns.find((col) => col.name == key_1);
97
+ if (!dbKey) {
98
+ continue; // should I continue or throw an error?
99
+ throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
100
+ }
101
+ newRow[key_1] = this.getFieldValue(dbKey, value);
102
+ }
103
+ console.log('newRow', newRow);
104
+ return newRow;
105
+ });
106
+ }
107
+ setFieldValue(field, value) {
108
+ if (field.type == AdminForthTypes.DATETIME) {
109
+ if (!value) {
110
+ return null;
111
+ }
112
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
113
+ // value is iso string now, convert to unix timestamp
114
+ return dayjs(value).unix();
115
+ }
116
+ else if (field._underlineType == 'varchar') {
117
+ // value is iso string now, convert to unix timestamp
118
+ return dayjs(value).toISOString();
119
+ }
120
+ }
121
+ else if (field.type == AdminForthTypes.BOOLEAN) {
122
+ return value ? 1 : 0;
123
+ }
124
+ return value;
125
+ }
126
+ getData(_a) {
127
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
128
+ // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
129
+ const tableName = resource.table;
130
+ for (const filter of filters) {
131
+ if (!this.OperatorsMap[filter.operator]) {
132
+ throw new Error(`Operator ${filter.operator} is not allowed`);
133
+ }
134
+ if (!resource.dataSourceColumns.some((col) => col.name == filter.field)) {
135
+ throw new Error(`Field ${filter.field} is not in resource ${resource.resourceId}. Available fields: ${resource.dataSourceColumns.map((col) => col.name).join(', ')}`);
136
+ }
137
+ }
138
+ const collection = this.db.db().collection(tableName);
139
+ const query = {};
140
+ for (const filter of filters) {
141
+ query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
142
+ }
143
+ const total = yield collection.countDocuments(query);
144
+ const result = yield collection.find(query)
145
+ .sort(sort)
146
+ .skip(offset)
147
+ .limit(limit)
148
+ .toArray();
149
+ return { data: result, total };
150
+ });
151
+ }
152
+ getMinMaxForColumns(_a) {
153
+ return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
154
+ const tableName = resource.table;
155
+ const collection = this.db.db().collection(tableName);
156
+ const result = {};
157
+ for (const column of columns) {
158
+ result[column] = yield collection
159
+ .aggregate([
160
+ { $group: { _id: null, min: { $min: `$${column}` }, max: { $max: `$${column}` } } },
161
+ ])
162
+ .toArray();
163
+ }
164
+ return result;
165
+ });
166
+ }
167
+ createRecord(_a) {
168
+ return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
169
+ const tableName = resource.table;
170
+ const collection = this.db.db().collection(tableName);
171
+ const columns = Object.keys(record);
172
+ const newRecord = {};
173
+ for (const colName of columns) {
174
+ const col = resource.dataSourceColumns.find((col) => col.name == colName);
175
+ if (col) {
176
+ newRecord[colName] = this.setFieldValue(col, record[colName]);
177
+ }
178
+ else {
179
+ newRecord[colName] = record[colName];
180
+ }
181
+ }
182
+ yield collection.insertOne(newRecord);
183
+ });
184
+ }
185
+ updateRecord(_a) {
186
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId, record, newValues }) {
187
+ const collection = this.db.db().collection(resource.table);
188
+ yield collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
189
+ });
190
+ }
191
+ deleteRecord(_a) {
192
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
193
+ const primaryKey = this.getPrimaryKey(resource);
194
+ const collection = this.db.db().collection(resource.table);
195
+ yield collection.deleteOne({ [primaryKey]: recordId });
196
+ });
197
+ }
198
+ close() {
199
+ return __awaiter(this, void 0, void 0, function* () {
200
+ yield this.db.close();
201
+ });
202
+ }
203
+ }
204
+ export default MongoConnector;
@@ -0,0 +1,298 @@
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 dayjs from 'dayjs';
11
+ import pkg from 'pg';
12
+ import { AdminForthFilterOperators, AdminForthSortDirections, AdminForthTypes } from '../types.js';
13
+ const { Client } = pkg;
14
+ class PostgresConnector {
15
+ constructor({ url }) {
16
+ this.OperatorsMap = {
17
+ [AdminForthFilterOperators.EQ]: '=',
18
+ [AdminForthFilterOperators.NE]: '!=',
19
+ [AdminForthFilterOperators.GT]: '>',
20
+ [AdminForthFilterOperators.LT]: '<',
21
+ [AdminForthFilterOperators.GTE]: '>=',
22
+ [AdminForthFilterOperators.LTE]: '<=',
23
+ [AdminForthFilterOperators.LIKE]: 'LIKE',
24
+ [AdminForthFilterOperators.ILIKE]: 'ILIKE',
25
+ [AdminForthFilterOperators.IN]: 'IN',
26
+ [AdminForthFilterOperators.NIN]: 'NOT IN',
27
+ };
28
+ this.SortDirectionsMap = {
29
+ [AdminForthSortDirections.ASC]: 'ASC',
30
+ [AdminForthSortDirections.DESC]: 'DESC',
31
+ };
32
+ this.db = new Client({
33
+ connectionString: url
34
+ });
35
+ (() => __awaiter(this, void 0, void 0, function* () {
36
+ yield this.db.connect();
37
+ this.db.on('error', (err) => {
38
+ console.log('Postgres error: ', err.message, err.stack);
39
+ this.db.end();
40
+ this.db = new PostgresConnector({ url }).db;
41
+ });
42
+ }))();
43
+ }
44
+ discoverFields(resource) {
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ const tableName = resource.table;
47
+ const stmt = yield this.db.query(`
48
+ SELECT
49
+ a.attname AS name,
50
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS type,
51
+ a.attnotnull AS notnull,
52
+ COALESCE(pg_get_expr(d.adbin, d.adrelid), '') AS dflt_value,
53
+ CASE
54
+ WHEN ct.contype = 'p' THEN 1
55
+ ELSE 0
56
+ END AS pk
57
+ FROM
58
+ pg_catalog.pg_attribute a
59
+ LEFT JOIN pg_catalog.pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
60
+ LEFT JOIN pg_catalog.pg_constraint ct ON a.attnum = ANY (ct.conkey) AND a.attrelid = ct.conrelid
61
+ LEFT JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
62
+ LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
63
+ WHERE
64
+ c.relname = $1
65
+ AND a.attnum > 0
66
+ AND NOT a.attisdropped
67
+ ORDER BY
68
+ a.attnum;
69
+ `, [tableName]);
70
+ const rows = stmt.rows;
71
+ const fieldTypes = {};
72
+ rows.forEach((row) => {
73
+ const field = {};
74
+ const baseType = row.type.toLowerCase();
75
+ if (baseType == 'int') {
76
+ field.type = AdminForthTypes.INTEGER;
77
+ field._underlineType = 'int';
78
+ }
79
+ else if (baseType.includes('float') || baseType.includes('double')) {
80
+ field.type = AdminForthTypes.FLOAT;
81
+ field._underlineType = 'float';
82
+ }
83
+ else if (baseType.includes('bool')) {
84
+ field.type = AdminForthTypes.BOOLEAN;
85
+ field._underlineType = 'bool';
86
+ }
87
+ else if (baseType.includes('character varying')) {
88
+ field.type = AdminForthTypes.STRING;
89
+ field._underlineType = 'varchar';
90
+ const length = baseType.match(/\d+/);
91
+ field.maxLength = length ? parseInt(length[0]) : null;
92
+ }
93
+ else if (baseType == 'text') {
94
+ field.type = AdminForthTypes.TEXT;
95
+ field._underlineType = 'text';
96
+ }
97
+ else if (baseType.includes('decimal(')) {
98
+ field.type = AdminForthTypes.DECIMAL;
99
+ field._underlineType = 'decimal';
100
+ const [precision, scale] = baseType.match(/\d+/g);
101
+ field.precision = parseInt(precision);
102
+ field.scale = parseInt(scale);
103
+ }
104
+ else if (baseType == 'real') {
105
+ field.type = AdminForthTypes.FLOAT;
106
+ field._underlineType = 'real';
107
+ }
108
+ else if (baseType.includes('date') || baseType.includes('time')) {
109
+ field.type = AdminForthTypes.DATETIME;
110
+ field._underlineType = 'timestamp';
111
+ }
112
+ else {
113
+ field.type = 'unknown';
114
+ }
115
+ field._baseTypeDebug = baseType;
116
+ field.primaryKey = row.pk == 1;
117
+ field.default = row.dflt_value;
118
+ field.required = row.notnull && !row.dflt_value;
119
+ fieldTypes[row.name] = field;
120
+ });
121
+ return fieldTypes;
122
+ });
123
+ }
124
+ getFieldValue(field, value) {
125
+ if (field.type == AdminForthTypes.DATETIME) {
126
+ if (!value) {
127
+ return null;
128
+ }
129
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
130
+ return dayjs.unix(+value).toISOString();
131
+ }
132
+ else if (field._underlineType == 'varchar') {
133
+ return dayjs.unix(+value).toISOString();
134
+ }
135
+ else {
136
+ throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps)`);
137
+ }
138
+ }
139
+ return value;
140
+ }
141
+ getPrimaryKey(resource) {
142
+ for (const col of resource.dataSourceColumns) {
143
+ if (col.primaryKey) {
144
+ return col.name;
145
+ }
146
+ }
147
+ }
148
+ getRecordByPrimaryKey(resource, key) {
149
+ return __awaiter(this, void 0, void 0, function* () {
150
+ const tableName = resource.table;
151
+ const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
152
+ const stmt = yield this.db.query(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = $1`, [key]);
153
+ const row = stmt.rows[0];
154
+ if (!row) {
155
+ return null;
156
+ }
157
+ const newRow = {};
158
+ for (const [key_1, value] of Object.entries(row)) {
159
+ newRow[key_1] = this.getFieldValue(resource.dataSourceColumns.find((col_1) => col_1.name == key_1), value);
160
+ }
161
+ return newRow;
162
+ });
163
+ }
164
+ setFieldValue(field, value) {
165
+ if (field.type == AdminForthTypes.DATETIME) {
166
+ if (!value) {
167
+ return null;
168
+ }
169
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
170
+ // value is iso string now, convert to unix timestamp
171
+ return dayjs(value).unix();
172
+ }
173
+ else if (field._underlineType == 'varchar') {
174
+ // value is iso string now, convert to unix timestamp
175
+ return dayjs(value).toISOString();
176
+ }
177
+ }
178
+ else if (field.type == AdminForthTypes.BOOLEAN) {
179
+ return value ? 1 : 0;
180
+ }
181
+ return value;
182
+ }
183
+ getData(_a) {
184
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
185
+ const columns = resource.dataSourceColumns.filter(c => !c.virtual).map((col) => col.name).join(', ');
186
+ const tableName = resource.table;
187
+ for (const filter of filters) {
188
+ if (!this.OperatorsMap[filter.operator]) {
189
+ throw new Error(`Operator ${filter.operator} is not allowed`);
190
+ }
191
+ if (!resource.dataSourceColumns.some((col) => col.name == filter.field)) {
192
+ throw new Error(`Field ${filter.field} is not in resource ${resource.resourceId}. Available fields: ${resource.dataSourceColumns.map((col) => col.name).join(', ')}`);
193
+ }
194
+ }
195
+ let totalCounter = 1;
196
+ const where = filters.length ? `WHERE ${filters.map((f, i) => {
197
+ let placeholder = '$' + (totalCounter);
198
+ let field = f.field;
199
+ let operator = this.OperatorsMap[f.operator];
200
+ if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
201
+ placeholder = `(${f.value.map((_, i) => `$${totalCounter + i}`).join(', ')})`;
202
+ totalCounter += f.value.length;
203
+ }
204
+ else {
205
+ totalCounter += 1;
206
+ }
207
+ return `${field} ${operator} ${placeholder}`;
208
+ }).join(' AND ')}` : '';
209
+ const filterValues = [];
210
+ filters.length ? filters.forEach((f) => {
211
+ // for arrays do set in map
212
+ let v;
213
+ if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
214
+ v = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
215
+ }
216
+ else {
217
+ v = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
218
+ }
219
+ if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
220
+ filterValues.push(`%${v}%`);
221
+ }
222
+ else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
223
+ filterValues.push(...v);
224
+ }
225
+ else {
226
+ filterValues.push(v);
227
+ }
228
+ }) : [];
229
+ const limitOffset = `LIMIT $${totalCounter} OFFSET $${totalCounter + 1}`;
230
+ const d = [...filterValues, limit, offset];
231
+ const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
232
+ const stmt = yield this.db.query(`SELECT ${columns} FROM ${tableName} ${where} ${orderBy} ${limitOffset}`, d);
233
+ const rows = stmt.rows;
234
+ const total = (yield this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
235
+ // run all fields via getFieldValue
236
+ return {
237
+ data: rows.map((row) => {
238
+ const newRow = {};
239
+ for (const [key, value] of Object.entries(row)) {
240
+ newRow[key] = this.getFieldValue(resource.dataSourceColumns.find((col) => col.name == key), value);
241
+ }
242
+ return newRow;
243
+ }),
244
+ total,
245
+ };
246
+ });
247
+ }
248
+ getMinMaxForColumns(_a) {
249
+ return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
250
+ const tableName = resource.table;
251
+ const result = {};
252
+ yield Promise.all(columns.map((col) => __awaiter(this, void 0, void 0, function* () {
253
+ const stmt = yield this.db.query(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
254
+ const { min, max } = stmt.rows[0];
255
+ result[col.name] = {
256
+ min: this.getFieldValue(col, min),
257
+ max: this.getFieldValue(col, max),
258
+ };
259
+ })));
260
+ return result;
261
+ });
262
+ }
263
+ createRecord(_a) {
264
+ return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
265
+ const tableName = resource.table;
266
+ const columns = Object.keys(record);
267
+ const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
268
+ const values = columns.map((colName) => {
269
+ const col = resource.dataSourceColumns.find((col) => col.name == colName);
270
+ if (col) {
271
+ return this.setFieldValue(col, record[colName]);
272
+ }
273
+ else {
274
+ return record[colName];
275
+ }
276
+ });
277
+ yield this.db.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values);
278
+ });
279
+ }
280
+ updateRecord(_a) {
281
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId, record, newValues }) {
282
+ const values = [...Object.values(newValues), recordId];
283
+ const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `${col} = $${i + 1}`).join(', ');
284
+ yield this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = $${values.length}`, values);
285
+ });
286
+ }
287
+ deleteRecord(_a) {
288
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
289
+ yield this.db.query(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = $1`, [recordId]);
290
+ });
291
+ }
292
+ close() {
293
+ return __awaiter(this, void 0, void 0, function* () {
294
+ yield this.db.end();
295
+ });
296
+ }
297
+ }
298
+ export default PostgresConnector;