adminforth 1.3.54-next.9 → 1.3.55-next.2
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.d.ts +31 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +42 -56
- package/dist/auth.js.map +1 -0
- package/dist/basePlugin.d.ts +18 -0
- package/dist/basePlugin.d.ts.map +1 -0
- package/dist/basePlugin.js +1 -0
- package/dist/basePlugin.js.map +1 -0
- package/dist/dataConnectors/baseConnector.d.ts +94 -0
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -0
- package/dist/dataConnectors/baseConnector.js +108 -122
- package/dist/dataConnectors/baseConnector.js.map +1 -0
- package/dist/dataConnectors/clickhouse.d.ts +92 -0
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -0
- package/dist/dataConnectors/clickhouse.js +132 -149
- package/dist/dataConnectors/clickhouse.js.map +1 -0
- package/dist/dataConnectors/mongo.d.ts +93 -0
- package/dist/dataConnectors/mongo.d.ts.map +1 -0
- package/dist/dataConnectors/mongo.js +75 -101
- package/dist/dataConnectors/mongo.js.map +1 -0
- package/dist/dataConnectors/postgres.d.ts +71 -0
- package/dist/dataConnectors/postgres.d.ts.map +1 -0
- package/dist/dataConnectors/postgres.js +124 -143
- package/dist/dataConnectors/postgres.js.map +1 -0
- package/dist/dataConnectors/sqlite.d.ts +67 -0
- package/dist/dataConnectors/sqlite.d.ts.map +1 -0
- package/dist/dataConnectors/sqlite.js +113 -130
- package/dist/dataConnectors/sqlite.js.map +1 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -217
- package/dist/index.js.map +1 -0
- package/dist/modules/codeInjector.d.ts +35 -0
- package/dist/modules/codeInjector.d.ts.map +1 -0
- package/dist/modules/codeInjector.js +480 -486
- package/dist/modules/codeInjector.js.map +1 -0
- package/dist/modules/configValidator.d.ts +12 -0
- package/dist/modules/configValidator.d.ts.map +1 -0
- package/dist/modules/configValidator.js +31 -22
- package/dist/modules/configValidator.js.map +1 -0
- package/dist/modules/operationalResource.d.ts +17 -0
- package/dist/modules/operationalResource.d.ts.map +1 -0
- package/dist/modules/operationalResource.js +50 -70
- package/dist/modules/operationalResource.js.map +1 -0
- package/dist/modules/restApi.d.ts +10 -0
- package/dist/modules/restApi.d.ts.map +1 -0
- package/dist/modules/restApi.js +104 -116
- package/dist/modules/restApi.js.map +1 -0
- package/dist/modules/styleGenerator.d.ts +9 -0
- package/dist/modules/styleGenerator.d.ts.map +1 -0
- package/dist/modules/styleGenerator.js +1 -0
- package/dist/modules/styleGenerator.js.map +1 -0
- package/dist/modules/styles.d.ts +96 -0
- package/dist/modules/styles.d.ts.map +1 -0
- package/dist/modules/styles.js +1 -0
- package/dist/modules/styles.js.map +1 -0
- package/dist/modules/utils.d.ts +39 -0
- package/dist/modules/utils.d.ts.map +1 -0
- package/dist/modules/utils.js +1 -0
- package/dist/modules/utils.js.map +1 -0
- package/dist/plugins/audit-log/types.d.ts +35 -0
- package/dist/plugins/audit-log/types.d.ts.map +1 -0
- package/dist/plugins/audit-log/types.js +2 -0
- package/dist/plugins/audit-log/types.js.map +1 -0
- package/dist/plugins/chat-gpt/types.d.ts +82 -0
- package/dist/plugins/chat-gpt/types.d.ts.map +1 -0
- package/dist/plugins/chat-gpt/types.js +2 -0
- package/dist/plugins/chat-gpt/types.js.map +1 -0
- package/dist/plugins/email-password-reset/types.d.ts +28 -0
- package/dist/plugins/email-password-reset/types.d.ts.map +1 -0
- package/dist/plugins/email-password-reset/types.js +2 -0
- package/dist/plugins/email-password-reset/types.js.map +1 -0
- package/dist/plugins/foreign-inline-list/types.d.ts +19 -0
- package/dist/plugins/foreign-inline-list/types.d.ts.map +1 -0
- package/dist/plugins/foreign-inline-list/types.js +2 -0
- package/dist/plugins/foreign-inline-list/types.js.map +1 -0
- package/dist/plugins/import-export/types.d.ts +3 -0
- package/dist/plugins/import-export/types.d.ts.map +1 -0
- package/dist/plugins/import-export/types.js +2 -0
- package/dist/plugins/import-export/types.js.map +1 -0
- package/dist/plugins/rich-editor/custom/async-queue.d.ts +8 -0
- package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +1 -0
- package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
- package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +8 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +1 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
- package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
- package/dist/plugins/rich-editor/types.d.ts +153 -0
- package/dist/plugins/rich-editor/types.d.ts.map +1 -0
- package/dist/plugins/rich-editor/types.js +16 -0
- package/dist/plugins/rich-editor/types.js.map +1 -0
- package/dist/plugins/two-factors-auth/types.d.ts +18 -0
- package/dist/plugins/two-factors-auth/types.d.ts.map +1 -0
- package/dist/plugins/two-factors-auth/types.js +2 -0
- package/dist/plugins/two-factors-auth/types.js.map +1 -0
- package/dist/plugins/upload/types.d.ts +132 -0
- package/dist/plugins/upload/types.d.ts.map +1 -0
- package/dist/plugins/upload/types.js +2 -0
- package/dist/plugins/upload/types.js.map +1 -0
- package/dist/servers/express.d.ts +18 -0
- package/dist/servers/express.d.ts.map +1 -0
- package/dist/servers/express.js +30 -42
- package/dist/servers/express.js.map +1 -0
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +87 -1
- package/dist/spa/package.json +4 -1
- package/dist/spa/src/App.vue +154 -50
- package/dist/spa/src/components/AcceptModal.vue +1 -1
- package/dist/spa/src/components/Breadcrumbs.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +1 -1
- package/dist/spa/src/components/CustomDateRangePicker.vue +1 -1
- package/dist/spa/src/components/CustomRangePicker.vue +9 -5
- package/dist/spa/src/components/Dropdown.vue +4 -4
- package/dist/spa/src/components/Filters.vue +2 -2
- package/dist/spa/src/components/MenuLink.vue +3 -0
- package/dist/spa/src/components/ResourceForm.vue +67 -36
- package/dist/spa/src/components/ResourceListTable.vue +216 -144
- package/dist/spa/src/components/SkeleteLoader.vue +4 -4
- package/dist/spa/src/components/Toast.vue +3 -2
- package/dist/spa/src/components/ValueRenderer.vue +81 -6
- package/dist/spa/src/composables/useFrontendApi.ts +1 -1
- package/dist/spa/src/composables/useStores.ts +18 -14
- package/dist/spa/src/index.scss +4 -0
- package/{spa → dist/spa}/src/renderers/CompactUUID.vue +4 -4
- package/{spa → dist/spa}/src/renderers/CountryFlag.vue +2 -2
- package/dist/spa/src/router/index.ts +4 -8
- package/dist/spa/src/spa_types/core.ts +2 -0
- package/dist/spa/src/stores/core.ts +6 -2
- package/dist/spa/src/stores/filters.ts +15 -10
- package/dist/spa/src/stores/toast.ts +22 -6
- package/dist/spa/src/types/AdminForthConfig.ts +340 -55
- package/dist/spa/src/types/FrontendAPI.ts +52 -30
- package/dist/spa/src/utils.ts +59 -2
- package/dist/spa/src/views/CreateView.vue +15 -4
- package/dist/spa/src/views/EditView.vue +20 -7
- package/dist/spa/src/views/ListView.vue +132 -38
- package/dist/spa/src/views/LoginView.vue +50 -18
- package/dist/spa/src/views/ShowView.vue +25 -15
- package/dist/types/AdminForthConfig.d.ts +1619 -0
- package/dist/types/AdminForthConfig.d.ts.map +1 -0
- package/dist/types/AdminForthConfig.js +1 -0
- package/dist/types/AdminForthConfig.js.map +1 -0
- package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
- package/dist/types/FrontendAPI.d.ts.map +1 -0
- package/dist/types/FrontendAPI.js +1 -0
- package/dist/types/FrontendAPI.js.map +1 -0
- package/package.json +16 -6
- package/auth.ts +0 -140
- package/basePlugin.ts +0 -70
- package/dataConnectors/baseConnector.ts +0 -216
- package/dataConnectors/clickhouse.ts +0 -338
- package/dataConnectors/mongo.ts +0 -202
- package/dataConnectors/postgres.ts +0 -306
- package/dataConnectors/sqlite.ts +0 -254
- package/index.ts +0 -428
- package/modules/codeInjector.ts +0 -736
- package/modules/configValidator.ts +0 -571
- package/modules/operationalResource.ts +0 -98
- package/modules/restApi.ts +0 -718
- package/modules/styleGenerator.ts +0 -55
- package/modules/styles.ts +0 -126
- package/modules/utils.ts +0 -472
- package/servers/express.ts +0 -259
- package/spa/.eslintrc.cjs +0 -14
- package/spa/README.md +0 -39
- package/spa/env.d.ts +0 -1
- package/spa/index.html +0 -23
- package/spa/package-lock.json +0 -4602
- package/spa/package.json +0 -51
- package/spa/postcss.config.js +0 -6
- package/spa/public/assets/favicon.png +0 -0
- package/spa/src/App.vue +0 -418
- package/spa/src/assets/base.css +0 -2
- package/spa/src/assets/logo.svg +0 -19
- package/spa/src/components/AcceptModal.vue +0 -45
- package/spa/src/components/Breadcrumbs.vue +0 -41
- package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
- package/spa/src/components/CustomDatePicker.vue +0 -176
- package/spa/src/components/CustomDateRangePicker.vue +0 -218
- package/spa/src/components/CustomRangePicker.vue +0 -156
- package/spa/src/components/Dropdown.vue +0 -168
- package/spa/src/components/Filters.vue +0 -222
- package/spa/src/components/HelloWorld.vue +0 -17
- package/spa/src/components/MenuLink.vue +0 -27
- package/spa/src/components/ResourceForm.vue +0 -290
- package/spa/src/components/ResourceListTable.vue +0 -466
- package/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/spa/src/components/SkeleteLoader.vue +0 -23
- package/spa/src/components/Toast.vue +0 -78
- package/spa/src/components/ValueRenderer.vue +0 -114
- package/spa/src/components/icons/IconCalendar.vue +0 -5
- package/spa/src/components/icons/IconCommunity.vue +0 -7
- package/spa/src/components/icons/IconDocumentation.vue +0 -7
- package/spa/src/components/icons/IconEcosystem.vue +0 -7
- package/spa/src/components/icons/IconSupport.vue +0 -7
- package/spa/src/components/icons/IconTime.vue +0 -5
- package/spa/src/components/icons/IconTooling.vue +0 -19
- package/spa/src/composables/useFrontendApi.ts +0 -26
- package/spa/src/composables/useStores.ts +0 -131
- package/spa/src/index.scss +0 -31
- package/spa/src/main.ts +0 -18
- package/spa/src/router/index.ts +0 -59
- package/spa/src/spa_types/core.ts +0 -53
- package/spa/src/stores/core.ts +0 -148
- package/spa/src/stores/filters.ts +0 -27
- package/spa/src/stores/modal.ts +0 -48
- package/spa/src/stores/toast.ts +0 -31
- package/spa/src/stores/user.ts +0 -72
- package/spa/src/utils.ts +0 -160
- package/spa/src/views/CreateView.vue +0 -167
- package/spa/src/views/EditView.vue +0 -170
- package/spa/src/views/ListView.vue +0 -352
- package/spa/src/views/LoginView.vue +0 -192
- package/spa/src/views/ResourceParent.vue +0 -17
- package/spa/src/views/ShowView.vue +0 -186
- package/spa/tailwind.config.js +0 -17
- package/spa/tsconfig.app.json +0 -14
- package/spa/tsconfig.json +0 -11
- package/spa/tsconfig.node.json +0 -19
- package/spa/vite.config.ts +0 -56
- package/tsconfig.json +0 -112
- package/types/AdminForthConfig.ts +0 -1762
- /package/{spa → dist/spa}/src/components/ThreeDotsMenu.vue +0 -0
|
@@ -1,216 +0,0 @@
|
|
|
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";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
|
|
7
|
-
getPrimaryKey(resource: AdminForthResource): string {
|
|
8
|
-
for (const col of resource.dataSourceColumns) {
|
|
9
|
-
if (col.primaryKey) {
|
|
10
|
-
return col.name;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async getRecordByPrimaryKeyWithOriginalTypes(resource: AdminForthResource, id: string): Promise<any> {
|
|
16
|
-
const data = await this.getDataWithOriginalTypes({
|
|
17
|
-
resource,
|
|
18
|
-
limit: 1,
|
|
19
|
-
offset: 0,
|
|
20
|
-
sort: [],
|
|
21
|
-
filters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }],
|
|
22
|
-
});
|
|
23
|
-
return data.length > 0 ? data[0] : null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: {
|
|
27
|
-
resource: AdminForthResource,
|
|
28
|
-
limit: number,
|
|
29
|
-
offset: 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> {
|
|
37
|
-
throw new Error('Method not implemented.');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
discoverFields(resource: AdminForthResource): Promise<{ [key: string]: AdminForthResourceColumn; }> {
|
|
41
|
-
throw new Error('Method not implemented.');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
getFieldValue(field: AdminForthResourceColumn, value: any) {
|
|
45
|
-
throw new Error('Method not implemented.');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setFieldValue(field: AdminForthResourceColumn, value: any) {
|
|
49
|
-
throw new Error('Method not implemented.');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
|
|
53
|
-
throw new Error('Method not implemented.');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
createRecordOriginalValues({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
|
|
57
|
-
throw new Error('Method not implemented.');
|
|
58
|
-
}
|
|
59
|
-
|
|
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
|
-
|
|
71
|
-
return existingRecord.data.length > 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async createRecord({ resource, record, adminUser }: {
|
|
75
|
-
resource: AdminForthResource; record: any; adminUser: any;
|
|
76
|
-
}): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
|
|
77
|
-
// transform value using setFieldValue and call createRecordOriginalValues
|
|
78
|
-
const filledRecord = {...record};
|
|
79
|
-
const recordWithOriginalValues = {...record};
|
|
80
|
-
|
|
81
|
-
for (const col of resource.dataSourceColumns) {
|
|
82
|
-
if (col.fillOnCreate) {
|
|
83
|
-
if (filledRecord[col.name] === undefined) {
|
|
84
|
-
filledRecord[col.name] = col.fillOnCreate({
|
|
85
|
-
initialRecord: record,
|
|
86
|
-
adminUser
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let error: string | null = null;
|
|
94
|
-
await Promise.all(
|
|
95
|
-
resource.dataSourceColumns.map(async (col) => {
|
|
96
|
-
if (col.isUnique && !col.virtual && !error) {
|
|
97
|
-
const exists = await this.checkUnique(resource, col, recordWithOriginalValues[col.name]);
|
|
98
|
-
if (exists) {
|
|
99
|
-
error = `Record with ${col.name} ${recordWithOriginalValues[col.name]} already exists`;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
);
|
|
104
|
-
if (error) {
|
|
105
|
-
process.env.HEAVY_DEBUG && console.log('🪲🆕 check unique error', error);
|
|
106
|
-
return { error, ok: false };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record', recordWithOriginalValues);
|
|
110
|
-
await this.createRecordOriginalValues({ resource, record: recordWithOriginalValues });
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
ok: true,
|
|
114
|
-
createdRecord: recordWithOriginalValues,
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<void> {
|
|
119
|
-
throw new Error('Method not implemented.');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async updateRecord({ resource, recordId, newValues }: { resource: AdminForthResource; recordId: string; newValues: any; }): Promise<{ error?: string; ok: boolean; }> {
|
|
123
|
-
// transform value using setFieldValue and call updateRecordOriginalValues
|
|
124
|
-
const recordWithOriginalValues = {...newValues};
|
|
125
|
-
|
|
126
|
-
for (const field of Object.keys(newValues)) {
|
|
127
|
-
const col = resource.dataSourceColumns.find((col) => col.name == field);
|
|
128
|
-
// todo instead of throwing error, we can just not use setFieldValue here, and pass original value to updateRecordOriginalValues
|
|
129
|
-
// we might consider this because some users might not want to define all columns in resource with showIn:[], but still want to use them in hooks
|
|
130
|
-
if (!col) {
|
|
131
|
-
const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), field);
|
|
132
|
-
throw new Error(`
|
|
133
|
-
Update record received field '${field}' (with value ${newValues[field]}), but such column not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}
|
|
134
|
-
`);
|
|
135
|
-
}
|
|
136
|
-
recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
process.env.HEAVY_DEBUG && console.log(`🪲✏️ updating record id:${recordId}, values: ${JSON.stringify(recordWithOriginalValues)}`);
|
|
140
|
-
|
|
141
|
-
await this.updateRecordOriginalValues({ resource, recordId, newValues: recordWithOriginalValues });
|
|
142
|
-
|
|
143
|
-
return { ok: true };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
deleteRecord({ resource, recordId }: { resource: AdminForthResource; recordId: string; }): Promise<boolean> {
|
|
147
|
-
throw new Error('Method not implemented.');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
async getData({ resource, limit, offset, sort, filters, getTotals }: {
|
|
152
|
-
resource: AdminForthResource,
|
|
153
|
-
limit: number,
|
|
154
|
-
offset: number,
|
|
155
|
-
sort: { field: string, direction: AdminForthSortDirections }[],
|
|
156
|
-
filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
|
|
157
|
-
getTotals: boolean,
|
|
158
|
-
}): Promise<{ data: any[], total: number }> {
|
|
159
|
-
if (filters) {
|
|
160
|
-
filters.map((f) => {
|
|
161
|
-
const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
|
|
162
|
-
if (!fieldObj) {
|
|
163
|
-
const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
|
|
164
|
-
throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
165
|
-
}
|
|
166
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
167
|
-
f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
|
|
168
|
-
} else {
|
|
169
|
-
f.value = this.setFieldValue(fieldObj, f.value);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const promises: Promise<any>[] = [this.getDataWithOriginalTypes({ resource, limit, offset, sort, filters })];
|
|
175
|
-
if (getTotals) {
|
|
176
|
-
promises.push(this.getCount({ resource, filters }));
|
|
177
|
-
} else {
|
|
178
|
-
promises.push(Promise.resolve(undefined));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const [data, total] = await Promise.all(promises);
|
|
182
|
-
|
|
183
|
-
// call getFieldValue for each field
|
|
184
|
-
data.map((record) => {
|
|
185
|
-
for (const col of resource.dataSourceColumns) {
|
|
186
|
-
record[col.name] = this.getFieldValue(col, record[col.name]);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
return { data, total };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
|
|
194
|
-
const mm = await this.getMinMaxForColumnsWithOriginalTypes({ resource, columns });
|
|
195
|
-
const result = {};
|
|
196
|
-
for (const col of columns) {
|
|
197
|
-
result[col.name] = {
|
|
198
|
-
min: this.getFieldValue(col, mm[col.name].min),
|
|
199
|
-
max: this.getFieldValue(col, mm[col.name].max),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
return result;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any> {
|
|
206
|
-
return this.getRecordByPrimaryKeyWithOriginalTypes(resource, recordId).then((record) => {
|
|
207
|
-
const newRecord = {};
|
|
208
|
-
for (const col of resource.dataSourceColumns) {
|
|
209
|
-
newRecord[col.name] = this.getFieldValue(col, record[col.name]);
|
|
210
|
-
}
|
|
211
|
-
return newRecord;
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
|
|
2
|
-
import AdminForthBaseConnector from './baseConnector.js';
|
|
3
|
-
import dayjs from 'dayjs';
|
|
4
|
-
import { createClient } from '@clickhouse/client'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
|
|
9
|
-
|
|
10
|
-
client: any;
|
|
11
|
-
dbName: string;
|
|
12
|
-
url: string;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* url: http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2]
|
|
16
|
-
* @param param0
|
|
17
|
-
*/
|
|
18
|
-
constructor({ url }: { url: string }) {
|
|
19
|
-
super();
|
|
20
|
-
this.dbName = new URL(url).pathname.replace('/', '');
|
|
21
|
-
this.url = url;;
|
|
22
|
-
// create connection here
|
|
23
|
-
this.client = createClient({
|
|
24
|
-
url: url.replace('clickhouse://', 'http://'),
|
|
25
|
-
clickhouse_settings: {
|
|
26
|
-
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
27
|
-
date_time_input_format: 'best_effort',
|
|
28
|
-
|
|
29
|
-
// Recommended for cluster usage to avoid situations where a query processing error occurred after the response code,
|
|
30
|
-
// and HTTP headers were already sent to the client.
|
|
31
|
-
// See https://clickhouse.com/docs/en/interfaces/http/#response-buffering
|
|
32
|
-
wait_end_of_query: 1,
|
|
33
|
-
},
|
|
34
|
-
// log:{
|
|
35
|
-
// level: ClickHouseLogLevel.TRACE,
|
|
36
|
-
// }
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
|
|
42
|
-
const tableName = resource.table;
|
|
43
|
-
|
|
44
|
-
let rows;
|
|
45
|
-
try {
|
|
46
|
-
const q = await this.client.query({
|
|
47
|
-
query: `SELECT * FROM system.columns WHERE table = '${tableName}' and database = '${this.dbName}'`,
|
|
48
|
-
format: 'JSONEachRow',
|
|
49
|
-
});
|
|
50
|
-
rows = await q.json();
|
|
51
|
-
} catch (e) {
|
|
52
|
-
console.error(` 🛑Error connecting to datasource URL ${this.url}:`, e);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fieldTypes = {};
|
|
57
|
-
rows.forEach((row) => {
|
|
58
|
-
const field: any = {};
|
|
59
|
-
const baseType = row.type;
|
|
60
|
-
if (baseType.startsWith('Int') || baseType.startsWith('UInt')) {
|
|
61
|
-
field.type = AdminForthDataTypes.INTEGER;
|
|
62
|
-
} else if (baseType === 'FixedString' || baseType === 'String') {
|
|
63
|
-
field.type = AdminForthDataTypes.STRING;
|
|
64
|
-
// TODO
|
|
65
|
-
// const length = baseType.match(/\d+/g);
|
|
66
|
-
// field.maxLength = length ? parseInt(length[0]) : null;
|
|
67
|
-
} else if (baseType == 'UUID') {
|
|
68
|
-
field.type = AdminForthDataTypes.STRING;
|
|
69
|
-
} else if (baseType.startsWith('Decimal')) {
|
|
70
|
-
field.type = AdminForthDataTypes.DECIMAL;
|
|
71
|
-
const [precision, scale] = baseType.match(/\d+/g);
|
|
72
|
-
field.precision = parseInt(precision);
|
|
73
|
-
field.scale = parseInt(scale);
|
|
74
|
-
} else if (baseType.startsWith('Float')) {
|
|
75
|
-
field.type = AdminForthDataTypes.FLOAT;
|
|
76
|
-
} else if (baseType == 'DateTime64' || baseType == 'DateTime') {
|
|
77
|
-
field.type = AdminForthDataTypes.DATETIME;
|
|
78
|
-
} else if (baseType == 'Date' || baseType == 'Date64') {
|
|
79
|
-
field.type = AdminForthDataTypes.DATE;
|
|
80
|
-
} else if (baseType == 'Boolean') {
|
|
81
|
-
field.type = AdminForthDataTypes.BOOLEAN;
|
|
82
|
-
field._underlineType = 'boolean';
|
|
83
|
-
} else {
|
|
84
|
-
field.type = 'unknown'
|
|
85
|
-
}
|
|
86
|
-
field._underlineType = baseType;
|
|
87
|
-
field._baseTypeDebug = baseType;
|
|
88
|
-
field.required = row.notnull == 1;
|
|
89
|
-
field.primaryKey = row.pk == 1;
|
|
90
|
-
field.default = row.dflt_value;
|
|
91
|
-
fieldTypes[row.name] = field
|
|
92
|
-
});
|
|
93
|
-
return fieldTypes;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
getFieldValue(field: AdminForthResourceColumn, value: any): any {
|
|
97
|
-
if (field.type == AdminForthDataTypes.DATETIME) {
|
|
98
|
-
if (!value) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
|
|
102
|
-
return dayjs.unix(+value).toISOString();
|
|
103
|
-
} else if (field._underlineType.startsWith('DateTime')
|
|
104
|
-
|| field._underlineType.startsWith('String')
|
|
105
|
-
|| field._underlineType.startsWith('FixedString')) {
|
|
106
|
-
const v = dayjs(value).toISOString();
|
|
107
|
-
return v;
|
|
108
|
-
} else {
|
|
109
|
-
throw new Error(`AdminForth does not support row type: ${field._underlineType} for timestamps, use VARCHAR (with iso strings) or TIMESTAMP/INT (with unix timestamps). Issue in field "${field.name}"`);
|
|
110
|
-
}
|
|
111
|
-
} else if (field.type == AdminForthDataTypes.DATE) {
|
|
112
|
-
if (!value) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
return dayjs(value).toISOString().split('T')[0];
|
|
116
|
-
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
|
|
117
|
-
return !!value;
|
|
118
|
-
} else if (field.type == AdminForthDataTypes.JSON) {
|
|
119
|
-
if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
|
|
120
|
-
return JSON.parse(value);
|
|
121
|
-
} else {
|
|
122
|
-
console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return value;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
setFieldValue(field: AdminForthResourceColumn, value: any): any {
|
|
129
|
-
if (field.type == AdminForthDataTypes.DATETIME) {
|
|
130
|
-
if (!value) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
if (field._underlineType.startsWith('Int') || field._underlineType.startsWith('UInt')) {
|
|
134
|
-
// value is iso string now, convert to unix timestamp
|
|
135
|
-
return dayjs(value).unix();
|
|
136
|
-
} else if (field._underlineType.startsWith('DateTime')
|
|
137
|
-
|| field._underlineType.startsWith('String')
|
|
138
|
-
|| field._underlineType.startsWith('FixedString')) {
|
|
139
|
-
// value is iso string now, convert to unix timestamp
|
|
140
|
-
const iso = dayjs(value).toISOString();
|
|
141
|
-
return iso;
|
|
142
|
-
}
|
|
143
|
-
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
|
|
144
|
-
return value ? 1 : 0;
|
|
145
|
-
} else if (field.type == AdminForthDataTypes.JSON) {
|
|
146
|
-
// check underline type is text or string
|
|
147
|
-
if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
|
|
148
|
-
return JSON.stringify(value);
|
|
149
|
-
} else {
|
|
150
|
-
console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return value;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
OperatorsMap = {
|
|
158
|
-
[AdminForthFilterOperators.EQ]: '=',
|
|
159
|
-
[AdminForthFilterOperators.NE]: '!=',
|
|
160
|
-
[AdminForthFilterOperators.GT]: '>',
|
|
161
|
-
[AdminForthFilterOperators.LT]: '<',
|
|
162
|
-
[AdminForthFilterOperators.GTE]: '>=',
|
|
163
|
-
[AdminForthFilterOperators.LTE]: '<=',
|
|
164
|
-
[AdminForthFilterOperators.LIKE]: 'LIKE',
|
|
165
|
-
[AdminForthFilterOperators.ILIKE]: 'ILIKE',
|
|
166
|
-
[AdminForthFilterOperators.IN]: 'IN',
|
|
167
|
-
[AdminForthFilterOperators.NIN]: 'NOT IN',
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
SortDirectionsMap = {
|
|
171
|
-
[AdminForthSortDirections.asc]: 'ASC',
|
|
172
|
-
[AdminForthSortDirections.desc]: 'DESC',
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
whereClause(
|
|
176
|
-
resource: AdminForthResource,
|
|
177
|
-
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
178
|
-
): string {
|
|
179
|
-
return filters.length ? `WHERE ${filters.map((f, i) => {
|
|
180
|
-
const column = resource.dataSourceColumns.find((col) => col.name == f.field);
|
|
181
|
-
let placeholder = `{f${i}:${column._underlineType}}`;
|
|
182
|
-
let field = f.field;
|
|
183
|
-
let operator = this.OperatorsMap[f.operator];
|
|
184
|
-
if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
185
|
-
placeholder = `(${f.value.map((_, j) => `{p${i}_${j}:${
|
|
186
|
-
column._underlineType
|
|
187
|
-
}}`).join(', ')})`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return `${field} ${operator} ${placeholder}`
|
|
191
|
-
}).join(' AND ')}` : '';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
whereParams(
|
|
195
|
-
filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
|
|
196
|
-
): any {
|
|
197
|
-
const params = {};
|
|
198
|
-
filters.length ? filters.forEach((f, i) => {
|
|
199
|
-
// for arrays do set in map
|
|
200
|
-
const v = f.value;
|
|
201
|
-
|
|
202
|
-
if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
|
|
203
|
-
params[`f${i}`] = `%${v}%`;
|
|
204
|
-
} else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
|
|
205
|
-
v.forEach((_, j) => {
|
|
206
|
-
params[`p${i}_${j}`] = v[j];
|
|
207
|
-
});
|
|
208
|
-
} else {
|
|
209
|
-
params[`f${i}`] = v;
|
|
210
|
-
}
|
|
211
|
-
}) : [];
|
|
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
|
-
|
|
231
|
-
const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT {limit:Int} OFFSET {offset:Int}`;
|
|
235
|
-
const d = {
|
|
236
|
-
...params,
|
|
237
|
-
limit,
|
|
238
|
-
offset,
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const stmt = await this.client.query({
|
|
242
|
-
query: q,
|
|
243
|
-
format: 'JSONEachRow',
|
|
244
|
-
query_params: d,
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const rows = await stmt.json();
|
|
248
|
-
|
|
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
|
-
}
|
|
257
|
-
|
|
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(*) as count FROM ${tableName} ${where}`,
|
|
271
|
-
format: 'JSONEachRow',
|
|
272
|
-
query_params: d,
|
|
273
|
-
});
|
|
274
|
-
const countResp = await countQ.json()
|
|
275
|
-
return +countResp[0]['count'];
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
|
|
279
|
-
const tableName = resource.table;
|
|
280
|
-
const result = {};
|
|
281
|
-
await Promise.all(columns.map(async (col) => {
|
|
282
|
-
const stmt = await this.client.query({
|
|
283
|
-
query: `SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`,
|
|
284
|
-
format: 'JSONEachRow',
|
|
285
|
-
});
|
|
286
|
-
const rows = await stmt.json();
|
|
287
|
-
result[col.name] = {
|
|
288
|
-
min: rows[0].min,
|
|
289
|
-
max: rows[0].max,
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
}))
|
|
293
|
-
return result;
|
|
294
|
-
}
|
|
295
|
-
async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
|
|
296
|
-
const tableName = resource.table;
|
|
297
|
-
const columns = Object.keys(record);
|
|
298
|
-
await this.client.insert({
|
|
299
|
-
database: this.dbName,
|
|
300
|
-
table: tableName,
|
|
301
|
-
columns: columns,
|
|
302
|
-
values: [Object.values(record)],
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
|
|
307
|
-
const columnsWithPlaceholders = Object.keys(newValues).map((col) => {
|
|
308
|
-
return `${col} = {${col}:${resource.dataSourceColumns.find((c) => c.name == col)._underlineType}}`
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
await this.client.command(
|
|
312
|
-
{
|
|
313
|
-
query: `ALTER TABLE ${this.dbName}.${resource.table} UPDATE ${columnsWithPlaceholders.join(', ')} WHERE ${this.getPrimaryKey(resource)} = {recordId:${resource.dataSourceColumns.find((c) => c.primaryKey)._underlineType}}`,
|
|
314
|
-
query_params: { ...newValues, recordId },
|
|
315
|
-
}
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<boolean> {
|
|
320
|
-
const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
|
|
321
|
-
const res = await this.client.command(
|
|
322
|
-
{
|
|
323
|
-
query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${
|
|
324
|
-
pkColumn.name
|
|
325
|
-
} = {recordId:${pkColumn._underlineType}}`,
|
|
326
|
-
query_params: { recordId },
|
|
327
|
-
}
|
|
328
|
-
);
|
|
329
|
-
// todo test what is in res
|
|
330
|
-
return res;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
close() {
|
|
334
|
-
this.client.disconnect();
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export default ClickhouseConnector;
|