adminforth 1.3.54-next.3 → 1.3.54-next.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/auth.js +42 -56
  2. package/dist/auth.js.map +1 -0
  3. package/dist/basePlugin.js +1 -0
  4. package/dist/basePlugin.js.map +1 -0
  5. package/dist/dataConnectors/baseConnector.js +108 -122
  6. package/dist/dataConnectors/baseConnector.js.map +1 -0
  7. package/dist/dataConnectors/clickhouse.js +132 -150
  8. package/dist/dataConnectors/clickhouse.js.map +1 -0
  9. package/dist/dataConnectors/mongo.js +75 -101
  10. package/dist/dataConnectors/mongo.js.map +1 -0
  11. package/dist/dataConnectors/postgres.js +124 -143
  12. package/dist/dataConnectors/postgres.js.map +1 -0
  13. package/dist/dataConnectors/sqlite.js +113 -130
  14. package/dist/dataConnectors/sqlite.js.map +1 -0
  15. package/dist/index.js +197 -217
  16. package/dist/index.js.map +1 -0
  17. package/dist/modules/codeInjector.js +480 -486
  18. package/dist/modules/codeInjector.js.map +1 -0
  19. package/dist/modules/configValidator.js +31 -22
  20. package/dist/modules/configValidator.js.map +1 -0
  21. package/dist/modules/operationalResource.js +50 -70
  22. package/dist/modules/operationalResource.js.map +1 -0
  23. package/dist/modules/restApi.js +104 -116
  24. package/dist/modules/restApi.js.map +1 -0
  25. package/dist/modules/styleGenerator.js +1 -0
  26. package/dist/modules/styleGenerator.js.map +1 -0
  27. package/dist/modules/styles.js +1 -0
  28. package/dist/modules/styles.js.map +1 -0
  29. package/dist/modules/utils.js +1 -0
  30. package/dist/modules/utils.js.map +1 -0
  31. package/dist/plugins/audit-log/types.js +2 -0
  32. package/dist/plugins/audit-log/types.js.map +1 -0
  33. package/dist/plugins/chat-gpt/types.js +2 -0
  34. package/dist/plugins/chat-gpt/types.js.map +1 -0
  35. package/dist/plugins/email-password-reset/types.js +2 -0
  36. package/dist/plugins/email-password-reset/types.js.map +1 -0
  37. package/dist/plugins/foreign-inline-list/types.js +2 -0
  38. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  39. package/dist/plugins/import-export/types.js +2 -0
  40. package/dist/plugins/import-export/types.js.map +1 -0
  41. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  42. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  43. package/dist/plugins/rich-editor/dist/async-queue.js +41 -0
  44. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  45. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  46. package/dist/plugins/rich-editor/types.js +16 -0
  47. package/dist/plugins/rich-editor/types.js.map +1 -0
  48. package/dist/plugins/two-factors-auth/types.js +2 -0
  49. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  50. package/dist/plugins/upload/types.js +2 -0
  51. package/dist/plugins/upload/types.js.map +1 -0
  52. package/dist/servers/express.js +30 -42
  53. package/dist/servers/express.js.map +1 -0
  54. package/dist/types/AdminForthConfig.js +1 -0
  55. package/dist/types/AdminForthConfig.js.map +1 -0
  56. package/dist/types/FrontendAPI.js +1 -0
  57. package/dist/types/FrontendAPI.js.map +1 -0
  58. package/package.json +8 -5
  59. package/auth.ts +0 -140
  60. package/basePlugin.ts +0 -70
  61. package/dataConnectors/baseConnector.ts +0 -216
  62. package/dataConnectors/clickhouse.ts +0 -341
  63. package/dataConnectors/mongo.ts +0 -202
  64. package/dataConnectors/postgres.ts +0 -306
  65. package/dataConnectors/sqlite.ts +0 -254
  66. package/index.ts +0 -428
  67. package/modules/codeInjector.ts +0 -736
  68. package/modules/configValidator.ts +0 -571
  69. package/modules/operationalResource.ts +0 -98
  70. package/modules/restApi.ts +0 -718
  71. package/modules/styleGenerator.ts +0 -55
  72. package/modules/styles.ts +0 -126
  73. package/modules/utils.ts +0 -472
  74. package/servers/express.ts +0 -259
  75. package/spa/.eslintrc.cjs +0 -14
  76. package/spa/README.md +0 -39
  77. package/spa/env.d.ts +0 -1
  78. package/spa/index.html +0 -23
  79. package/spa/package-lock.json +0 -4573
  80. package/spa/package.json +0 -49
  81. package/spa/postcss.config.js +0 -6
  82. package/spa/public/assets/favicon.png +0 -0
  83. package/spa/src/App.vue +0 -418
  84. package/spa/src/assets/base.css +0 -2
  85. package/spa/src/assets/logo.svg +0 -19
  86. package/spa/src/components/AcceptModal.vue +0 -45
  87. package/spa/src/components/Breadcrumbs.vue +0 -41
  88. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  89. package/spa/src/components/CustomDatePicker.vue +0 -176
  90. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  91. package/spa/src/components/CustomRangePicker.vue +0 -156
  92. package/spa/src/components/Dropdown.vue +0 -168
  93. package/spa/src/components/Filters.vue +0 -222
  94. package/spa/src/components/HelloWorld.vue +0 -17
  95. package/spa/src/components/MenuLink.vue +0 -27
  96. package/spa/src/components/ResourceForm.vue +0 -290
  97. package/spa/src/components/ResourceListTable.vue +0 -460
  98. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  99. package/spa/src/components/SkeleteLoader.vue +0 -23
  100. package/spa/src/components/ThreeDotsMenu.vue +0 -43
  101. package/spa/src/components/Toast.vue +0 -78
  102. package/spa/src/components/ValueRenderer.vue +0 -114
  103. package/spa/src/components/icons/IconCalendar.vue +0 -5
  104. package/spa/src/components/icons/IconCommunity.vue +0 -7
  105. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  106. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  107. package/spa/src/components/icons/IconSupport.vue +0 -7
  108. package/spa/src/components/icons/IconTime.vue +0 -5
  109. package/spa/src/components/icons/IconTooling.vue +0 -19
  110. package/spa/src/composables/useFrontendApi.ts +0 -26
  111. package/spa/src/composables/useStores.ts +0 -131
  112. package/spa/src/index.scss +0 -31
  113. package/spa/src/main.ts +0 -18
  114. package/spa/src/router/index.ts +0 -59
  115. package/spa/src/spa_types/core.ts +0 -53
  116. package/spa/src/stores/core.ts +0 -148
  117. package/spa/src/stores/filters.ts +0 -27
  118. package/spa/src/stores/modal.ts +0 -48
  119. package/spa/src/stores/toast.ts +0 -31
  120. package/spa/src/stores/user.ts +0 -72
  121. package/spa/src/utils.ts +0 -149
  122. package/spa/src/views/CreateView.vue +0 -167
  123. package/spa/src/views/EditView.vue +0 -170
  124. package/spa/src/views/ListView.vue +0 -279
  125. package/spa/src/views/LoginView.vue +0 -192
  126. package/spa/src/views/ResourceParent.vue +0 -17
  127. package/spa/src/views/ShowView.vue +0 -186
  128. package/spa/tailwind.config.js +0 -17
  129. package/spa/tsconfig.app.json +0 -14
  130. package/spa/tsconfig.json +0 -11
  131. package/spa/tsconfig.node.json +0 -19
  132. package/spa/vite.config.ts +0 -56
  133. package/tsconfig.json +0 -112
  134. package/types/AdminForthConfig.ts +0 -1762
  135. package/types/FrontendAPI.ts +0 -143
