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.
Files changed (224) hide show
  1. package/dist/auth.d.ts +31 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +42 -56
  4. package/dist/auth.js.map +1 -0
  5. package/dist/basePlugin.d.ts +18 -0
  6. package/dist/basePlugin.d.ts.map +1 -0
  7. package/dist/basePlugin.js +1 -0
  8. package/dist/basePlugin.js.map +1 -0
  9. package/dist/dataConnectors/baseConnector.d.ts +94 -0
  10. package/dist/dataConnectors/baseConnector.d.ts.map +1 -0
  11. package/dist/dataConnectors/baseConnector.js +108 -122
  12. package/dist/dataConnectors/baseConnector.js.map +1 -0
  13. package/dist/dataConnectors/clickhouse.d.ts +92 -0
  14. package/dist/dataConnectors/clickhouse.d.ts.map +1 -0
  15. package/dist/dataConnectors/clickhouse.js +132 -149
  16. package/dist/dataConnectors/clickhouse.js.map +1 -0
  17. package/dist/dataConnectors/mongo.d.ts +93 -0
  18. package/dist/dataConnectors/mongo.d.ts.map +1 -0
  19. package/dist/dataConnectors/mongo.js +75 -101
  20. package/dist/dataConnectors/mongo.js.map +1 -0
  21. package/dist/dataConnectors/postgres.d.ts +71 -0
  22. package/dist/dataConnectors/postgres.d.ts.map +1 -0
  23. package/dist/dataConnectors/postgres.js +124 -143
  24. package/dist/dataConnectors/postgres.js.map +1 -0
  25. package/dist/dataConnectors/sqlite.d.ts +67 -0
  26. package/dist/dataConnectors/sqlite.d.ts.map +1 -0
  27. package/dist/dataConnectors/sqlite.js +113 -130
  28. package/dist/dataConnectors/sqlite.js.map +1 -0
  29. package/dist/index.d.ts +92 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +197 -217
  32. package/dist/index.js.map +1 -0
  33. package/dist/modules/codeInjector.d.ts +35 -0
  34. package/dist/modules/codeInjector.d.ts.map +1 -0
  35. package/dist/modules/codeInjector.js +480 -486
  36. package/dist/modules/codeInjector.js.map +1 -0
  37. package/dist/modules/configValidator.d.ts +12 -0
  38. package/dist/modules/configValidator.d.ts.map +1 -0
  39. package/dist/modules/configValidator.js +31 -22
  40. package/dist/modules/configValidator.js.map +1 -0
  41. package/dist/modules/operationalResource.d.ts +17 -0
  42. package/dist/modules/operationalResource.d.ts.map +1 -0
  43. package/dist/modules/operationalResource.js +50 -70
  44. package/dist/modules/operationalResource.js.map +1 -0
  45. package/dist/modules/restApi.d.ts +10 -0
  46. package/dist/modules/restApi.d.ts.map +1 -0
  47. package/dist/modules/restApi.js +104 -116
  48. package/dist/modules/restApi.js.map +1 -0
  49. package/dist/modules/styleGenerator.d.ts +9 -0
  50. package/dist/modules/styleGenerator.d.ts.map +1 -0
  51. package/dist/modules/styleGenerator.js +1 -0
  52. package/dist/modules/styleGenerator.js.map +1 -0
  53. package/dist/modules/styles.d.ts +96 -0
  54. package/dist/modules/styles.d.ts.map +1 -0
  55. package/dist/modules/styles.js +1 -0
  56. package/dist/modules/styles.js.map +1 -0
  57. package/dist/modules/utils.d.ts +39 -0
  58. package/dist/modules/utils.d.ts.map +1 -0
  59. package/dist/modules/utils.js +1 -0
  60. package/dist/modules/utils.js.map +1 -0
  61. package/dist/plugins/audit-log/types.d.ts +35 -0
  62. package/dist/plugins/audit-log/types.d.ts.map +1 -0
  63. package/dist/plugins/audit-log/types.js +2 -0
  64. package/dist/plugins/audit-log/types.js.map +1 -0
  65. package/dist/plugins/chat-gpt/types.d.ts +82 -0
  66. package/dist/plugins/chat-gpt/types.d.ts.map +1 -0
  67. package/dist/plugins/chat-gpt/types.js +2 -0
  68. package/dist/plugins/chat-gpt/types.js.map +1 -0
  69. package/dist/plugins/email-password-reset/types.d.ts +28 -0
  70. package/dist/plugins/email-password-reset/types.d.ts.map +1 -0
  71. package/dist/plugins/email-password-reset/types.js +2 -0
  72. package/dist/plugins/email-password-reset/types.js.map +1 -0
  73. package/dist/plugins/foreign-inline-list/types.d.ts +19 -0
  74. package/dist/plugins/foreign-inline-list/types.d.ts.map +1 -0
  75. package/dist/plugins/foreign-inline-list/types.js +2 -0
  76. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  77. package/dist/plugins/import-export/types.d.ts +3 -0
  78. package/dist/plugins/import-export/types.d.ts.map +1 -0
  79. package/dist/plugins/import-export/types.js +2 -0
  80. package/dist/plugins/import-export/types.js.map +1 -0
  81. package/dist/plugins/rich-editor/custom/async-queue.d.ts +8 -0
  82. package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +1 -0
  83. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  84. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  85. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +8 -0
  86. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +1 -0
  87. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  88. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  89. package/dist/plugins/rich-editor/types.d.ts +153 -0
  90. package/dist/plugins/rich-editor/types.d.ts.map +1 -0
  91. package/dist/plugins/rich-editor/types.js +16 -0
  92. package/dist/plugins/rich-editor/types.js.map +1 -0
  93. package/dist/plugins/two-factors-auth/types.d.ts +18 -0
  94. package/dist/plugins/two-factors-auth/types.d.ts.map +1 -0
  95. package/dist/plugins/two-factors-auth/types.js +2 -0
  96. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  97. package/dist/plugins/upload/types.d.ts +132 -0
  98. package/dist/plugins/upload/types.d.ts.map +1 -0
  99. package/dist/plugins/upload/types.js +2 -0
  100. package/dist/plugins/upload/types.js.map +1 -0
  101. package/dist/servers/express.d.ts +18 -0
  102. package/dist/servers/express.d.ts.map +1 -0
  103. package/dist/servers/express.js +30 -42
  104. package/dist/servers/express.js.map +1 -0
  105. package/dist/spa/index.html +2 -2
  106. package/dist/spa/package-lock.json +87 -1
  107. package/dist/spa/package.json +4 -1
  108. package/dist/spa/src/App.vue +154 -50
  109. package/dist/spa/src/components/AcceptModal.vue +1 -1
  110. package/dist/spa/src/components/Breadcrumbs.vue +1 -1
  111. package/dist/spa/src/components/CustomDatePicker.vue +1 -1
  112. package/dist/spa/src/components/CustomDateRangePicker.vue +1 -1
  113. package/dist/spa/src/components/CustomRangePicker.vue +9 -5
  114. package/dist/spa/src/components/Dropdown.vue +4 -4
  115. package/dist/spa/src/components/Filters.vue +2 -2
  116. package/dist/spa/src/components/MenuLink.vue +3 -0
  117. package/dist/spa/src/components/ResourceForm.vue +67 -36
  118. package/dist/spa/src/components/ResourceListTable.vue +216 -144
  119. package/dist/spa/src/components/SkeleteLoader.vue +4 -4
  120. package/dist/spa/src/components/Toast.vue +3 -2
  121. package/dist/spa/src/components/ValueRenderer.vue +81 -6
  122. package/dist/spa/src/composables/useFrontendApi.ts +1 -1
  123. package/dist/spa/src/composables/useStores.ts +18 -14
  124. package/dist/spa/src/index.scss +4 -0
  125. package/{spa → dist/spa}/src/renderers/CompactUUID.vue +4 -4
  126. package/{spa → dist/spa}/src/renderers/CountryFlag.vue +2 -2
  127. package/dist/spa/src/router/index.ts +4 -8
  128. package/dist/spa/src/spa_types/core.ts +2 -0
  129. package/dist/spa/src/stores/core.ts +6 -2
  130. package/dist/spa/src/stores/filters.ts +15 -10
  131. package/dist/spa/src/stores/toast.ts +22 -6
  132. package/dist/spa/src/types/AdminForthConfig.ts +340 -55
  133. package/dist/spa/src/types/FrontendAPI.ts +52 -30
  134. package/dist/spa/src/utils.ts +59 -2
  135. package/dist/spa/src/views/CreateView.vue +15 -4
  136. package/dist/spa/src/views/EditView.vue +20 -7
  137. package/dist/spa/src/views/ListView.vue +132 -38
  138. package/dist/spa/src/views/LoginView.vue +50 -18
  139. package/dist/spa/src/views/ShowView.vue +25 -15
  140. package/dist/types/AdminForthConfig.d.ts +1619 -0
  141. package/dist/types/AdminForthConfig.d.ts.map +1 -0
  142. package/dist/types/AdminForthConfig.js +1 -0
  143. package/dist/types/AdminForthConfig.js.map +1 -0
  144. package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
  145. package/dist/types/FrontendAPI.d.ts.map +1 -0
  146. package/dist/types/FrontendAPI.js +1 -0
  147. package/dist/types/FrontendAPI.js.map +1 -0
  148. package/package.json +16 -6
  149. package/auth.ts +0 -140
  150. package/basePlugin.ts +0 -70
  151. package/dataConnectors/baseConnector.ts +0 -216
  152. package/dataConnectors/clickhouse.ts +0 -338
  153. package/dataConnectors/mongo.ts +0 -202
  154. package/dataConnectors/postgres.ts +0 -306
  155. package/dataConnectors/sqlite.ts +0 -254
  156. package/index.ts +0 -428
  157. package/modules/codeInjector.ts +0 -736
  158. package/modules/configValidator.ts +0 -571
  159. package/modules/operationalResource.ts +0 -98
  160. package/modules/restApi.ts +0 -718
  161. package/modules/styleGenerator.ts +0 -55
  162. package/modules/styles.ts +0 -126
  163. package/modules/utils.ts +0 -472
  164. package/servers/express.ts +0 -259
  165. package/spa/.eslintrc.cjs +0 -14
  166. package/spa/README.md +0 -39
  167. package/spa/env.d.ts +0 -1
  168. package/spa/index.html +0 -23
  169. package/spa/package-lock.json +0 -4602
  170. package/spa/package.json +0 -51
  171. package/spa/postcss.config.js +0 -6
  172. package/spa/public/assets/favicon.png +0 -0
  173. package/spa/src/App.vue +0 -418
  174. package/spa/src/assets/base.css +0 -2
  175. package/spa/src/assets/logo.svg +0 -19
  176. package/spa/src/components/AcceptModal.vue +0 -45
  177. package/spa/src/components/Breadcrumbs.vue +0 -41
  178. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  179. package/spa/src/components/CustomDatePicker.vue +0 -176
  180. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  181. package/spa/src/components/CustomRangePicker.vue +0 -156
  182. package/spa/src/components/Dropdown.vue +0 -168
  183. package/spa/src/components/Filters.vue +0 -222
  184. package/spa/src/components/HelloWorld.vue +0 -17
  185. package/spa/src/components/MenuLink.vue +0 -27
  186. package/spa/src/components/ResourceForm.vue +0 -290
  187. package/spa/src/components/ResourceListTable.vue +0 -466
  188. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  189. package/spa/src/components/SkeleteLoader.vue +0 -23
  190. package/spa/src/components/Toast.vue +0 -78
  191. package/spa/src/components/ValueRenderer.vue +0 -114
  192. package/spa/src/components/icons/IconCalendar.vue +0 -5
  193. package/spa/src/components/icons/IconCommunity.vue +0 -7
  194. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  195. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  196. package/spa/src/components/icons/IconSupport.vue +0 -7
  197. package/spa/src/components/icons/IconTime.vue +0 -5
  198. package/spa/src/components/icons/IconTooling.vue +0 -19
  199. package/spa/src/composables/useFrontendApi.ts +0 -26
  200. package/spa/src/composables/useStores.ts +0 -131
  201. package/spa/src/index.scss +0 -31
  202. package/spa/src/main.ts +0 -18
  203. package/spa/src/router/index.ts +0 -59
  204. package/spa/src/spa_types/core.ts +0 -53
  205. package/spa/src/stores/core.ts +0 -148
  206. package/spa/src/stores/filters.ts +0 -27
  207. package/spa/src/stores/modal.ts +0 -48
  208. package/spa/src/stores/toast.ts +0 -31
  209. package/spa/src/stores/user.ts +0 -72
  210. package/spa/src/utils.ts +0 -160
  211. package/spa/src/views/CreateView.vue +0 -167
  212. package/spa/src/views/EditView.vue +0 -170
  213. package/spa/src/views/ListView.vue +0 -352
  214. package/spa/src/views/LoginView.vue +0 -192
  215. package/spa/src/views/ResourceParent.vue +0 -17
  216. package/spa/src/views/ShowView.vue +0 -186
  217. package/spa/tailwind.config.js +0 -17
  218. package/spa/tsconfig.app.json +0 -14
  219. package/spa/tsconfig.json +0 -11
  220. package/spa/tsconfig.node.json +0 -19
  221. package/spa/vite.config.ts +0 -56
  222. package/tsconfig.json +0 -112
  223. package/types/AdminForthConfig.ts +0 -1762
  224. /package/{spa → dist/spa}/src/components/ThreeDotsMenu.vue +0 -0
