digitaltwin-core 0.14.2 → 1.0.0

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 (227) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +494 -359
  3. package/dist/auth/apisix_parser.d.ts +141 -0
  4. package/dist/auth/apisix_parser.d.ts.map +1 -0
  5. package/dist/auth/apisix_parser.js +161 -0
  6. package/dist/auth/apisix_parser.js.map +1 -0
  7. package/dist/auth/auth_config.d.ts +126 -0
  8. package/dist/auth/auth_config.d.ts.map +1 -0
  9. package/dist/auth/auth_config.js +169 -0
  10. package/dist/auth/auth_config.js.map +1 -0
  11. package/dist/auth/auth_provider.d.ts +118 -0
  12. package/dist/auth/auth_provider.d.ts.map +1 -0
  13. package/dist/auth/auth_provider.js +8 -0
  14. package/dist/auth/auth_provider.js.map +1 -0
  15. package/dist/auth/auth_provider_factory.d.ts +91 -0
  16. package/dist/auth/auth_provider_factory.d.ts.map +1 -0
  17. package/dist/auth/auth_provider_factory.js +146 -0
  18. package/dist/auth/auth_provider_factory.js.map +1 -0
  19. package/dist/auth/index.d.ts +8 -0
  20. package/dist/auth/index.d.ts.map +1 -0
  21. package/dist/auth/index.js +7 -0
  22. package/dist/auth/index.js.map +1 -0
  23. package/dist/auth/providers/gateway_auth_provider.d.ts +78 -0
  24. package/dist/auth/providers/gateway_auth_provider.d.ts.map +1 -0
  25. package/dist/auth/providers/gateway_auth_provider.js +109 -0
  26. package/dist/auth/providers/gateway_auth_provider.js.map +1 -0
  27. package/dist/auth/providers/index.d.ts +4 -0
  28. package/dist/auth/providers/index.d.ts.map +1 -0
  29. package/dist/auth/providers/index.js +4 -0
  30. package/dist/auth/providers/index.js.map +1 -0
  31. package/dist/auth/providers/jwt_auth_provider.d.ts +91 -0
  32. package/dist/auth/providers/jwt_auth_provider.d.ts.map +1 -0
  33. package/dist/auth/providers/jwt_auth_provider.js +204 -0
  34. package/dist/auth/providers/jwt_auth_provider.js.map +1 -0
  35. package/dist/auth/providers/no_auth_provider.d.ts +61 -0
  36. package/dist/auth/providers/no_auth_provider.d.ts.map +1 -0
  37. package/dist/auth/providers/no_auth_provider.js +76 -0
  38. package/dist/auth/providers/no_auth_provider.js.map +1 -0
  39. package/dist/auth/types.d.ts +100 -0
  40. package/dist/auth/types.d.ts.map +1 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/auth/types.js.map +1 -0
  43. package/dist/auth/user_service.d.ts +86 -0
  44. package/dist/auth/user_service.d.ts.map +1 -0
  45. package/dist/auth/user_service.js +237 -0
  46. package/dist/auth/user_service.js.map +1 -0
  47. package/dist/components/assets_manager.d.ts +662 -0
  48. package/dist/components/assets_manager.d.ts.map +1 -0
  49. package/dist/components/assets_manager.js +1537 -0
  50. package/dist/components/assets_manager.js.map +1 -0
  51. package/dist/components/async_upload.d.ts +20 -0
  52. package/dist/components/async_upload.d.ts.map +1 -0
  53. package/dist/components/async_upload.js +10 -0
  54. package/dist/components/async_upload.js.map +1 -0
  55. package/dist/components/collector.d.ts +203 -0
  56. package/dist/components/collector.d.ts.map +1 -0
  57. package/dist/components/collector.js +214 -0
  58. package/dist/components/collector.js.map +1 -0
  59. package/dist/components/custom_table_manager.d.ts +503 -0
  60. package/dist/components/custom_table_manager.d.ts.map +1 -0
  61. package/dist/components/custom_table_manager.js +1023 -0
  62. package/dist/components/custom_table_manager.js.map +1 -0
  63. package/dist/components/global_assets_handler.d.ts +63 -0
  64. package/dist/components/global_assets_handler.d.ts.map +1 -0
  65. package/dist/components/global_assets_handler.js +127 -0
  66. package/dist/components/global_assets_handler.js.map +1 -0
  67. package/dist/components/handler.d.ts +104 -0
  68. package/dist/components/handler.d.ts.map +1 -0
  69. package/dist/components/handler.js +110 -0
  70. package/dist/components/handler.js.map +1 -0
  71. package/dist/components/harvester.d.ts +182 -0
  72. package/dist/components/harvester.d.ts.map +1 -0
  73. package/dist/components/harvester.js +406 -0
  74. package/dist/components/harvester.js.map +1 -0
  75. package/dist/components/index.d.ts +11 -0
  76. package/dist/components/index.d.ts.map +1 -0
  77. package/dist/components/index.js +9 -0
  78. package/dist/components/index.js.map +1 -0
  79. package/dist/components/interfaces.d.ts +126 -0
  80. package/dist/components/interfaces.d.ts.map +1 -0
  81. package/dist/components/interfaces.js +8 -0
  82. package/dist/components/interfaces.js.map +1 -0
  83. package/dist/components/map_manager.d.ts +61 -0
  84. package/dist/components/map_manager.d.ts.map +1 -0
  85. package/dist/components/map_manager.js +242 -0
  86. package/dist/components/map_manager.js.map +1 -0
  87. package/dist/components/tileset_manager.d.ts +125 -0
  88. package/dist/components/tileset_manager.d.ts.map +1 -0
  89. package/dist/components/tileset_manager.js +623 -0
  90. package/dist/components/tileset_manager.js.map +1 -0
  91. package/dist/components/types.d.ts +226 -0
  92. package/dist/components/types.d.ts.map +1 -0
  93. package/dist/components/types.js +8 -0
  94. package/dist/components/types.js.map +1 -0
  95. package/dist/database/adapters/knex_database_adapter.d.ts +97 -0
  96. package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -0
  97. package/dist/database/adapters/knex_database_adapter.js +729 -0
  98. package/dist/database/adapters/knex_database_adapter.js.map +1 -0
  99. package/dist/database/database_adapter.d.ts +262 -0
  100. package/dist/database/database_adapter.d.ts.map +1 -0
  101. package/dist/database/database_adapter.js +46 -0
  102. package/dist/database/database_adapter.js.map +1 -0
  103. package/dist/engine/digital_twin_engine.d.ts +295 -0
  104. package/dist/engine/digital_twin_engine.d.ts.map +1 -0
  105. package/dist/engine/digital_twin_engine.js +907 -0
  106. package/dist/engine/digital_twin_engine.js.map +1 -0
  107. package/dist/engine/endpoints.d.ts +47 -0
  108. package/dist/engine/endpoints.d.ts.map +1 -0
  109. package/dist/engine/endpoints.js +88 -0
  110. package/dist/engine/endpoints.js.map +1 -0
  111. package/dist/engine/error_handler.d.ts +20 -0
  112. package/dist/engine/error_handler.d.ts.map +1 -0
  113. package/dist/engine/error_handler.js +69 -0
  114. package/dist/engine/error_handler.js.map +1 -0
  115. package/dist/engine/events.d.ts +93 -0
  116. package/dist/engine/events.d.ts.map +1 -0
  117. package/dist/engine/events.js +71 -0
  118. package/dist/engine/events.js.map +1 -0
  119. package/dist/engine/health.d.ts +112 -0
  120. package/dist/engine/health.d.ts.map +1 -0
  121. package/dist/engine/health.js +190 -0
  122. package/dist/engine/health.js.map +1 -0
  123. package/dist/engine/initializer.d.ts +62 -0
  124. package/dist/engine/initializer.d.ts.map +1 -0
  125. package/dist/engine/initializer.js +108 -0
  126. package/dist/engine/initializer.js.map +1 -0
  127. package/dist/engine/queue_manager.d.ts +87 -0
  128. package/dist/engine/queue_manager.d.ts.map +1 -0
  129. package/dist/engine/queue_manager.js +196 -0
  130. package/dist/engine/queue_manager.js.map +1 -0
  131. package/dist/engine/scheduler.d.ts +30 -0
  132. package/dist/engine/scheduler.d.ts.map +1 -0
  133. package/dist/engine/scheduler.js +378 -0
  134. package/dist/engine/scheduler.js.map +1 -0
  135. package/dist/engine/upload_processor.d.ts +36 -0
  136. package/dist/engine/upload_processor.d.ts.map +1 -0
  137. package/dist/engine/upload_processor.js +113 -0
  138. package/dist/engine/upload_processor.js.map +1 -0
  139. package/dist/env/env.d.ts +134 -0
  140. package/dist/env/env.d.ts.map +1 -0
  141. package/dist/env/env.js +177 -0
  142. package/dist/env/env.js.map +1 -0
  143. package/dist/errors/index.d.ts +94 -0
  144. package/dist/errors/index.d.ts.map +1 -0
  145. package/dist/errors/index.js +149 -0
  146. package/dist/errors/index.js.map +1 -0
  147. package/dist/index.d.ts +55 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +65 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/openapi/generator.d.ts +93 -0
  152. package/dist/openapi/generator.d.ts.map +1 -0
  153. package/dist/openapi/generator.js +293 -0
  154. package/dist/openapi/generator.js.map +1 -0
  155. package/dist/openapi/index.d.ts +9 -0
  156. package/dist/openapi/index.d.ts.map +1 -0
  157. package/dist/openapi/index.js +9 -0
  158. package/dist/openapi/index.js.map +1 -0
  159. package/dist/openapi/types.d.ts +182 -0
  160. package/dist/openapi/types.d.ts.map +1 -0
  161. package/dist/openapi/types.js +16 -0
  162. package/dist/openapi/types.js.map +1 -0
  163. package/dist/storage/adapters/local_storage_service.d.ts +57 -0
  164. package/dist/storage/adapters/local_storage_service.d.ts.map +1 -0
  165. package/dist/storage/adapters/local_storage_service.js +132 -0
  166. package/dist/storage/adapters/local_storage_service.js.map +1 -0
  167. package/dist/storage/adapters/ovh_storage_service.d.ts +72 -0
  168. package/dist/storage/adapters/ovh_storage_service.d.ts.map +1 -0
  169. package/dist/storage/adapters/ovh_storage_service.js +205 -0
  170. package/dist/storage/adapters/ovh_storage_service.js.map +1 -0
  171. package/dist/storage/storage_factory.d.ts +14 -0
  172. package/dist/storage/storage_factory.d.ts.map +1 -0
  173. package/dist/storage/storage_factory.js +43 -0
  174. package/dist/storage/storage_factory.js.map +1 -0
  175. package/dist/storage/storage_service.d.ts +163 -0
  176. package/dist/storage/storage_service.d.ts.map +1 -0
  177. package/dist/storage/storage_service.js +58 -0
  178. package/dist/storage/storage_service.js.map +1 -0
  179. package/dist/types/data_record.d.ts +123 -0
  180. package/dist/types/data_record.d.ts.map +1 -0
  181. package/dist/types/data_record.js +8 -0
  182. package/dist/types/data_record.js.map +1 -0
  183. package/dist/utils/graceful_shutdown.d.ts +44 -0
  184. package/dist/utils/graceful_shutdown.d.ts.map +1 -0
  185. package/dist/utils/graceful_shutdown.js +79 -0
  186. package/dist/utils/graceful_shutdown.js.map +1 -0
  187. package/dist/utils/http_responses.d.ts +175 -0
  188. package/dist/utils/http_responses.d.ts.map +1 -0
  189. package/dist/utils/http_responses.js +216 -0
  190. package/dist/utils/http_responses.js.map +1 -0
  191. package/dist/utils/index.d.ts +8 -0
  192. package/dist/utils/index.d.ts.map +1 -0
  193. package/dist/utils/index.js +6 -0
  194. package/dist/utils/index.js.map +1 -0
  195. package/dist/utils/logger.d.ts +74 -0
  196. package/dist/utils/logger.d.ts.map +1 -0
  197. package/dist/utils/logger.js +92 -0
  198. package/dist/utils/logger.js.map +1 -0
  199. package/dist/utils/map_to_data_record.d.ts +10 -0
  200. package/dist/utils/map_to_data_record.d.ts.map +1 -0
  201. package/dist/utils/map_to_data_record.js +36 -0
  202. package/dist/utils/map_to_data_record.js.map +1 -0
  203. package/dist/utils/safe_async.d.ts +50 -0
  204. package/dist/utils/safe_async.d.ts.map +1 -0
  205. package/dist/utils/safe_async.js +90 -0
  206. package/dist/utils/safe_async.js.map +1 -0
  207. package/dist/utils/servable_endpoint.d.ts +63 -0
  208. package/dist/utils/servable_endpoint.d.ts.map +1 -0
  209. package/dist/utils/servable_endpoint.js +67 -0
  210. package/dist/utils/servable_endpoint.js.map +1 -0
  211. package/dist/utils/zip_utils.d.ts +66 -0
  212. package/dist/utils/zip_utils.d.ts.map +1 -0
  213. package/dist/utils/zip_utils.js +169 -0
  214. package/dist/utils/zip_utils.js.map +1 -0
  215. package/dist/validation/index.d.ts +3 -0
  216. package/dist/validation/index.d.ts.map +1 -0
  217. package/dist/validation/index.js +7 -0
  218. package/dist/validation/index.js.map +1 -0
  219. package/dist/validation/schemas.d.ts +273 -0
  220. package/dist/validation/schemas.d.ts.map +1 -0
  221. package/dist/validation/schemas.js +82 -0
  222. package/dist/validation/schemas.js.map +1 -0
  223. package/dist/validation/validate.d.ts +49 -0
  224. package/dist/validation/validate.d.ts.map +1 -0
  225. package/dist/validation/validate.js +110 -0
  226. package/dist/validation/validate.js.map +1 -0
  227. package/package.json +23 -13