package/basePlugin.ts DELETED
@@ -1,70 +0,0 @@
1
- import { AdminForthResource, IAdminForthPlugin, IAdminForth } from './types/AdminForthConfig.js';
2
- import { getComponentNameFromPath } from './modules/utils.js';
3
- import { currentFileDir } from './modules/utils.js';
4
- import path from 'path';
5
- import fs from 'fs';
6
-
7
- import crypto from 'crypto';
8
-
9
-
10
- export default class AdminForthPlugin implements IAdminForthPlugin {
11
-
12
- adminforth: IAdminForth;
13
- pluginDir: string;
14
- customFolderName: string = 'custom';
15
- pluginInstanceId: string;
16
- customFolderPath: string;
17
- pluginOptions: any;
18
- resourceConfig: AdminForthResource;
19
- className: string;
20
- activationOrder: number = 0;
21
-
22
- constructor(pluginOptions: any, metaUrl: string) {
23
- // set up plugin here
24
- this.pluginDir = currentFileDir(metaUrl);
25
- this.customFolderPath = path.join(this.pluginDir, this.customFolderName);
26
- this.pluginOptions = pluginOptions;
27
- console.log(`🪲 🪲 🪲 🪲 🪲 🪲 AdminForthPlugin.constructor`, this.constructor.name);
28
- this.className = this.constructor.name;
29
- }
30
-
31
- setupEndpoints(server: any) {
32
-
33
- }
34
-
35
- instanceUniqueRepresentation(pluginOptions: any) : string {
36
- return 'non-uniquely-identified';
37
- }
38
-
39
- modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
40
- this.resourceConfig = resourceConfig;
41
- const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
42
-
43
- const seed = `af_pl_${this.constructor.name}_${resourceConfig.resourceId}_${uniqueness}`;
44
- this.pluginInstanceId = crypto.createHash('sha256').update(
45
- seed
46
- ).digest('hex')
47
- process.env.HEAVY_DEBUG && console.log(`🪲 AdminForthPlugin.modifyResourceConfig`, seed, 'id', this.pluginInstanceId);
48
- this.adminforth = adminforth;
49
- }
50
-
51
- componentPath(componentFile: string) {
52
- const key = `@@/plugins/${this.constructor.name}/${componentFile}`;
53
- const componentName = getComponentNameFromPath(key);
54
-
55
- if (!this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath]) {
56
- this.adminforth.codeInjector.srcFoldersToSync[this.customFolderPath] = `./plugins/${this.constructor.name}/`;
57
- }
58
-
59
- if (!this.adminforth.codeInjector.allComponentNames[key]) {
60
- const absSrcPath = path.join(this.customFolderPath, componentFile);
61
- if (!fs.existsSync(absSrcPath)) {
62
- throw new Error(`Plugin "${this.constructor.name}" tried to use file as component which does not exist at "${absSrcPath}"`);
63
- }
64
- this.adminforth.codeInjector.allComponentNames[key] = componentName;
65
- }
66
-
67
- return key;
68
- }
69
-
70
- }
@@ -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,341 +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&param2=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
-
270
- const countQ = await this.client.query({
271
- query: `SELECT COUNT(*) FROM ${tableName} ${where}`,
272
- format: 'JSONEachRow',
273
- query_params: d,
274
- });
275
- const countResp = await countQ.json()
276
- process.env.HEAVY_DEBUG && console.log('🪲🪲⏲️ Clickhouse COUNT', `SELECT COUNT(*) FROM ${tableName} ${where}`, d, 'resp', countResp);
277
-
278
- return countResp[0]['COUNT()'];
279
- }
280
-
281
- async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
282
- const tableName = resource.table;
283
- const result = {};
284
- await Promise.all(columns.map(async (col) => {
285
- const stmt = await this.client.query({
286
- query: `SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`,
287
- format: 'JSONEachRow',
288
- });
289
- const rows = await stmt.json();
290
- result[col.name] = {
291
- min: rows[0].min,
292
- max: rows[0].max,
293
- };
294
-
295
- }))
296
- return result;
297
- }
298
- async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
299
- const tableName = resource.table;
300
- const columns = Object.keys(record);
301
- await this.client.insert({
302
- database: this.dbName,
303
- table: tableName,
304
- columns: columns,
305
- values: [Object.values(record)],
306
- });
307
- }
308
-
309
- async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
310
- const columnsWithPlaceholders = Object.keys(newValues).map((col) => {
311
- return `${col} = {${col}:${resource.dataSourceColumns.find((c) => c.name == col)._underlineType}}`
312
- });
313
-
314
- await this.client.command(
315
- {
316
- query: `ALTER TABLE ${this.dbName}.${resource.table} UPDATE ${columnsWithPlaceholders.join(', ')} WHERE ${this.getPrimaryKey(resource)} = {recordId:${resource.dataSourceColumns.find((c) => c.primaryKey)._underlineType}}`,
317
- query_params: { ...newValues, recordId },
318
- }
319
- );
320
- }
321
-
322
- async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<boolean> {
323
- const pkColumn = resource.dataSourceColumns.find((col) => col.primaryKey);
324
- const res = await this.client.command(
325
- {
326
- query: `ALTER TABLE ${this.dbName}.${resource.table} DELETE WHERE ${
327
- pkColumn.name
328
- } = {recordId:${pkColumn._underlineType}}`,
329
- query_params: { recordId },
330
- }
331
- );
332
- // todo test what is in res
333
- return res;
334
- }
335
-
336
- close() {
337
- this.client.disconnect();
338
- }
339
- }
340
-
341
- export default ClickhouseConnector;