adminforth 1.1.73 → 1.1.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth.ts +14 -0
- 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/auth.js +7 -0
- 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 +125 -66
- package/dist/modules/codeInjector.js +29 -15
- package/dist/plugins/AuditLogPlugin/index.js +2 -17
- package/dist/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
- package/dist/plugins/S3UploadPlugin/index.js +103 -0
- package/dist/plugins/S3UploadPlugin/package-lock.json +431 -0
- package/dist/plugins/S3UploadPlugin/package.json +15 -0
- package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
- package/dist/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
- package/dist/plugins/TwoFactorsAuthPlugin/index.js +150 -0
- package/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +111 -0
- package/dist/spa/package.json +1 -0
- package/dist/spa/src/App.vue +178 -133
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +96 -96
- package/dist/spa/src/components/ResourceListTable.vue +69 -84
- package/dist/spa/src/components/ValueRenderer.vue +2 -3
- package/dist/spa/src/composables/useStores.ts +24 -11
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +0 -1
- package/dist/spa/src/utils.ts +1 -1
- package/dist/spa/src/views/CreateView.vue +1 -9
- package/dist/spa/src/views/EditView.vue +1 -9
- package/dist/spa/src/views/ListView.vue +12 -3
- package/dist/spa/src/views/LoginView.vue +6 -1
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +6 -14
- package/dist/spa/tailwind.config.js +3 -1
- package/index.ts +103 -36
- package/modules/codeInjector.ts +38 -27
- package/package.json +2 -1
- package/plugins/AuditLogPlugin/index.ts +4 -20
- package/plugins/S3UploadPlugin/custom/s3uploader.vue +120 -0
- package/plugins/S3UploadPlugin/index.ts +112 -0
- package/plugins/S3UploadPlugin/package-lock.json +431 -0
- package/plugins/S3UploadPlugin/package.json +15 -0
- package/plugins/S3UploadPlugin/types.ts +76 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsConfirmation.vue +152 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue +0 -0
- package/plugins/TwoFactorsAuthPlugin/custom/TwoFactorsSetup.vue +188 -0
- package/plugins/TwoFactorsAuthPlugin/index.ts +150 -0
- package/plugins/TwoFactorsAuthPlugin/types.ts +10 -0
- package/servers/express.ts +1 -0
- package/spa/src/App.vue +154 -121
- package/spa/src/components/ResourceForm.vue +91 -91
- package/spa/src/router/index.ts +0 -1
- package/spa/src/utils.ts +1 -1
- package/spa/src/views/CreateView.vue +0 -1
- package/spa/src/views/LoginView.vue +4 -1
- package/types/AdminForthConfig.ts +166 -5
- package/dist/plugins/AuditLog/index.js +0 -13
- package/dist/spa/spa/.eslintrc.cjs +0 -14
- package/dist/spa/spa/.vscode/extensions.json +0 -6
- package/dist/spa/spa/README.md +0 -39
- package/dist/spa/spa/env.d.ts +0 -1
- package/dist/spa/spa/index.html +0 -23
- package/dist/spa/spa/package-lock.json +0 -4470
- package/dist/spa/spa/package.json +0 -48
- package/dist/spa/spa/postcss.config.js +0 -6
- package/dist/spa/spa/public/assets/favicon.png +0 -0
- package/dist/spa/spa/src/App.vue +0 -283
- package/dist/spa/spa/src/assets/logo.svg +0 -19
- package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
- package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
- package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
- package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
- package/dist/spa/spa/src/components/Dropdown.vue +0 -168
- package/dist/spa/spa/src/components/Filters.vue +0 -211
- package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
- package/dist/spa/spa/src/components/MenuLink.vue +0 -24
- package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
- package/dist/spa/spa/src/components/ResourceListTable.vue +0 -404
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/dist/spa/spa/src/components/Toast.vue +0 -66
- package/dist/spa/spa/src/components/ValueRenderer.vue +0 -59
- package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
- package/dist/spa/spa/src/composables/useFrontendApi.ts +0 -26
- package/dist/spa/spa/src/composables/useStores.ts +0 -126
- package/dist/spa/spa/src/index.scss +0 -26
- package/dist/spa/spa/src/main.ts +0 -18
- package/dist/spa/spa/src/router/index.ts +0 -64
- package/dist/spa/spa/src/spa_types/core.ts +0 -51
- package/dist/spa/spa/src/stores/core.ts +0 -144
- package/dist/spa/spa/src/stores/filters.ts +0 -22
- package/dist/spa/spa/src/stores/modal.ts +0 -48
- package/dist/spa/spa/src/stores/toast.ts +0 -15
- package/dist/spa/spa/src/stores/user.ts +0 -54
- package/dist/spa/spa/src/utils.ts +0 -101
- package/dist/spa/spa/src/views/CreateView.vue +0 -156
- package/dist/spa/spa/src/views/EditView.vue +0 -157
- package/dist/spa/spa/src/views/ListView.vue +0 -266
- package/dist/spa/spa/src/views/LoginView.vue +0 -136
- package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
- package/dist/spa/spa/src/views/ShowView.vue +0 -180
- package/dist/spa/spa/tailwind.config.js +0 -17
- package/dist/spa/spa/tsconfig.app.json +0 -14
- package/dist/spa/spa/tsconfig.json +0 -11
- package/dist/spa/spa/tsconfig.node.json +0 -19
- package/dist/spa/spa/vite.config.ts +0 -49
- /package/dist/plugins/{AuditLog → S3UploadPlugin}/types.js +0 -0
- /package/dist/{spa/spa/src/assets/base.css → plugins/TwoFactorsAuthPlugin/custom/TwoFactorsOtp.vue} +0 -0
- /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
package/auth.ts
CHANGED
|
@@ -37,6 +37,20 @@ class AdminForthAuth {
|
|
|
37
37
|
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
removeCustomCookie({response, name}) {
|
|
41
|
+
response.setHeader('Set-Cookie', `adminforth_test'='2'; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setCustomCookie({ response, payload }: {
|
|
45
|
+
response: any, payload: {name: string, value: string, expiry: number, httpOnly: boolean}
|
|
46
|
+
}) {
|
|
47
|
+
const {name,value,expiry,httpOnly} = payload
|
|
48
|
+
response.setHeader('Set-Cookie', `adminforth_${name}=${value}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${new Date(Date.now() + expiry).toUTCString() } `);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
40
54
|
issueJWT(payload: Object, type: string) {
|
|
41
55
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
42
56
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
@@ -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
|
|
package/dist/auth.js
CHANGED
|
@@ -34,6 +34,13 @@ class AdminForthAuth {
|
|
|
34
34
|
const token = this.issueJWT({ username, pk, }, 'auth');
|
|
35
35
|
response.setHeader('Set-Cookie', `adminforth_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict`);
|
|
36
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
|
+
}
|
|
37
44
|
issueJWT(payload, type) {
|
|
38
45
|
// read ADMINFORH_SECRET from environment if not drop error
|
|
39
46
|
const secret = process.env.ADMINFORTH_SECRET;
|
|
@@ -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
|
+
}
|