@@ -0,0 +1,1023 @@
1
+ import { UserService } from '../auth/user_service.js';
2
+ import { ApisixAuthParser } from '../auth/apisix_parser.js';
3
+ import { validateIdParam, validateCustomRecordCreate, validateCustomRecordUpdate } from '../validation/index.js';
4
+ import { validateData, validateParams } from '../validation/validate.js';
5
+ import { DigitalTwinError } from '../errors/index.js';
6
+ /**
7
+ * Helper to create error response with proper status code for DigitalTwinError
8
+ */
9
+ function createErrorResponse(error) {
10
+ const statusCode = error instanceof DigitalTwinError ? error.statusCode : 500;
11
+ const message = error instanceof Error ? error.message : 'Unknown error';
12
+ return {
13
+ status: statusCode,
14
+ content: JSON.stringify({ error: message }),
15
+ headers: { 'Content-Type': 'application/json' }
16
+ };
17
+ }
18
+ export class CustomTableManager {
19
+ /**
20
+ * Injects required dependencies into the custom table manager instance.
21
+ *
22
+ * Called by the Digital Twin Engine during component initialization.
23
+ *
24
+ * @param {DatabaseAdapter} db - Database adapter for data operations
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const customTableManager = new MyCustomTableManager()
29
+ * customTableManager.setDependencies(databaseAdapter)
30
+ * ```
31
+ */
32
+ setDependencies(db) {
33
+ this.db = db;
34
+ this.userService = new UserService(db);
35
+ this.tableName = this.getConfiguration().name;
36
+ }
37
+ /**
38
+ * Initialize the database table with custom columns.
39
+ *
40
+ * Creates the table if it doesn't exist, with standard columns (id, created_at, updated_at)
41
+ * plus the custom columns defined in the configuration.
42
+ * Automatically adds an 'owner_id' column for user ownership tracking.
43
+ *
44
+ * @returns {Promise<void>}
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * // Called automatically by the framework
49
+ * await customTableManager.initializeTable()
50
+ * ```
51
+ */
52
+ async initializeTable() {
53
+ const config = this.getConfiguration();
54
+ // Add owner_id column automatically for user ownership
55
+ const columnsWithOwnership = {
56
+ owner_id: 'integer',
57
+ ...config.columns
58
+ };
59
+ // Use the new createTableWithColumns method from DatabaseAdapter
60
+ await this.db.createTableWithColumns(config.name, columnsWithOwnership);
61
+ }
62
+ /**
63
+ * Validate query conditions against requirements.
64
+ *
65
+ * @private
66
+ * @param {Record<string, any>} conditions - Conditions to validate
67
+ * @param {QueryValidationOptions} options - Validation options
68
+ * @throws {Error} If validation fails
69
+ */
70
+ validateQuery(conditions, options) {
71
+ if (!options)
72
+ return;
73
+ // Check required fields
74
+ if (options.required) {
75
+ for (const field of options.required) {
76
+ if (!(field in conditions)) {
77
+ throw new Error(`Field '${field}' is required`);
78
+ }
79
+ const value = conditions[field];
80
+ if (value == null || value === '') {
81
+ throw new Error(`Field '${field}' must have a non-empty value`);
82
+ }
83
+ }
84
+ }
85
+ // Custom validation
86
+ if (options.validate) {
87
+ try {
88
+ options.validate(conditions);
89
+ }
90
+ catch (error) {
91
+ throw new Error(`Validation failed: ${error instanceof Error ? error.message : 'Unknown validation error'}`);
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * Create a new record in the custom table.
97
+ *
98
+ * @param {Record<string, any>} data - Data to insert (excluding id, created_at, updated_at)
99
+ * @returns {Promise<number>} The ID of the created record
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const id = await customTableManager.create({
104
+ * sensor_id: 'TEMP001',
105
+ * type: 'temperature',
106
+ * location: 'Building A - Floor 1',
107
+ * active: true
108
+ * })
109
+ * console.log(`Created sensor with ID: ${id}`)
110
+ * ```
111
+ */
112
+ async create(data) {
113
+ // Use the specialized method for custom tables
114
+ return await this.db.insertCustomTableRecord(this.tableName, data);
115
+ }
116
+ /**
117
+ * Find all records in the custom table.
118
+ *
119
+ * @returns {Promise<CustomTableRecord[]>} Array of all records
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const allSensors = await customTableManager.findAll()
124
+ * console.log(`Found ${allSensors.length} sensors`)
125
+ * ```
126
+ */
127
+ async findAll() {
128
+ // Use specialized method for custom tables that returns raw rows
129
+ const records = await this.db.findCustomTableRecords(this.tableName, {});
130
+ return records.map(record => ({
131
+ id: record.id,
132
+ created_at: new Date(record.created_at),
133
+ updated_at: new Date(record.updated_at),
134
+ ...this.extractCustomFields(record)
135
+ }));
136
+ }
137
+ /**
138
+ * Find a record by its ID.
139
+ *
140
+ * @param {number} id - The record ID to find
141
+ * @returns {Promise<CustomTableRecord | null>} The record or null if not found
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const sensor = await customTableManager.findById(123)
146
+ * if (sensor) {
147
+ * console.log(`Sensor: ${sensor.sensor_id}`)
148
+ * }
149
+ * ```
150
+ */
151
+ async findById(id) {
152
+ const record = await this.db.getCustomTableRecordById(this.tableName, id);
153
+ if (!record) {
154
+ return null;
155
+ }
156
+ return {
157
+ id: record.id,
158
+ created_at: new Date(record.created_at),
159
+ updated_at: new Date(record.updated_at),
160
+ ...this.extractCustomFields(record)
161
+ };
162
+ }
163
+ /**
164
+ * Find records by a single column value with optional validation.
165
+ *
166
+ * @param {string} columnName - Name of the column to search by
167
+ * @param {any} value - Value to search for
168
+ * @param {boolean} required - Whether the value is required (default: true)
169
+ * @returns {Promise<CustomTableRecord[]>} Array of matching records
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const wmsLayers = await wmsManager.findByColumn('wms_url', 'https://example.com/wms')
174
+ * const activeLayers = await wmsManager.findByColumn('active', true)
175
+ *
176
+ * // Optional value (won't throw if empty)
177
+ * const layers = await wmsManager.findByColumn('description', '', false)
178
+ * ```
179
+ */
180
+ async findByColumn(columnName, value, required = true) {
181
+ if (required && (value == null || value === '')) {
182
+ throw new Error(`Value for column '${columnName}' is required and cannot be empty`);
183
+ }
184
+ return this.findByCondition({ [columnName]: value });
185
+ }
186
+ /**
187
+ * Find records by multiple column values with validation support.
188
+ *
189
+ * @param {Record<string, any>} conditions - Key-value pairs to match
190
+ * @param {QueryValidationOptions} options - Validation options
191
+ * @returns {Promise<CustomTableRecord[]>} Array of matching records
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * // Simple query
196
+ * const layers = await wmsManager.findByColumns({
197
+ * wms_url: 'https://example.com/wms',
198
+ * active: true
199
+ * })
200
+ *
201
+ * // With validation
202
+ * const layers = await wmsManager.findByColumns(
203
+ * { wms_url: wmsUrl, projection: projection },
204
+ * {
205
+ * required: ['wms_url', 'projection'],
206
+ * validate: (conditions) => {
207
+ * if (!conditions.wms_url.startsWith('http')) {
208
+ * throw new Error('WMS URL must start with http or https')
209
+ * }
210
+ * }
211
+ * }
212
+ * )
213
+ * ```
214
+ */
215
+ async findByColumns(conditions, options) {
216
+ this.validateQuery(conditions, options);
217
+ return this.findByCondition(conditions);
218
+ }
219
+ /**
220
+ * Find records matching specific conditions (base method).
221
+ *
222
+ * @param {Record<string, any>} conditions - Key-value pairs to match
223
+ * @returns {Promise<CustomTableRecord[]>} Array of matching records
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * const activeSensors = await customTableManager.findByCondition({ active: true })
228
+ * const tempSensors = await customTableManager.findByCondition({ type: 'temperature' })
229
+ * ```
230
+ */
231
+ async findByCondition(conditions) {
232
+ const records = await this.db.findCustomTableRecords(this.tableName, conditions);
233
+ return records.map(record => ({
234
+ id: record.id,
235
+ created_at: new Date(record.created_at),
236
+ updated_at: new Date(record.updated_at),
237
+ ...this.extractCustomFields(record)
238
+ }));
239
+ }
240
+ /**
241
+ * Update a record by its ID.
242
+ *
243
+ * @param {number} id - The record ID to update
244
+ * @param {Record<string, any>} data - Data to update (excluding id, created_at)
245
+ * @returns {Promise<void>}
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * await customTableManager.update(123, {
250
+ * active: false,
251
+ * last_ping: new Date()
252
+ * })
253
+ * ```
254
+ */
255
+ async update(id, data) {
256
+ // Use the new efficient SQL UPDATE method
257
+ await this.db.updateById(this.tableName, id, data);
258
+ }
259
+ /**
260
+ * Delete a record by its ID.
261
+ *
262
+ * @param {number} id - The record ID to delete
263
+ * @returns {Promise<void>}
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * await customTableManager.delete(123)
268
+ * console.log('Sensor deleted successfully')
269
+ * ```
270
+ */
271
+ async delete(id) {
272
+ await this.db.delete(id.toString(), this.tableName);
273
+ }
274
+ /**
275
+ * Delete records by a single column value.
276
+ *
277
+ * @param {string} columnName - Name of the column to match for deletion
278
+ * @param {any} value - Value to match for deletion
279
+ * @returns {Promise<number>} Number of records deleted
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const deleted = await wmsManager.deleteByColumn('active', false)
284
+ * console.log(`Deleted ${deleted} inactive layers`)
285
+ * ```
286
+ */
287
+ async deleteByColumn(columnName, value) {
288
+ return this.deleteByCondition({ [columnName]: value });
289
+ }
290
+ /**
291
+ * Delete records matching specific conditions.
292
+ *
293
+ * @param {Record<string, any>} conditions - Key-value pairs to match for deletion
294
+ * @returns {Promise<number>} Number of records deleted
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * const deleted = await customTableManager.deleteByCondition({ active: false })
299
+ * console.log(`Deleted ${deleted} inactive sensors`)
300
+ * ```
301
+ */
302
+ async deleteByCondition(conditions) {
303
+ const recordsToDelete = await this.findByCondition(conditions);
304
+ for (const record of recordsToDelete) {
305
+ await this.delete(record.id);
306
+ }
307
+ return recordsToDelete.length;
308
+ }
309
+ /**
310
+ * Extract custom fields from a database record, excluding framework fields.
311
+ *
312
+ * @private
313
+ * @param {any} record - Database record
314
+ * @returns {Record<string, any>} Custom fields only (including owner_id)
315
+ */
316
+ extractCustomFields(record) {
317
+ const config = this.getConfiguration();
318
+ const customFields = {};
319
+ // Add owner_id if present
320
+ if ('owner_id' in record) {
321
+ customFields.owner_id = record.owner_id;
322
+ }
323
+ // Add all configured custom columns
324
+ for (const columnName of Object.keys(config.columns)) {
325
+ if (columnName in record) {
326
+ customFields[columnName] = record[columnName];
327
+ }
328
+ }
329
+ return customFields;
330
+ }
331
+ /**
332
+ * AUTHENTICATION HELPERS
333
+ * These methods help custom endpoints implement authentication and authorization.
334
+ */
335
+ /**
336
+ * Authenticate a request and return the user record.
337
+ *
338
+ * Use this for endpoints that require authentication but don't need ownership checks.
339
+ *
340
+ * @protected
341
+ * @param {any} req - HTTP request object
342
+ * @returns {Promise<UserRecord | null>} User record if authenticated, null otherwise
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * async myCustomEndpoint(req: any): Promise<DataResponse> {
347
+ * const userRecord = await this.authenticateRequest(req)
348
+ * if (!userRecord) {
349
+ * return this.authErrorResponse()
350
+ * }
351
+ *
352
+ * // Use userRecord.id for your logic
353
+ * console.log(`User ${userRecord.id} is authenticated`)
354
+ * }
355
+ * ```
356
+ */
357
+ async authenticateRequest(req) {
358
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
359
+ return null;
360
+ }
361
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
362
+ if (!authUser) {
363
+ return null;
364
+ }
365
+ const userRecord = await this.userService.findOrCreateUser(authUser);
366
+ if (!userRecord.id) {
367
+ return null;
368
+ }
369
+ return userRecord;
370
+ }
371
+ /**
372
+ * Authenticate a request and verify the user owns the specified record.
373
+ *
374
+ * Use this for endpoints that modify or delete specific records.
375
+ *
376
+ * @protected
377
+ * @param {any} req - HTTP request object
378
+ * @param {number} recordId - ID of the record to check ownership for
379
+ * @returns {Promise<{ userRecord: UserRecord; record: CustomTableRecord } | null>}
380
+ * Both user and record if authenticated and owns the record, null otherwise
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * async deleteMyRecord(req: any): Promise<DataResponse> {
385
+ * const recordId = parseInt(req.params.id)
386
+ * const auth = await this.authenticateAndCheckOwnership(req, recordId)
387
+ *
388
+ * if (!auth) {
389
+ * return this.authErrorResponse()
390
+ * }
391
+ *
392
+ * if (auth.record.owner_id !== auth.userRecord.id) {
393
+ * return this.forbiddenErrorResponse()
394
+ * }
395
+ *
396
+ * await this.delete(recordId)
397
+ * return { status: 200, content: JSON.stringify({ message: 'Deleted' }) }
398
+ * }
399
+ * ```
400
+ */
401
+ async authenticateAndCheckOwnership(req, recordId) {
402
+ const userRecord = await this.authenticateRequest(req);
403
+ if (!userRecord) {
404
+ return null;
405
+ }
406
+ const record = await this.findById(recordId);
407
+ if (!record) {
408
+ return null;
409
+ }
410
+ return { userRecord, record };
411
+ }
412
+ /**
413
+ * Get authenticated user from request, throwing an error response if not authenticated.
414
+ *
415
+ * Use this when authentication is required and you want to fail fast.
416
+ *
417
+ * @protected
418
+ * @param {any} req - HTTP request object
419
+ * @returns {Promise<UserRecord | DataResponse>} User record if authenticated, error response otherwise
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * async myProtectedEndpoint(req: any): Promise<DataResponse> {
424
+ * const userOrError = await this.requireAuthentication(req)
425
+ * if ('status' in userOrError) {
426
+ * return userOrError // Return error response
427
+ * }
428
+ *
429
+ * const userRecord = userOrError
430
+ * // Continue with authenticated user
431
+ * }
432
+ * ```
433
+ */
434
+ async requireAuthentication(req) {
435
+ const userRecord = await this.authenticateRequest(req);
436
+ if (!userRecord) {
437
+ return this.authErrorResponse();
438
+ }
439
+ return userRecord;
440
+ }
441
+ /**
442
+ * Helper to return a 401 Unauthorized error response.
443
+ *
444
+ * @protected
445
+ * @param {string} message - Custom error message (default: "Authentication required")
446
+ * @returns {DataResponse} 401 error response
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * if (!userRecord) {
451
+ * return this.authErrorResponse()
452
+ * }
453
+ * ```
454
+ */
455
+ authErrorResponse(message = 'Authentication required') {
456
+ return {
457
+ status: 401,
458
+ content: JSON.stringify({ error: message }),
459
+ headers: { 'Content-Type': 'application/json' }
460
+ };
461
+ }
462
+ /**
463
+ * Helper to return a 403 Forbidden error response.
464
+ *
465
+ * @protected
466
+ * @param {string} message - Custom error message (default: "You don't have permission to access this resource")
467
+ * @returns {DataResponse} 403 error response
468
+ *
469
+ * @example
470
+ * ```typescript
471
+ * if (record.owner_id !== userRecord.id) {
472
+ * return this.forbiddenErrorResponse('You can only modify your own records')
473
+ * }
474
+ * ```
475
+ */
476
+ forbiddenErrorResponse(message = "You don't have permission to access this resource") {
477
+ return {
478
+ status: 403,
479
+ content: JSON.stringify({ error: message }),
480
+ headers: { 'Content-Type': 'application/json' }
481
+ };
482
+ }
483
+ /**
484
+ * Helper to check if a user owns a specific record.
485
+ *
486
+ * Returns false if the record doesn't have an owner_id (legacy records without ownership).
487
+ *
488
+ * @protected
489
+ * @param {CustomTableRecord} record - The record to check
490
+ * @param {UserRecord} userRecord - The user to check against
491
+ * @returns {boolean} True if user owns the record, false otherwise
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * const record = await this.findById(recordId)
496
+ * const userRecord = await this.authenticateRequest(req)
497
+ *
498
+ * if (!this.userOwnsRecord(record, userRecord)) {
499
+ * return this.forbiddenErrorResponse()
500
+ * }
501
+ * ```
502
+ */
503
+ userOwnsRecord(record, userRecord) {
504
+ // If record doesn't have an owner_id, deny access for security
505
+ if (!record.owner_id) {
506
+ return false;
507
+ }
508
+ return record.owner_id === userRecord.id;
509
+ }
510
+ /**
511
+ * Get HTTP endpoints exposed by this custom table manager.
512
+ *
513
+ * Returns both standard CRUD endpoints and custom endpoints defined in configuration.
514
+ *
515
+ * @returns {Array} Array of endpoint descriptors with methods, paths, and handlers
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Standard endpoints for a table named 'sensors':
520
+ * GET /sensors - Get all records
521
+ * POST /sensors - Create new record
522
+ * GET /sensors/:id - Get specific record
523
+ * PUT /sensors/:id - Update specific record
524
+ * DELETE /sensors/:id - Delete specific record
525
+ *
526
+ * // Plus any custom endpoints defined in configuration
527
+ * ```
528
+ */
529
+ getEndpoints() {
530
+ const config = this.getConfiguration();
531
+ const endpoints = [];
532
+ // Custom endpoints from configuration - MUST be registered BEFORE :id routes
533
+ // to avoid Express matching /proxy as /:id
534
+ if (config.endpoints) {
535
+ for (const endpoint of config.endpoints) {
536
+ const handlerMethod = this[endpoint.handler];
537
+ if (typeof handlerMethod === 'function') {
538
+ endpoints.push({
539
+ method: endpoint.method,
540
+ path: `/${config.name}${endpoint.path}`,
541
+ handler: handlerMethod.bind(this),
542
+ responseType: endpoint.responseType || 'application/json'
543
+ });
544
+ }
545
+ }
546
+ }
547
+ // Standard CRUD endpoints
548
+ endpoints.push({
549
+ method: 'get',
550
+ path: `/${config.name}`,
551
+ handler: this.handleGetAll.bind(this),
552
+ responseType: 'application/json'
553
+ });
554
+ endpoints.push({
555
+ method: 'post',
556
+ path: `/${config.name}`,
557
+ handler: this.handleCreate.bind(this),
558
+ responseType: 'application/json'
559
+ });
560
+ // :id routes MUST come AFTER custom endpoints
561
+ endpoints.push({
562
+ method: 'get',
563
+ path: `/${config.name}/:id`,
564
+ handler: this.handleGetById.bind(this),
565
+ responseType: 'application/json'
566
+ });
567
+ endpoints.push({
568
+ method: 'put',
569
+ path: `/${config.name}/:id`,
570
+ handler: this.handleUpdate.bind(this),
571
+ responseType: 'application/json'
572
+ });
573
+ endpoints.push({
574
+ method: 'delete',
575
+ path: `/${config.name}/:id`,
576
+ handler: this.handleDelete.bind(this),
577
+ responseType: 'application/json'
578
+ });
579
+ return endpoints;
580
+ }
581
+ /**
582
+ * Returns the OpenAPI specification for this custom table manager's endpoints.
583
+ *
584
+ * Generates documentation for all CRUD endpoints and custom endpoints.
585
+ * Can be overridden by subclasses for more detailed specifications.
586
+ *
587
+ * @returns {OpenAPIComponentSpec} OpenAPI paths, tags, and schemas for this custom table manager
588
+ */
589
+ getOpenAPISpec() {
590
+ const config = this.getConfiguration();
591
+ const basePath = `/${config.name}`;
592
+ const tagName = config.tags?.[0] || config.name;
593
+ // Build properties schema from columns
594
+ const columnProperties = {
595
+ id: { type: 'integer', readOnly: true },
596
+ owner_id: { type: 'integer', nullable: true },
597
+ created_at: { type: 'string', format: 'date-time', readOnly: true },
598
+ updated_at: { type: 'string', format: 'date-time', readOnly: true }
599
+ };
600
+ // Add configured columns to the schema
601
+ for (const [columnName, columnDef] of Object.entries(config.columns)) {
602
+ columnProperties[columnName] = this.columnDefToOpenAPISchema(columnDef);
603
+ }
604
+ const paths = {
605
+ [basePath]: {
606
+ get: {
607
+ summary: `List all ${config.name} records`,
608
+ description: config.description,
609
+ tags: [tagName],
610
+ responses: {
611
+ '200': {
612
+ description: 'List of records',
613
+ content: {
614
+ 'application/json': {
615
+ schema: {
616
+ type: 'array',
617
+ items: { $ref: `#/components/schemas/${config.name}Record` }
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
623
+ },
624
+ post: {
625
+ summary: `Create a new ${config.name} record`,
626
+ description: 'Create a new record. Requires authentication.',
627
+ tags: [tagName],
628
+ security: [{ ApiKeyAuth: [] }],
629
+ requestBody: {
630
+ required: true,
631
+ content: {
632
+ 'application/json': {
633
+ schema: { $ref: `#/components/schemas/${config.name}Input` }
634
+ }
635
+ }
636
+ },
637
+ responses: {
638
+ '201': {
639
+ description: 'Record created successfully',
640
+ content: {
641
+ 'application/json': {
642
+ schema: {
643
+ type: 'object',
644
+ properties: {
645
+ id: { type: 'integer' },
646
+ message: { type: 'string' }
647
+ }
648
+ }
649
+ }
650
+ }
651
+ },
652
+ '400': { description: 'Bad request' },
653
+ '401': { description: 'Unauthorized' }
654
+ }
655
+ }
656
+ },
657
+ [`${basePath}/{id}`]: {
658
+ get: {
659
+ summary: `Get ${config.name} record by ID`,
660
+ description: 'Returns a single record',
661
+ tags: [tagName],
662
+ parameters: [
663
+ {
664
+ name: 'id',
665
+ in: 'path',
666
+ required: true,
667
+ schema: { type: 'integer' },
668
+ description: 'Record ID'
669
+ }
670
+ ],
671
+ responses: {
672
+ '200': {
673
+ description: 'Record details',
674
+ content: {
675
+ 'application/json': {
676
+ schema: { $ref: `#/components/schemas/${config.name}Record` }
677
+ }
678
+ }
679
+ },
680
+ '404': { description: 'Record not found' }
681
+ }
682
+ },
683
+ put: {
684
+ summary: `Update ${config.name} record`,
685
+ description: 'Update a record. Requires authentication and ownership.',
686
+ tags: [tagName],
687
+ security: [{ ApiKeyAuth: [] }],
688
+ parameters: [
689
+ {
690
+ name: 'id',
691
+ in: 'path',
692
+ required: true,
693
+ schema: { type: 'integer' },
694
+ description: 'Record ID'
695
+ }
696
+ ],
697
+ requestBody: {
698
+ required: true,
699
+ content: {
700
+ 'application/json': {
701
+ schema: { $ref: `#/components/schemas/${config.name}Input` }
702
+ }
703
+ }
704
+ },
705
+ responses: {
706
+ '200': { description: 'Record updated successfully' },
707
+ '400': { description: 'Bad request' },
708
+ '401': { description: 'Unauthorized' },
709
+ '403': { description: 'Forbidden - not owner' },
710
+ '404': { description: 'Record not found' }
711
+ }
712
+ },
713
+ delete: {
714
+ summary: `Delete ${config.name} record`,
715
+ description: 'Delete a record. Requires authentication and ownership.',
716
+ tags: [tagName],
717
+ security: [{ ApiKeyAuth: [] }],
718
+ parameters: [
719
+ {
720
+ name: 'id',
721
+ in: 'path',
722
+ required: true,
723
+ schema: { type: 'integer' },
724
+ description: 'Record ID'
725
+ }
726
+ ],
727
+ responses: {
728
+ '200': { description: 'Record deleted successfully' },
729
+ '401': { description: 'Unauthorized' },
730
+ '403': { description: 'Forbidden - not owner' },
731
+ '404': { description: 'Record not found' }
732
+ }
733
+ }
734
+ }
735
+ };
736
+ // Add custom endpoints from configuration
737
+ if (config.endpoints) {
738
+ for (const endpoint of config.endpoints) {
739
+ const fullPath = `${basePath}${endpoint.path}`;
740
+ const method = endpoint.method.toLowerCase();
741
+ if (!paths[fullPath]) {
742
+ paths[fullPath] = {};
743
+ }
744
+ paths[fullPath][method] = {
745
+ summary: `Custom endpoint: ${endpoint.path}`,
746
+ tags: [tagName],
747
+ responses: {
748
+ '200': { description: 'Successful response' }
749
+ }
750
+ };
751
+ }
752
+ }
753
+ // Build input schema (excludes readonly fields)
754
+ const inputProperties = {};
755
+ for (const [columnName, columnDef] of Object.entries(config.columns)) {
756
+ inputProperties[columnName] = this.columnDefToOpenAPISchema(columnDef);
757
+ }
758
+ return {
759
+ paths,
760
+ tags: [
761
+ {
762
+ name: tagName,
763
+ description: config.description
764
+ }
765
+ ],
766
+ schemas: {
767
+ [`${config.name}Record`]: {
768
+ type: 'object',
769
+ properties: columnProperties
770
+ },
771
+ [`${config.name}Input`]: {
772
+ type: 'object',
773
+ properties: inputProperties
774
+ }
775
+ }
776
+ };
777
+ }
778
+ /**
779
+ * Convert SQL column definition to OpenAPI schema type.
780
+ *
781
+ * @private
782
+ * @param {string} columnDef - SQL column definition (e.g., 'text not null', 'integer default 0')
783
+ * @returns {object} OpenAPI schema object
784
+ */
785
+ columnDefToOpenAPISchema(columnDef) {
786
+ const lowerDef = columnDef.toLowerCase();
787
+ if (lowerDef.includes('integer') || lowerDef.includes('int')) {
788
+ return { type: 'integer' };
789
+ }
790
+ else if (lowerDef.includes('real') || lowerDef.includes('float') || lowerDef.includes('double')) {
791
+ return { type: 'number' };
792
+ }
793
+ else if (lowerDef.includes('boolean') || lowerDef.includes('bool')) {
794
+ return { type: 'boolean' };
795
+ }
796
+ else if (lowerDef.includes('timestamp') || lowerDef.includes('datetime')) {
797
+ return { type: 'string', format: 'date-time' };
798
+ }
799
+ else if (lowerDef.includes('date')) {
800
+ return { type: 'string', format: 'date' };
801
+ }
802
+ else {
803
+ return { type: 'string' };
804
+ }
805
+ }
806
+ /**
807
+ * Standard endpoint handlers for CRUD operations
808
+ */
809
+ async handleGetAll(_req) {
810
+ try {
811
+ const records = await this.findAll();
812
+ return {
813
+ status: 200,
814
+ content: JSON.stringify(records),
815
+ headers: { 'Content-Type': 'application/json' }
816
+ };
817
+ }
818
+ catch (error) {
819
+ return createErrorResponse(error);
820
+ }
821
+ }
822
+ async handleCreate(req) {
823
+ try {
824
+ // Check authentication
825
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
826
+ return {
827
+ status: 401,
828
+ content: JSON.stringify({ error: 'Authentication required' }),
829
+ headers: { 'Content-Type': 'application/json' }
830
+ };
831
+ }
832
+ // Parse authenticated user
833
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
834
+ if (!authUser) {
835
+ return {
836
+ status: 401,
837
+ content: JSON.stringify({ error: 'Invalid authentication headers' }),
838
+ headers: { 'Content-Type': 'application/json' }
839
+ };
840
+ }
841
+ if (!req?.body) {
842
+ return {
843
+ status: 400,
844
+ content: JSON.stringify({ error: 'Request body is required' }),
845
+ headers: { 'Content-Type': 'application/json' }
846
+ };
847
+ }
848
+ // Validate request body (ValidationError bubbles up to global handler -> 422)
849
+ const validatedBody = await validateData(validateCustomRecordCreate, req.body, 'Record data');
850
+ // Find or create user in database
851
+ const userRecord = await this.userService.findOrCreateUser(authUser);
852
+ if (!userRecord.id) {
853
+ return {
854
+ status: 500,
855
+ content: JSON.stringify({ error: 'Failed to retrieve user information' }),
856
+ headers: { 'Content-Type': 'application/json' }
857
+ };
858
+ }
859
+ // Add owner_id to the data
860
+ const dataWithOwner = {
861
+ ...validatedBody,
862
+ owner_id: userRecord.id
863
+ };
864
+ const id = await this.create(dataWithOwner);
865
+ return {
866
+ status: 201,
867
+ content: JSON.stringify({ id, message: 'Record created successfully' }),
868
+ headers: { 'Content-Type': 'application/json' }
869
+ };
870
+ }
871
+ catch (error) {
872
+ return createErrorResponse(error);
873
+ }
874
+ }
875
+ async handleGetById(req) {
876
+ try {
877
+ // Validate ID parameter (ValidationError bubbles up to global handler -> 422)
878
+ const validatedParams = await validateParams(validateIdParam, req.params || {}, 'Record ID');
879
+ const record = await this.findById(validatedParams.id);
880
+ if (!record) {
881
+ return {
882
+ status: 404,
883
+ content: JSON.stringify({ error: 'Record not found' }),
884
+ headers: { 'Content-Type': 'application/json' }
885
+ };
886
+ }
887
+ return {
888
+ status: 200,
889
+ content: JSON.stringify(record),
890
+ headers: { 'Content-Type': 'application/json' }
891
+ };
892
+ }
893
+ catch (error) {
894
+ return createErrorResponse(error);
895
+ }
896
+ }
897
+ async handleUpdate(req) {
898
+ try {
899
+ // Check authentication
900
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
901
+ return {
902
+ status: 401,
903
+ content: JSON.stringify({ error: 'Authentication required' }),
904
+ headers: { 'Content-Type': 'application/json' }
905
+ };
906
+ }
907
+ // Parse authenticated user
908
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
909
+ if (!authUser) {
910
+ return {
911
+ status: 401,
912
+ content: JSON.stringify({ error: 'Invalid authentication headers' }),
913
+ headers: { 'Content-Type': 'application/json' }
914
+ };
915
+ }
916
+ // Validate ID parameter (ValidationError bubbles up to global handler -> 422)
917
+ const validatedParams = await validateParams(validateIdParam, req.params || {}, 'Record ID');
918
+ if (!req?.body) {
919
+ return {
920
+ status: 400,
921
+ content: JSON.stringify({ error: 'Request body is required' }),
922
+ headers: { 'Content-Type': 'application/json' }
923
+ };
924
+ }
925
+ // Validate request body (ValidationError bubbles up to global handler -> 422)
926
+ const validatedBody = await validateData(validateCustomRecordUpdate, req.body, 'Record data');
927
+ // Find or create user in database
928
+ const userRecord = await this.userService.findOrCreateUser(authUser);
929
+ if (!userRecord.id) {
930
+ return {
931
+ status: 500,
932
+ content: JSON.stringify({ error: 'Failed to retrieve user information' }),
933
+ headers: { 'Content-Type': 'application/json' }
934
+ };
935
+ }
936
+ // Check if record exists and belongs to this user
937
+ const existingRecord = await this.findById(validatedParams.id);
938
+ if (!existingRecord) {
939
+ return {
940
+ status: 404,
941
+ content: JSON.stringify({ error: 'Record not found' }),
942
+ headers: { 'Content-Type': 'application/json' }
943
+ };
944
+ }
945
+ // Check ownership (only owner can modify their records)
946
+ if (!existingRecord.owner_id || existingRecord.owner_id !== userRecord.id) {
947
+ return {
948
+ status: 403,
949
+ content: JSON.stringify({ error: 'You can only modify your own records' }),
950
+ headers: { 'Content-Type': 'application/json' }
951
+ };
952
+ }
953
+ await this.update(validatedParams.id, validatedBody);
954
+ return {
955
+ status: 200,
956
+ content: JSON.stringify({ message: 'Record updated successfully' }),
957
+ headers: { 'Content-Type': 'application/json' }
958
+ };
959
+ }
960
+ catch (error) {
961
+ return createErrorResponse(error);
962
+ }
963
+ }
964
+ async handleDelete(req) {
965
+ try {
966
+ // Check authentication
967
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
968
+ return {
969
+ status: 401,
970
+ content: JSON.stringify({ error: 'Authentication required' }),
971
+ headers: { 'Content-Type': 'application/json' }
972
+ };
973
+ }
974
+ // Parse authenticated user
975
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
976
+ if (!authUser) {
977
+ return {
978
+ status: 401,
979
+ content: JSON.stringify({ error: 'Invalid authentication headers' }),
980
+ headers: { 'Content-Type': 'application/json' }
981
+ };
982
+ }
983
+ // Validate ID parameter (ValidationError bubbles up to global handler -> 422)
984
+ const validatedParams = await validateParams(validateIdParam, req.params || {}, 'Record ID');
985
+ // Find or create user in database
986
+ const userRecord = await this.userService.findOrCreateUser(authUser);
987
+ if (!userRecord.id) {
988
+ return {
989
+ status: 500,
990
+ content: JSON.stringify({ error: 'Failed to retrieve user information' }),
991
+ headers: { 'Content-Type': 'application/json' }
992
+ };
993
+ }
994
+ // Check if record exists and belongs to this user
995
+ const existingRecord = await this.findById(validatedParams.id);
996
+ if (!existingRecord) {
997
+ return {
998
+ status: 404,
999
+ content: JSON.stringify({ error: 'Record not found' }),
1000
+ headers: { 'Content-Type': 'application/json' }
1001
+ };
1002
+ }
1003
+ // Check ownership (only owner can delete their records)
1004
+ if (!existingRecord.owner_id || existingRecord.owner_id !== userRecord.id) {
1005
+ return {
1006
+ status: 403,
1007
+ content: JSON.stringify({ error: 'You can only delete your own records' }),
1008
+ headers: { 'Content-Type': 'application/json' }
1009
+ };
1010
+ }
1011
+ await this.delete(validatedParams.id);
1012
+ return {
1013
+ status: 200,
1014
+ content: JSON.stringify({ message: 'Record deleted successfully' }),
1015
+ headers: { 'Content-Type': 'application/json' }
1016
+ };
1017
+ }
1018
+ catch (error) {
1019
+ return createErrorResponse(error);
1020
+ }
1021
+ }
1022
+ }
1023
+ //# sourceMappingURL=custom_table_manager.js.map