@@ -1,254 +0,0 @@
1
- import betterSqlite3 from 'better-sqlite3';
2
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, IAdminForthDataSourceConnector, AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig.js';
3
- import AdminForthBaseConnector from './baseConnector.js';
4
- import dayjs from 'dayjs';
5
-
6
- class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
7
-
8
- db: any;
9
-
10
- constructor({ url }: { url: string }) {
11
- super();
12
- // create connection here
13
- this.db = betterSqlite3(url.replace('sqlite://', ''));
14
- }
15
-
16
- async discoverFields(resource: AdminForthResource): Promise<{[key: string]: AdminForthResourceColumn}> {
17
- const tableName = resource.table;
18
- const stmt = this.db.prepare(`PRAGMA table_info(${tableName})`);
19
- const rows = await stmt.all();
20
- const fieldTypes = {};
21
- rows.forEach((row) => {
22
- const field: any = {};
23
- const baseType = row.type.toLowerCase();
24
- if (baseType == 'int') {
25
- field.type = AdminForthDataTypes.INTEGER;
26
- field._underlineType = 'int';
27
- } else if (baseType.includes('varchar(')) {
28
- field.type = AdminForthDataTypes.STRING;
29
- field._underlineType = 'varchar';
30
- const length = baseType.match(/\d+/);
31
- field.maxLength = length ? parseInt(length[0]) : null;
32
- } else if (baseType == 'text') {
33
- field.type = AdminForthDataTypes.TEXT;
34
- field._underlineType = 'text';
35
- } else if (baseType.includes('decimal(')) {
36
- field.type = AdminForthDataTypes.DECIMAL;
37
- field._underlineType = 'decimal';
38
- const [precision, scale] = baseType.match(/\d+/g);
39
- field.precision = parseInt(precision);
40
- field.scale = parseInt(scale);
41
- } else if (baseType == 'real') {
42
- field.type = AdminForthDataTypes.FLOAT; //8-byte IEEE floating point number. It
43
- field._underlineType = 'real';
44
- } else if (baseType == 'timestamp') {
45
- field.type = AdminForthDataTypes.DATETIME;
46
- field._underlineType = 'timestamp';
47
- } else if (baseType == 'boolean') {
48
- field.type = AdminForthDataTypes.BOOLEAN;
49
- field._underlineType = 'boolean';
50
- } else if (baseType == 'datetime') {
51
- field.type = AdminForthDataTypes.DATETIME;
52
- field._underlineType = 'datetime';
53
- } else {
54
- field.type = 'unknown'
55
- }
56
- field._baseTypeDebug = baseType;
57
- field.required = row.notnull == 1;
58
- field.primaryKey = row.pk == 1;
59
- field.default = row.dflt_value;
60
- fieldTypes[row.name] = field
61
- });
62
- return fieldTypes;
63
- }
64
-
65
- getFieldValue(field: AdminForthResourceColumn, value: any): any {
66
- if (field.type == AdminForthDataTypes.DATETIME) {
67
- if (!value) {
68
- return null;
69
- }
70
- if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
71
- return dayjs.unix(+value).toISOString();
72
- } else if (field._underlineType == 'varchar') {
73
- return dayjs(value).toISOString();
74
- } else if (field._underlineType == 'datetime') {
75
- return dayjs(value).toISOString();
76
- } else {
77
- 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}"`);
78
- }
79
-
80
- } else if (field.type == AdminForthDataTypes.DATE) {
81
- if (!value) {
82
- return null;
83
- }
84
- return dayjs(value).toISOString().split('T')[0];
85
-
86
- } else if (field.type == AdminForthDataTypes.BOOLEAN) {
87
- return !!value;
88
- } else if (field.type == AdminForthDataTypes.JSON) {
89
- if (field._underlineType == 'text' || field._underlineType == 'varchar') {
90
- return JSON.parse(value);
91
- } else {
92
- console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
93
- }
94
- }
95
-
96
- return value;
97
- }
98
-
99
- setFieldValue(field: AdminForthResourceColumn, value: any): any {
100
- if (field.type == AdminForthDataTypes.DATETIME) {
101
- if (!value) {
102
- return null;
103
- }
104
- if (field._underlineType == 'timestamp' || field._underlineType == 'int') {
105
- // value is iso string now, convert to unix timestamp
106
- return dayjs(value).unix();
107
- } else if (field._underlineType == 'varchar') {
108
- // value is iso string now, convert to unix timestamp
109
- return dayjs(value).toISOString();
110
- } else {
111
- return value;
112
- }
113
- } else if (field.type == AdminForthDataTypes.BOOLEAN) {
114
- return value ? 1 : 0;
115
- } else if (field.type == AdminForthDataTypes.JSON) {
116
- // check underline type is text or string
117
- if (field._underlineType == 'text' || field._underlineType == 'varchar') {
118
- return JSON.stringify(value);
119
- } else {
120
- console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
121
- }
122
- }
123
-
124
- return value;
125
- }
126
-
127
- OperatorsMap = {
128
- [AdminForthFilterOperators.EQ]: '=',
129
- [AdminForthFilterOperators.NE]: '!=',
130
- [AdminForthFilterOperators.GT]: '>',
131
- [AdminForthFilterOperators.LT]: '<',
132
- [AdminForthFilterOperators.GTE]: '>=',
133
- [AdminForthFilterOperators.LTE]: '<=',
134
- [AdminForthFilterOperators.LIKE]: 'LIKE',
135
- [AdminForthFilterOperators.ILIKE]: 'ILIKE',
136
- [AdminForthFilterOperators.IN]: 'IN',
137
- [AdminForthFilterOperators.NIN]: 'NOT IN',
138
- };
139
-
140
- SortDirectionsMap = {
141
- [AdminForthSortDirections.asc]: 'ASC',
142
- [AdminForthSortDirections.desc]: 'DESC',
143
- };
144
-
145
- whereClause(filters) {
146
- return filters.length ? `WHERE ${filters.map((f, i) => {
147
- let placeholder = '?';
148
- let field = f.field;
149
- let operator = this.OperatorsMap[f.operator];
150
- if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
151
- placeholder = `(${f.value.map(() => '?').join(', ')})`;
152
- } else if (f.operator == AdminForthFilterOperators.ILIKE) {
153
- placeholder = `LOWER(?)`;
154
- field = `LOWER(${f.field})`;
155
- operator = 'LIKE';
156
- }
157
-
158
- return `${field} ${operator} ${placeholder}`
159
- }).join(' AND ')}` : '';
160
- }
161
- whereParams(filters) {
162
- return filters.reduce((acc, f) => {
163
- if (f.operator == AdminForthFilterOperators.LIKE || f.operator == AdminForthFilterOperators.ILIKE) {
164
- acc.push(`%${f.value}%`);
165
- } else if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
166
- acc.push(...f.value);
167
- } else {
168
- acc.push(f.value);
169
- }
170
- return acc;
171
- }, []);
172
- }
173
-
174
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }): Promise<any[]> {
175
- const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
176
- const tableName = resource.table;
177
-
178
- const where = this.whereClause(filters);
179
-
180
- const filterValues = this.whereParams(filters);
181
-
182
- const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
183
-
184
- const q = `SELECT ${columns} FROM ${tableName} ${where} ${orderBy} LIMIT ? OFFSET ?`;
185
- const stmt = this.db.prepare(q);
186
- const d = [...filterValues, limit, offset];
187
-
188
- if (process.env.HEAVY_DEBUG) {
189
- console.log('🪲📜 SQLITE Q', q, 'params:', d);
190
- }
191
- const rows = await stmt.all(d);
192
-
193
- return rows.map((row) => {
194
- const newRow = {};
195
- for (const [key, value] of Object.entries(row)) {
196
- newRow[key] = value;
197
- }
198
- return newRow;
199
- })
200
- }
201
-
202
- async getCount({ resource, filters }) {
203
- const tableName = resource.table;
204
- const where = this.whereClause(filters);
205
- const filterValues = this.whereParams(filters);
206
- const totalStmt = this.db.prepare(`SELECT COUNT(*) FROM ${tableName} ${where}`);
207
- return totalStmt.get([...filterValues])['COUNT(*)'];
208
- }
209
-
210
- async getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }> {
211
- const tableName = resource.table;
212
- const result = {};
213
- await Promise.all(columns.map(async (col) => {
214
- const stmt = await this.db.prepare(`SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`);
215
- const { min, max } = stmt.get();
216
- result[col.name] = {
217
- min, max,
218
- };
219
- }))
220
- return result;
221
- }
222
-
223
- async createRecordOriginalValues({ resource, record }: { resource: AdminForthResource, record: any }) {
224
- const tableName = resource.table;
225
- const columns = Object.keys(record);
226
- const placeholders = columns.map(() => '?').join(', ');
227
- const values = columns.map((colName) => record[colName]);
228
- const q = this.db.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`)
229
- await q.run(values);
230
- }
231
-
232
- async updateRecordOriginalValues({ resource, recordId, newValues }: { resource: AdminForthResource, recordId: any, newValues: any }) {
233
- const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`);
234
- const values = [...Object.values(newValues), recordId];
235
-
236
- const q = this.db.prepare(
237
- `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`
238
- )
239
- process.env.HEAVY_DEBUG && console.log('🪲 SQLITE Query', `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`, 'params:', values);
240
- await q.run(values);
241
- }
242
-
243
- async deleteRecord({ resource, recordId }: { resource: AdminForthResource, recordId: any }): Promise<boolean> {
244
- const q = this.db.prepare(`DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`);
245
- const res = await q.run(recordId);
246
- return res.changes > 0;
247
- }
248
-
249
- close() {
250
- this.db.close();
251
- }
252
- }
253
-
254
- export default SQLiteConnector;
package/index.ts DELETED
@@ -1,428 +0,0 @@
1
-
2
- import AdminForthAuth from './auth.js';
3
- import MongoConnector from './dataConnectors/mongo.js';
4
- import PostgresConnector from './dataConnectors/postgres.js';
5
- import SQLiteConnector from './dataConnectors/sqlite.js';
6
- import CodeInjector from './modules/codeInjector.js';
7
- import ExpressServer from './servers/express.js';
8
- import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, getClinetIp } from './modules/utils.js';
9
- import {
10
- type AdminForthConfig,
11
- type IAdminForth,
12
- type IConfigValidator,
13
- IOperationalResource,
14
- AdminForthFilterOperators,
15
- AdminForthDataTypes, IHttpServer,
16
- BeforeSaveFunction,
17
- AfterSaveFunction,
18
- AdminUser,
19
- AdminForthResource,
20
- IAdminForthDataSourceConnectorBase,
21
- } from './types/AdminForthConfig.js';
22
- import AdminForthPlugin from './basePlugin.js';
23
- import ConfigValidator from './modules/configValidator.js';
24
- import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
25
- import ClickhouseConnector from './dataConnectors/clickhouse.js';
26
- import OperationalResource from './modules/operationalResource.js';
27
-
28
- // exports
29
- export * from './types/AdminForthConfig.js';
30
- export { interpretResource };
31
- export { AdminForthPlugin };
32
- export { suggestIfTypo, RateLimiter, getClinetIp };
33
-
34
-
35
- class AdminForth implements IAdminForth {
36
- static Types = AdminForthDataTypes;
37
-
38
- static Utils = {
39
- generatePasswordHash: async (password) => {
40
- return await AdminForthAuth.generatePasswordHash(password);
41
- },
42
-
43
- PASSWORD_VALIDATORS: {
44
- UP_LOW_NUM_SPECIAL: {
45
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*\\(\\)\\-_=\\+\\[\\]\\{\\}\\|;:\',\\.<>\\/\\?]).+$',
46
- message: 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character'
47
- },
48
- UP_LOW_NUM: {
49
- regExp: '^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).+$',
50
- message: 'Password must include at least one uppercase letter, one lowercase letter, and one number'
51
- },
52
- },
53
- EMAIL_VALIDATOR: {
54
- regExp: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$',
55
- message: 'Email is not valid, must be in format example@test.com',
56
- }
57
- }
58
-
59
- #defaultConfig = {
60
- deleteConfirmation: true,
61
- }
62
-
63
- config: AdminForthConfig;
64
- express: ExpressServer;
65
- auth: AdminForthAuth;
66
- codeInjector: CodeInjector;
67
- connectors: {
68
- [dataSourceId: string]: IAdminForthDataSourceConnectorBase,
69
- };
70
- connectorClasses: any;
71
- runningHotReload: boolean;
72
- activatedPlugins: Array<AdminForthPlugin>;
73
- configValidator: IConfigValidator;
74
- restApi: AdminForthRestAPI;
75
- operationalResources: {
76
- [resourceId: string]: IOperationalResource,
77
- }
78
- baseUrlSlashed: string;
79
-
80
- statuses: {
81
- dbDiscover: 'running' | 'done',
82
- }
83
-
84
- constructor(config: AdminForthConfig) {
85
- this.config = {...this.#defaultConfig,...config};
86
- this.codeInjector = new CodeInjector(this);
87
- this.configValidator = new ConfigValidator(this, this.config);
88
- this.restApi = new AdminForthRestAPI(this);
89
- this.activatedPlugins = [];
90
-
91
- this.configValidator.validateConfig();
92
- this.activatePlugins();
93
- this.configValidator.validateConfig(); // revalidate after plugins
94
-
95
- this.express = new ExpressServer(this);
96
- this.auth = new AdminForthAuth(this);
97
- this.connectors = {};
98
- this.statuses = {
99
- dbDiscover: 'running',
100
- };
101
-
102
- console.log(`🚀 AdminForth v${ADMINFORTH_VERSION} starting up`)
103
- }
104
-
105
- activatePlugins() {
106
- process.env.HEAVY_DEBUG && console.log('🔌🔌🔌 Activating plugins');
107
- const allPluginInstances = [];
108
- for (let resource of this.config.resources) {
109
- for (let pluginInstance of resource.plugins || []) {
110
- allPluginInstances.push({pi: pluginInstance, resource});
111
- }
112
- }
113
- allPluginInstances.sort(({pi: a}, {pi: b}) => a.activationOrder - b.activationOrder);
114
- allPluginInstances.forEach(
115
- ({pi: pluginInstance, resource}) => {
116
- pluginInstance.modifyResourceConfig(this, resource);
117
- const plugin = this.activatedPlugins.find((p) => p.pluginInstanceId === pluginInstance.pluginInstanceId);
118
- if (plugin) {
119
- process.env.HEAVY_DEBUG && console.log(`Current plugin pluginInstance.pluginInstanceId ${pluginInstance.pluginInstanceId}`);
120
-
121
- throw new Error(`Attempt to activate Plugin ${pluginInstance.constructor.name} second time for same resource, but plugin does not support it.
122
- To support multiple plugin instance pre one resource, plugin should return unique string values for each installation from instanceUniqueRepresentation`);
123
- }
124
- this.activatedPlugins.push(pluginInstance);
125
- }
126
- );
127
- }
128
-
129
- getPluginsByClassName<T>(className: string): T[] {
130
- const plugins = this.activatedPlugins.filter((plugin) => plugin.className === className);
131
- return plugins as T[];
132
- }
133
-
134
- getPluginByClassName<T>(className: string): T {
135
- const plugins = this.getPluginsByClassName(className);
136
- if (plugins.length > 1) {
137
- throw new Error(`Multiple plugins with className ${className} found. Use getPluginsByClassName instead`);
138
- }
139
- if (plugins.length === 0) {
140
- const similar = suggestIfTypo(this.activatedPlugins.map((p) => p.className), className);
141
- throw new Error(`Plugin with className ${className} not found. ${similar ? `Did you mean ${similar}?` : ''}`);
142
- }
143
- return plugins[0] as T;
144
- }
145
-
146
- async discoverDatabases() {
147
- this.statuses.dbDiscover = 'running';
148
- this.connectorClasses = {
149
- 'sqlite': SQLiteConnector,
150
- 'postgres': PostgresConnector,
151
- 'mongodb': MongoConnector,
152
- 'clickhouse': ClickhouseConnector,
153
- };
154
- if (!this.config.databaseConnectors) {
155
- this.config.databaseConnectors = {...this.connectorClasses};
156
- }
157
- this.config.dataSources.forEach((ds) => {
158
- const dbType = ds.url.split(':')[0];
159
- if (!this.config.databaseConnectors[dbType]) {
160
- throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
161
- }
162
- this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
163
- });
164
-
165
- await Promise.all(this.config.resources.map(async (res) => {
166
- if (!this.connectors[res.dataSource]) {
167
- const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
168
- throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
169
- ? `. Did you mean '${similar}'?` : 'Available dataSources: '+Object.keys(this.connectors).join(', ')}`
170
- );
171
- }
172
- const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
173
- if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
174
- throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
175
- }
176
- if (fieldTypes === null) {
177
- console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
178
- return;
179
- }
180
- if (!res.columns) {
181
- res.columns = Object.keys(fieldTypes).map((name) => ({ name }));
182
- }
183
-
184
- res.columns.forEach((col, i) => {
185
- if (!fieldTypes[col.name] && !col.virtual) {
186
- const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
187
- throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
188
- }
189
- // first find discovered values, but allow override
190
- res.columns[i] = { ...fieldTypes[col.name], ...col };
191
- });
192
-
193
- this.configValidator.postProcessAfterDiscover(res);
194
-
195
- // check if primaryKey column is present
196
- if (!res.columns.some((col) => col.primaryKey)) {
197
- throw new Error(`Resource '${res.table}' has no column defined or auto-discovered. Please set 'primaryKey: true' in a columns which has unique value for each record and index`);
198
- }
199
-
200
- }));
201
-
202
- this.statuses.dbDiscover = 'done';
203
-
204
- this.operationalResources = {};
205
- this.config.resources.forEach((resource) => {
206
- this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
207
- });
208
-
209
- // console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
210
- }
211
-
212
- async bundleNow({ hotReload=false }) {
213
- await this.codeInjector.bundleNow({ hotReload });
214
- }
215
-
216
- async getUserByPk(pk: string) {
217
- // if database discovery is not done, throw
218
- if (this.statuses.dbDiscover !== 'done') {
219
- if (this.statuses.dbDiscover === 'running') {
220
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
221
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
222
- }
223
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
224
- 'Call admin.discoverDatabases() first and await it before using data API');
225
- }
226
-
227
- const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
228
- if (!resource) {
229
- const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
230
- throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
231
- `Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`
232
- );
233
- }
234
- const users = await this.connectors[resource.dataSource].getData({
235
- resource,
236
- filters: [
237
- { field: resource.columns.find((col) => col.primaryKey).name, operator: AdminForthFilterOperators.EQ, value: pk },
238
- ],
239
- limit: 1,
240
- offset: 0,
241
- sort: [],
242
- });
243
- return users.data[0] || null;
244
- }
245
-
246
- async createResourceRecord(
247
- { resource, record, adminUser }:
248
- { resource: AdminForthResource, record: any, adminUser: AdminUser }
249
- ): Promise<{ error?: string, createdRecord?: any }> {
250
-
251
- // execute hook if needed
252
- for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
253
- const resp = await hook({ recordId: undefined, resource, record, adminUser });
254
- if (!resp || (!resp.ok && !resp.error)) {
255
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
256
- }
257
-
258
- if (resp.error) {
259
- return { error: resp.error };
260
- }
261
- }
262
-
263
- // remove virtual columns from record
264
- for (const column of resource.columns.filter((col) => col.virtual)) {
265
- if (record[column.name]) {
266
- delete record[column.name];
267
- }
268
- }
269
- const connector = this.connectors[resource.dataSource];
270
- process.env.HEAVY_DEBUG && console.log('🪲🆕 creating record createResourceRecord', record);
271
- const { error, createdRecord } = await connector.createRecord({ resource, record, adminUser });
272
- if ( error ) {
273
- return { error };
274
- }
275
-
276
- const primaryKey = record[resource.columns.find((col) => col.primaryKey).name];
277
-
278
- // execute hook if needed
279
- for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
280
- process.env.HEAVY_DEBUG && console.log('🪲 Hook afterSave', hook);
281
- const resp = await hook({
282
- recordId: primaryKey,
283
- resource,
284
- record: createdRecord,
285
- adminUser
286
- });
287
-
288
- if (!resp || (!resp.ok && !resp.error)) {
289
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
290
- }
291
-
292
- if (resp.error) {
293
- return { error: resp.error };
294
- }
295
- }
296
-
297
- return { error, createdRecord };
298
- }
299
-
300
- /**
301
- * record is partial record with only changed fields
302
- */
303
- async updateResourceRecord(
304
- { resource, recordId, record, oldRecord, adminUser }:
305
- { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser }
306
- ): Promise<{ error?: string }> {
307
-
308
- // execute hook if needed
309
- for (const hook of listify(resource.hooks?.edit?.beforeSave as BeforeSaveFunction[])) {
310
- const resp = await hook({
311
- recordId,
312
- resource,
313
- record,
314
- oldRecord,
315
- adminUser
316
- });
317
- if (!resp || (!resp.ok && !resp.error)) {
318
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
319
- }
320
- if (resp.error) {
321
- return { error: resp.error };
322
- }
323
- }
324
- const newValues = {};
325
- const connector = this.connectors[resource.dataSource];
326
-
327
- for (const recordField in record) {
328
- if (record[recordField] !== oldRecord[recordField]) {
329
- // leave only changed fields to reduce data transfer/modifications in db
330
- const column = resource.columns.find((col) => col.name === recordField);
331
- if (!column || !column.virtual) {
332
- // exclude virtual columns
333
- newValues[recordField] = record[recordField];
334
- }
335
- }
336
- }
337
-
338
- if (Object.keys(newValues).length > 0) {
339
- await connector.updateRecord({ resource, recordId, newValues });
340
- }
341
-
342
- // execute hook if needed
343
- for (const hook of listify(resource.hooks?.edit?.afterSave as AfterSaveFunction[])) {
344
- const resp = await hook({
345
- resource,
346
- record,
347
- adminUser,
348
- oldRecord,
349
- recordId,
350
- });
351
- if (!resp || (!resp.ok && !resp.error)) {
352
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
353
- }
354
- if (resp.error) {
355
- return { error: resp.error };
356
- }
357
- }
358
-
359
- return { error: null };
360
- }
361
-
362
- async deleteResourceRecord(
363
- { resource, recordId, adminUser, record }:
364
- { resource: AdminForthResource, recordId: any, adminUser: AdminUser, record: any }
365
- ): Promise<{ error?: string }> {
366
- // execute hook if needed
367
- for (const hook of listify(resource.hooks?.delete?.beforeSave as BeforeSaveFunction[])) {
368
- const resp = await hook({
369
- resource,
370
- record,
371
- adminUser,
372
- recordId,
373
- });
374
- if (!resp || (!resp.ok && !resp.error)) {
375
- throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
376
- }
377
-
378
- if (resp.error) {
379
- return { error: resp.error };
380
- }
381
- }
382
-
383
- const connector = this.connectors[resource.dataSource];
384
- await connector.deleteRecord({ resource, recordId});
385
-
386
- // execute hook if needed
387
- for (const hook of listify(resource.hooks?.delete?.afterSave as BeforeSaveFunction[])) {
388
- const resp = await hook({
389
- resource,
390
- record,
391
- adminUser,
392
- recordId,
393
- });
394
- if (!resp || (!resp.ok && !resp.error)) {
395
- throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
396
- }
397
-
398
- if (resp.error) {
399
- return { error: resp.error };
400
- }
401
- }
402
-
403
- return { error: null };
404
- }
405
-
406
- resource(resourceId: string): IOperationalResource {
407
- if (this.statuses.dbDiscover !== 'done') {
408
- if (this.statuses.dbDiscover === 'running') {
409
- throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
410
- 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
411
- } else {
412
- throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
413
- 'Call admin.discoverDatabases() first and await it before using data API');
414
- }
415
- }
416
- if (!this.operationalResources[resourceId]) {
417
- const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
418
- throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
419
- }
420
- return this.operationalResources[resourceId];
421
- }
422
-
423
- setupEndpoints(server: IHttpServer) {
424
- this.restApi.registerEndpoints(server);
425
- }
426
- }
427
-
428
- export default AdminForth;