@adminforth/two-factors-auth 1.0.4 → 1.0.5

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/dist/auth.js ADDED
@@ -0,0 +1,108 @@
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
+ constructor(adminforth) {
28
+ this.adminforth = adminforth;
29
+ }
30
+ removeAuthCookie(response) {
31
+ response.setHeader('Set-Cookie', `adminforth_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
32
+ }
33
+ setAuthCookie({ response, username, pk }) {
34
+ const token = this.issueJWT({ username, pk, }, 'auth');
35
+ response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
36
+ }
37
+ removeCustomCookie({ response, name }) {
38
+ response.setHeader('Set-Cookie', `adminforth_test'='2'; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
39
+ }
40
+ setCustomCookie({ response, payload }) {
41
+ const { name, value, expiry, httpOnly } = payload;
42
+ response.setHeader('Set-Cookie', `adminforth_${name}=${value}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${new Date(Date.now() + expiry).toUTCString()} `);
43
+ }
44
+ issueJWT(payload, type) {
45
+ // read ADMINFORH_SECRET from environment if not drop error
46
+ const secret = process.env.ADMINFORTH_SECRET;
47
+ if (!secret) {
48
+ throw new Error('ADMINFORTH_SECRET environment not set');
49
+ }
50
+ // issue JWT token
51
+ const expiresIn = process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h';
52
+ return jwt.sign(Object.assign(Object.assign({}, payload), { t: type }), secret, { expiresIn });
53
+ }
54
+ verify(jwtToken, mustHaveType) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ // read ADMINFORH_SECRET from environment if not drop error
57
+ const secret = process.env.ADMINFORTH_SECRET;
58
+ if (!secret) {
59
+ throw new Error('ADMINFORTH_SECRET environment not set');
60
+ }
61
+ let decoded;
62
+ try {
63
+ // verify JWT token
64
+ decoded = jwt.verify(jwtToken, secret);
65
+ }
66
+ catch (err) {
67
+ if (err.name === 'TokenExpiredError') {
68
+ console.error('Token expired:', err.message);
69
+ }
70
+ else if (err.name === 'JsonWebTokenError') {
71
+ console.error('Token error:', err.message);
72
+ }
73
+ else {
74
+ console.error('Failed to verify JWT token', err);
75
+ }
76
+ return null;
77
+ }
78
+ const { pk, t } = decoded;
79
+ if (t !== mustHaveType) {
80
+ console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
81
+ return null;
82
+ }
83
+ if (pk === null) {
84
+ decoded.isRoot = true;
85
+ }
86
+ else {
87
+ const dbUser = yield this.adminforth.getUserByPk(pk);
88
+ decoded.dbUser = dbUser;
89
+ }
90
+ return decoded;
91
+ });
92
+ }
93
+ static generatePasswordHash(password) {
94
+ return __awaiter(this, void 0, void 0, function* () {
95
+ const salt = generateSalt();
96
+ const hashedPassword = yield calcPasswordHash(password, salt);
97
+ return `${salt}:${hashedPassword}`;
98
+ });
99
+ }
100
+ static verifyPassword(password, hashedPassword) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ const [salt, hash] = hashedPassword.split(':');
103
+ const newHash = yield calcPasswordHash(password, salt);
104
+ return newHash === hash;
105
+ });
106
+ }
107
+ }
108
+ export default AdminForthAuth;
@@ -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
+ }
@@ -0,0 +1,191 @@
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 { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.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
+ if (!col.type) {
49
+ throw new Error(`Type is not defined for column ${col.name}`);
50
+ }
51
+ acc[col.name] = {
52
+ name: col.name,
53
+ type: col.type,
54
+ primaryKey: col.primaryKey,
55
+ virtual: col.virtual,
56
+ _underlineType: col._underlineType,
57
+ };
58
+ return acc;
59
+ }, {});
60
+ });
61
+ }
62
+ getPrimaryKey(resource) {
63
+ for (const col of resource.dataSourceColumns) {
64
+ if (col.primaryKey) {
65
+ return col.name;
66
+ }
67
+ }
68
+ }
69
+ getFieldValue(field, value) {
70
+ if (field.type == AdminForthDataTypes.DATETIME) {
71
+ if (!value) {
72
+ return null;
73
+ }
74
+ return dayjs(Date.parse(value)).toISOString();
75
+ }
76
+ else if (field.type == AdminForthDataTypes.DATE) {
77
+ if (!value) {
78
+ return null;
79
+ }
80
+ return dayjs(Date.parse(value)).toISOString().split('T')[0];
81
+ }
82
+ else if (field.type == AdminForthDataTypes.BOOLEAN) {
83
+ return !!value;
84
+ }
85
+ return value;
86
+ }
87
+ getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ const tableName = resource.table;
90
+ const collection = this.db.db().collection(tableName);
91
+ const row = yield collection.findOne({ [this.getPrimaryKey(resource)]: key });
92
+ if (!row) {
93
+ return null;
94
+ }
95
+ const newRow = {};
96
+ for (const [key_1, value] of Object.entries(row)) {
97
+ const dbKey = resource.dataSourceColumns.find((col) => col.name == key_1);
98
+ if (!dbKey) {
99
+ continue; // should I continue or throw an error?
100
+ throw new Error(`Resource '${resource.table}' has no column '${key_1}' defined`);
101
+ }
102
+ newRow[key_1] = value;
103
+ }
104
+ console.log('newRow', newRow);
105
+ return newRow;
106
+ });
107
+ }
108
+ setFieldValue(field, value) {
109
+ if (field.type == AdminForthDataTypes.DATETIME) {
110
+ if (!value) {
111
+ return null;
112
+ }
113
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
114
+ // value is iso string now, convert to unix timestamp
115
+ return dayjs(value).unix();
116
+ }
117
+ else if (field._underlineType == 'varchar') {
118
+ // value is iso string now, convert to unix timestamp
119
+ return dayjs(value).toISOString();
120
+ }
121
+ }
122
+ else if (field.type == AdminForthDataTypes.BOOLEAN) {
123
+ return value ? 1 : 0;
124
+ }
125
+ return value;
126
+ }
127
+ getDataWithOriginalTypes(_a) {
128
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
129
+ // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
130
+ const tableName = resource.table;
131
+ const collection = this.db.db().collection(tableName);
132
+ const query = {};
133
+ for (const filter of filters) {
134
+ query[filter.field] = this.OperatorsMap[filter.operator](filter.value);
135
+ }
136
+ const total = yield collection.countDocuments(query);
137
+ const result = yield collection.find(query)
138
+ .sort(sort)
139
+ .skip(offset)
140
+ .limit(limit)
141
+ .toArray();
142
+ return { data: result, total };
143
+ });
144
+ }
145
+ getMinMaxForColumnsWithOriginalTypes(_a) {
146
+ return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
147
+ const tableName = resource.table;
148
+ const collection = this.db.db().collection(tableName);
149
+ const result = {};
150
+ for (const column of columns) {
151
+ result[column] = yield collection
152
+ .aggregate([
153
+ { $group: { _id: null, min: { $min: `$${column}` }, max: { $max: `$${column}` } } },
154
+ ])
155
+ .toArray();
156
+ }
157
+ return result;
158
+ });
159
+ }
160
+ createRecord(_a) {
161
+ return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
162
+ const tableName = resource.table;
163
+ const collection = this.db.db().collection(tableName);
164
+ const columns = Object.keys(record);
165
+ const newRecord = {};
166
+ for (const colName of columns) {
167
+ newRecord[colName] = record[colName];
168
+ }
169
+ yield collection.insertOne(newRecord);
170
+ });
171
+ }
172
+ updateRecord(_a) {
173
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
174
+ const collection = this.db.db().collection(resource.table);
175
+ yield collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
176
+ });
177
+ }
178
+ deleteRecord(_a) {
179
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
180
+ const primaryKey = this.getPrimaryKey(resource);
181
+ const collection = this.db.db().collection(resource.table);
182
+ yield collection.deleteOne({ [primaryKey]: recordId });
183
+ });
184
+ }
185
+ close() {
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ yield this.db.close();
188
+ });
189
+ }
190
+ }
191
+ export default MongoConnector;
@@ -0,0 +1,295 @@
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 { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from '../types/AdminForthConfig.js';
13
+ import AdminForthBaseConnector from './baseConnector.js';
14
+ const { Client } = pkg;
15
+ class PostgresConnector extends AdminForthBaseConnector {
16
+ constructor({ url }) {
17
+ super();
18
+ this.OperatorsMap = {
19
+ [AdminForthFilterOperators.EQ]: '=',
20
+ [AdminForthFilterOperators.NE]: '!=',
21
+ [AdminForthFilterOperators.GT]: '>',
22
+ [AdminForthFilterOperators.LT]: '<',
23
+ [AdminForthFilterOperators.GTE]: '>=',
24
+ [AdminForthFilterOperators.LTE]: '<=',
25
+ [AdminForthFilterOperators.LIKE]: 'LIKE',
26
+ [AdminForthFilterOperators.ILIKE]: 'ILIKE',
27
+ [AdminForthFilterOperators.IN]: 'IN',
28
+ [AdminForthFilterOperators.NIN]: 'NOT IN',
29
+ };
30
+ this.SortDirectionsMap = {
31
+ [AdminForthSortDirections.asc]: 'ASC',
32
+ [AdminForthSortDirections.desc]: 'DESC',
33
+ };
34
+ this.db = new Client({
35
+ connectionString: url
36
+ });
37
+ (() => __awaiter(this, void 0, void 0, function* () {
38
+ yield this.db.connect();
39
+ this.db.on('error', (err) => {
40
+ console.log('Postgres error: ', err.message, err.stack);
41
+ this.db.end();
42
+ this.db = new PostgresConnector({ url }).db;
43
+ });
44
+ }))();
45
+ }
46
+ discoverFields(resource) {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ const tableName = resource.table;
49
+ const stmt = yield this.db.query(`
50
+ SELECT
51
+ a.attname AS name,
52
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS type,
53
+ a.attnotnull AS notnull,
54
+ COALESCE(pg_get_expr(d.adbin, d.adrelid), '') AS dflt_value,
55
+ CASE
56
+ WHEN ct.contype = 'p' THEN 1
57
+ ELSE 0
58
+ END AS pk
59
+ FROM
60
+ pg_catalog.pg_attribute a
61
+ LEFT JOIN pg_catalog.pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
62
+ LEFT JOIN pg_catalog.pg_constraint ct ON a.attnum = ANY (ct.conkey) AND a.attrelid = ct.conrelid
63
+ LEFT JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
64
+ LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
65
+ WHERE
66
+ c.relname = $1
67
+ AND a.attnum > 0
68
+ AND NOT a.attisdropped
69
+ ORDER BY
70
+ a.attnum;
71
+ `, [tableName]);
72
+ const rows = stmt.rows;
73
+ const fieldTypes = {};
74
+ rows.forEach((row) => {
75
+ const field = {};
76
+ const baseType = row.type.toLowerCase();
77
+ if (baseType == 'int') {
78
+ field.type = AdminForthDataTypes.INTEGER;
79
+ field._underlineType = 'int';
80
+ }
81
+ else if (baseType.includes('float') || baseType.includes('double')) {
82
+ field.type = AdminForthDataTypes.FLOAT;
83
+ field._underlineType = 'float';
84
+ }
85
+ else if (baseType.includes('bool')) {
86
+ field.type = AdminForthDataTypes.BOOLEAN;
87
+ field._underlineType = 'bool';
88
+ }
89
+ else if (baseType == 'uuid') {
90
+ field.type = AdminForthDataTypes.STRING;
91
+ field._underlineType = 'uuid';
92
+ }
93
+ else if (baseType.includes('character varying')) {
94
+ field.type = AdminForthDataTypes.STRING;
95
+ field._underlineType = 'varchar';
96
+ const length = baseType.match(/\d+/);
97
+ field.maxLength = length ? parseInt(length[0]) : null;
98
+ }
99
+ else if (baseType == 'text') {
100
+ field.type = AdminForthDataTypes.TEXT;
101
+ field._underlineType = 'text';
102
+ }
103
+ else if (baseType.includes('decimal(')) {
104
+ field.type = AdminForthDataTypes.DECIMAL;
105
+ field._underlineType = 'decimal';
106
+ const [precision, scale] = baseType.match(/\d+/g);
107
+ field.precision = parseInt(precision);
108
+ field.scale = parseInt(scale);
109
+ }
110
+ else if (baseType == 'real') {
111
+ field.type = AdminForthDataTypes.FLOAT;
112
+ field._underlineType = 'real';
113
+ }
114
+ else if (baseType == 'date') {
115
+ field.type = AdminForthDataTypes.DATE;
116
+ field._underlineType = 'timestamp';
117
+ }
118
+ else if (baseType.includes('date') || baseType.includes('time')) {
119
+ field.type = AdminForthDataTypes.DATETIME;
120
+ field._underlineType = 'timestamp';
121
+ }
122
+ else {
123
+ field.type = 'unknown';
124
+ }
125
+ field._baseTypeDebug = baseType;
126
+ field.primaryKey = row.pk == 1;
127
+ field.default = row.dflt_value;
128
+ field.required = row.notnull && !row.dflt_value;
129
+ fieldTypes[row.name] = field;
130
+ });
131
+ return fieldTypes;
132
+ });
133
+ }
134
+ getFieldValue(field, value) {
135
+ if (field.type == AdminForthDataTypes.DATETIME) {
136
+ if (!value) {
137
+ return null;
138
+ }
139
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
140
+ return dayjs(value).toISOString();
141
+ }
142
+ else if (field._underlineType == 'varchar') {
143
+ return dayjs(value).toISOString();
144
+ }
145
+ else {
146
+ throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps)`);
147
+ }
148
+ }
149
+ if (field.type == AdminForthDataTypes.DATE) {
150
+ if (!value) {
151
+ return null;
152
+ }
153
+ return dayjs(value).toISOString().split('T')[0];
154
+ }
155
+ return value;
156
+ }
157
+ getRecordByPrimaryKeyWithOriginalTypes(resource, key) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ const tableName = resource.table;
160
+ const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
161
+ const stmt = yield this.db.query(`SELECT ${columns} FROM ${tableName} WHERE ${this.getPrimaryKey(resource)} = $1`, [key]);
162
+ const row = stmt.rows[0];
163
+ if (!row) {
164
+ return null;
165
+ }
166
+ const newRow = {};
167
+ for (const [key_1, value] of Object.entries(row)) {
168
+ newRow[key_1] = value;
169
+ }
170
+ return newRow;
171
+ });
172
+ }
173
+ setFieldValue(field, value) {
174
+ if (field.type == AdminForthDataTypes.DATETIME) {
175
+ if (!value) {
176
+ return null;
177
+ }
178
+ if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
179
+ return dayjs(value);
180
+ }
181
+ else if (field._underlineType == 'varchar') {
182
+ return dayjs(value).toISOString();
183
+ }
184
+ }
185
+ else if (field.type == AdminForthDataTypes.BOOLEAN) {
186
+ return value ? 1 : 0;
187
+ }
188
+ return value;
189
+ }
190
+ getDataWithOriginalTypes(_a) {
191
+ return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
192
+ const columns = resource.dataSourceColumns.map((col) => `"${col.name}"`).join(', ');
193
+ const tableName = resource.table;
194
+ let totalCounter = 1;
195
+ const where = filters.length ? `WHERE ${filters.map((f, i) => {
196
+ let placeholder = '$' + (totalCounter);
197
+ const fieldData = resource.dataSourceColumns.find((col) => col.name == f.field);
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
+ if (fieldData._underlineType == 'uuid') {
208
+ field = `cast("${field}" as text)`;
209
+ }
210
+ else {
211
+ field = `"${field}"`;
212
+ }
213
+ return `${field} ${operator} ${placeholder}`;
214
+ }).join(' AND ')}` : '';
215
+ const filterValues = [];
216
+ filters.length ? filters.forEach((f) => {
217
+ // for arrays do set in map
218
+ let v = f.value;
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 selectQuery = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} ${limitOffset}`;
233
+ if (process.env.HEAVY_DEBUG) {
234
+ console.log('🪲 PG selectQuery:', selectQuery, 'params:', d);
235
+ }
236
+ const stmt = yield this.db.query(selectQuery, d);
237
+ const rows = stmt.rows;
238
+ const total = (yield this.db.query(`SELECT COUNT(*) FROM ${tableName} ${where}`, filterValues)).rows[0].count;
239
+ return {
240
+ data: rows.map((row) => {
241
+ const newRow = {};
242
+ for (const [key, value] of Object.entries(row)) {
243
+ newRow[key] = value;
244
+ }
245
+ return newRow;
246
+ }),
247
+ total,
248
+ };
249
+ });
250
+ }
251
+ getMinMaxForColumnsWithOriginalTypes(_a) {
252
+ return __awaiter(this, arguments, void 0, function* ({ resource, columns }) {
253
+ const tableName = resource.table;
254
+ const result = {};
255
+ yield Promise.all(columns.map((col) => __awaiter(this, void 0, void 0, function* () {
256
+ const stmt = yield this.db.query(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
257
+ const { min, max } = stmt.rows[0];
258
+ result[col.name] = {
259
+ min, max,
260
+ };
261
+ })));
262
+ return result;
263
+ });
264
+ }
265
+ createRecord(_a) {
266
+ return __awaiter(this, arguments, void 0, function* ({ resource, record }) {
267
+ const tableName = resource.table;
268
+ const columns = Object.keys(record);
269
+ const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
270
+ const values = columns.map((colName) => record[colName]);
271
+ for (let i = 0; i < columns.length; i++) {
272
+ columns[i] = `"${columns[i]}"`;
273
+ }
274
+ yield this.db.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values);
275
+ });
276
+ }
277
+ updateRecord(_a) {
278
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId, newValues }) {
279
+ const values = [...Object.values(newValues), recordId];
280
+ const columnsWithPlaceholders = Object.keys(newValues).map((col, i) => `"${col}" = $${i + 1}`).join(', ');
281
+ yield this.db.query(`UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE "${this.getPrimaryKey(resource)}" = $${values.length}`, values);
282
+ });
283
+ }
284
+ deleteRecord(_a) {
285
+ return __awaiter(this, arguments, void 0, function* ({ resource, recordId }) {
286
+ yield this.db.query(`DELETE FROM ${resource.table} WHERE "${this.getPrimaryKey(resource)}" = $1`, [recordId]);
287
+ });
288
+ }
289
+ close() {
290
+ return __awaiter(this, void 0, void 0, function* () {
291
+ yield this.db.end();
292
+ });
293
+ }
294
+ }
295
+ export default PostgresConnector;