@oino-ts/db 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/OINODbApi.ts CHANGED
@@ -1,385 +1,385 @@
1
- /*
2
- * This Source Code Form is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- */
6
-
7
- import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINODataRow, OINODataCell, OINODbModelSet, OINOBenchmark, OINODbApiRequestParams, OINODbConfig, OINOHttpResult, OINOHtmlTemplate, OINONumberDataField, OINODbParser } from "./index.js"
8
- import { OINOLog, OINOResult } from "@oino-ts/common";
9
- import { OINOHashid } from "@oino-ts/hashid"
10
-
11
- const API_EMPTY_PARAMS:OINODbApiRequestParams = { sqlParams: {} }
12
-
13
- /**
14
- * OINO API request result object with returned data and/or http status code/message and
15
- * error / warning messages.
16
- *
17
- */
18
- export class OINODbApiResult extends OINOResult {
19
- /** DbApi request params */
20
- params: OINODbApiRequestParams
21
-
22
- /** Returned data if any */
23
- data?: OINODbModelSet;
24
-
25
- /**
26
- * Constructor of OINODbApiResult.
27
- *
28
- * @param params DbApi request parameters
29
- * @param data result data
30
- *
31
- */
32
- constructor (params:OINODbApiRequestParams, data?:OINODbModelSet) {
33
- super()
34
- this.params = params
35
- this.data = data
36
- }
37
-
38
- /**
39
- * Creates a HTTP Response from API results.
40
- *
41
- * @param headers Headers to include in the response
42
- *
43
- */
44
- async getResponse(headers:Record<string, string> = {}):Promise<Response> {
45
- let response:Response|null = null
46
- if (this.success && this.data) {
47
- const body = await this.data.writeString(this.params.responseType)
48
- response = new Response(body, {status:this.statusCode, statusText: this.statusMessage, headers: headers })
49
- } else {
50
- response = new Response(JSON.stringify(this, null, 3), {status:this.statusCode, statusText: this.statusMessage, headers: headers })
51
- }
52
- for (let i=0; i<this.messages.length; i++) {
53
- response.headers.set('X-OINO-MESSAGE-' + i, this.messages[i])
54
- }
55
- return Promise.resolve(response)
56
- }
57
- }
58
-
59
- /**
60
- * Specialized HTML template that can render ´OINODbApiResult´.
61
- *
62
- */
63
- export class OINODbHtmlTemplate extends OINOHtmlTemplate {
64
-
65
- /**
66
- * Creates HTML Response from API modelset.
67
- *
68
- * @param modelset OINO API dataset
69
- * @param overrideValues values to override in the data
70
- *
71
- */
72
- async renderFromDbData(modelset:OINODbModelSet, overrideValues?:any):Promise<OINOHttpResult> {
73
- OINOBenchmark.start("OINOHtmlTemplate", "renderFromDbData")
74
- let html:string = ""
75
- const dataset:OINODbDataSet|undefined = modelset.dataset
76
- const datamodel:OINODbDataModel = modelset.datamodel
77
- const api:OINODbApi = modelset.datamodel.api
78
- const modified_index = datamodel.findFieldIndexByName(api.params.cacheModifiedField || "")
79
- let last_modified:number = this.modified
80
- // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
81
-
82
- while (!dataset.isEof()) {
83
- const row:OINODataRow = dataset.getRow()
84
- if (modified_index >= 0) {
85
- last_modified = Math.max(last_modified, new Date(row[modified_index] as Date).getTime())
86
- // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
87
- }
88
- let row_id_seed:string = datamodel.getRowPrimarykeyValues(row).join(' ')
89
- let primary_key_values:string[] = []
90
- this.clearVariables()
91
- this.setVariableFromValue(OINODbConfig.OINODB_ID_FIELD, "")
92
- // let html_row:string = this.template.replaceAll('###' + OINODbConfig.OINODB_ID_FIELD + '###', '###createHtmlFromData_temporary_oinoid###')
93
- for (let i=0; i<datamodel.fields.length; i++) {
94
- const f:OINODbDataField = datamodel.fields[i]
95
- let value:string|null|undefined = f.serializeCell(row[i])
96
- if (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) {
97
- if (value && (f instanceof OINONumberDataField) && (datamodel.api.hashid)) {
98
- value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed)
99
- }
100
- if (f.fieldParams.isPrimaryKey) {
101
- primary_key_values.push(value || "")
102
- }
103
- }
104
- // OINOLog.debug("renderFromDbData replace field value", {field:f.name, value:value })
105
- this.setVariableFromValue(f.name, value || "")
106
- }
107
- this.setVariableFromProperties(overrideValues)
108
- this.setVariableFromValue(OINODbConfig.OINODB_ID_FIELD, OINODbConfig.printOINOId(primary_key_values))
109
- // html_row = html_row.replaceAll('###createHtmlFromData_temporary_oinoid###', OINOStr.encode(OINODbConfig.printOINOId(primary_key_values), OINOContentType.html))
110
- html += this._renderHtml() + "\r\n"
111
- await dataset.next()
112
- }
113
- // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
114
- this.modified = last_modified
115
- const result:OINOHttpResult = this._createHttpResult(html, false)
116
- OINOBenchmark.end("OINOHtmlTemplate", "renderFromDbData")
117
- return result
118
- }
119
-
120
- }
121
-
122
-
123
- /**
124
- * API class with method to process HTTP REST requests.
125
- *
126
- */
127
- export class OINODbApi {
128
- /** API database reference */
129
- readonly db: OINODb
130
-
131
- /** API datamodel */
132
- readonly datamodel: OINODbDataModel
133
-
134
- /** API parameters */
135
- readonly params: OINODbApiParams
136
-
137
- /** API hashid */
138
- readonly hashid:OINOHashid|null
139
-
140
- /**
141
- * Constructor of API object.
142
- * NOTE! OINODb.initDatamodel must be called if created manually instead of the factory.
143
- *
144
- * @param db database for the API
145
- * @param params parameters for the API
146
- *
147
- */
148
- constructor (db: OINODb, params:OINODbApiParams) {
149
- // OINOLog.debug("OINODbApi.constructor", {db:db, tableName:tableName, params:params})
150
- if (!params.tableName) {
151
- throw new Error(OINO_ERROR_PREFIX + ": OINODbApiParams needs to define a table name!")
152
- }
153
- this.db = db
154
- this.params = params
155
- this.datamodel = new OINODbDataModel(this)
156
- if (this.params.hashidKey) {
157
- this.hashid = new OINOHashid(this.params.hashidKey, this.db.name, this.params.hashidLength, this.params.hashidStaticIds)
158
- } else {
159
- this.hashid = null
160
- }
161
- }
162
-
163
- private _validateRowValues(httpResult:OINODbApiResult, row:OINODataRow, requirePrimaryKey:boolean):void {
164
- let field:OINODbDataField
165
- for (let i=0; i<this.datamodel.fields.length; i++) {
166
- field = this.datamodel.fields[i]
167
- // OINOLog.debug("OINODbApi.validateHttpValues", {field:field})
168
- const val:OINODataCell = row[i]
169
- // OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
170
- if ((val === null) && ((field.fieldParams.isNotNull)||(field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
171
- httpResult.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues")
172
-
173
- } else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
174
- httpResult.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues")
175
-
176
- } else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
177
- httpResult.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues")
178
-
179
- } else {
180
- if ((field instanceof OINOStringDataField) && ((field.maxLength > 0))){
181
- const str_val = val?.toString() || ""
182
- // OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
183
- if (str_val.length > field.maxLength) {
184
- if (this.params.failOnOversizedValues) {
185
- httpResult.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues")
186
- } else {
187
- httpResult.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues")
188
- }
189
- }
190
- }
191
-
192
- }
193
- }
194
- //logDebug("OINODbApi.validateHttpValues", {result:result})
195
- }
196
-
197
- private async _doGet(result:OINODbApiResult, id:string, params:OINODbApiRequestParams):Promise<void> {
198
- let sql:string = ""
199
- try {
200
- sql = this.datamodel.printSqlSelect(id, params.sqlParams || {})
201
- // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
202
- const sql_res:OINODbDataSet = await this.db.sqlSelect(sql)
203
- // OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
204
- if (sql_res.hasErrors()) {
205
- result.setError(500, sql_res.getFirstError(), "DoGet")
206
- result.addDebug("OINO GET SQL [" + sql + "]", "DoPut")
207
- } else {
208
- result.data = new OINODbModelSet(this.datamodel, sql_res)
209
- }
210
- } catch (e:any) {
211
- result.setError(500, "Unhandled exception in doGet: " + e.message, "DoGet")
212
- result.addDebug("OINO GET SQL [" + sql + "]", "DoGet")
213
- }
214
- }
215
-
216
- private async _doPost(result:OINODbApiResult, rows:OINODataRow[]):Promise<void> {
217
- let sql:string = ""
218
- try {
219
- let i:number = 0
220
- while (i<rows.length) {
221
- this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey||false)
222
- if (result.success) {
223
- sql += this.datamodel.printSqlInsert(rows[i])
224
- }
225
- result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
226
- i++
227
- }
228
- if (sql == "") {
229
- result.setError(405, "No valid rows for POST!", "DoPost")
230
- result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost")
231
-
232
- } else {
233
- // OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
234
- const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
235
- // OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
236
- if (sql_res.hasErrors()) {
237
- result.setError(500, sql_res.getFirstError(), "DoPost")
238
- result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
239
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
240
- }
241
- }
242
- } catch (e:any) {
243
- result.setError(500, "Unhandled exception in doPost: " + e.message, "DoPost")
244
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
245
- }
246
- }
247
-
248
- private async _doPut(result:OINODbApiResult, id:string, row:OINODataRow):Promise<void> {
249
- let sql:string = ""
250
- try {
251
- this._validateRowValues(result, row, false)
252
- if (result.success) {
253
- sql = this.datamodel.printSqlUpdate(id, row)
254
- // OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
255
- const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
256
- // OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
257
- if (sql_res.hasErrors()) {
258
- result.setError(500, sql_res.getFirstError(), "DoPut")
259
- result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
260
- result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
261
- }
262
- }
263
- } catch (e:any) {
264
- result.setError(500, "Unhandled exception: " + e.message, "DoPut")
265
- result.addDebug("OINO POST SQL [" + sql + "]", "DoPut")
266
- }
267
- }
268
-
269
- private async _doDelete(result:OINODbApiResult, id:string):Promise<void> {
270
- let sql:string = ""
271
- try {
272
- sql = this.datamodel.printSqlDelete(id)
273
- // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
274
- const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
275
- // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
276
- if (sql_res.hasErrors()) {
277
- result.setError(500, sql_res.getFirstError(), "DoDelete")
278
- result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
279
- result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
280
- }
281
- } catch (e:any) {
282
- result.setError(500, "Unhandled exception: " + e.message, "DoDelete")
283
- result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
284
- }
285
- }
286
-
287
- /**
288
- * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
289
- * SQL select, insert, update and delete.
290
- *
291
- * @param method HTTP verb (uppercase)
292
- * @param id URL id of the REST request
293
- * @param body HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
294
- * @param params HTTP URL parameters as key-value-pairs
295
- *
296
- */
297
- async doRequest(method:string, id: string, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
298
- OINOBenchmark.start("OINODbApi", "doRequest")
299
- // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
300
- let result:OINODbApiResult = new OINODbApiResult(params)
301
- let rows:OINODataRow[] = []
302
- if ((method == "POST") || (method == "PUT")) {
303
- try {
304
- if (Array.isArray(body)) {
305
- rows = body as OINODataRow[]
306
- } else {
307
- rows = OINODbParser.createRows(this.datamodel, body, params)
308
- }
309
-
310
- } catch (e:any) {
311
- result.setError(400, "Invalid data: " + e.message, "DoRequest")
312
- }
313
- // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
314
- }
315
- if (method == "GET") {
316
- await this._doGet(result, id, params)
317
-
318
- } else if (method == "PUT") {
319
- if (!id) {
320
- result.setError(400, "HTTP PUT method requires an URL ID for the row that is updated!", "DoRequest")
321
-
322
- } else if (rows.length != 1) {
323
- result.setError(400, "HTTP PUT method requires exactly one row in the body data!", "DoRequest")
324
-
325
- } else {
326
- try {
327
- await this._doPut(result, id, rows[0])
328
-
329
- } catch (e:any) {
330
- result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest")
331
- }
332
- }
333
- } else if (method == "POST") {
334
- if (id) {
335
- result.setError(400, "HTTP POST method must not have an URL ID as it does not target an existing row but creates a new one!", "DoRequest")
336
-
337
- } else if (rows.length == 0) {
338
- result.setError(400, "HTTP POST method requires at least one row in the body data!", "DoRequest")
339
-
340
- } else {
341
- try {
342
- // OINOLog.debug("OINODbApi.doRequest / POST", {rows:rows})
343
- await this._doPost(result, rows)
344
-
345
- } catch (e:any) {
346
- result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest")
347
- }
348
- }
349
- } else if (method == "DELETE") {
350
- if (!id) {
351
- result.setError(400, "HTTP DELETE method requires an id!", "DoRequest")
352
-
353
- } else {
354
- try {
355
- await this._doDelete(result, id)
356
-
357
- } catch (e:any) {
358
- result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest")
359
- }
360
- }
361
- } else {
362
- result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest")
363
- }
364
- OINOBenchmark.end("OINODbApi", "doRequest", method)
365
- return Promise.resolve(result)
366
- }
367
-
368
- /**
369
- * Method to check if a field is included in the API params.
370
- *
371
- * @param fieldName name of the field
372
- *
373
- */
374
-
375
- public isFieldIncluded(fieldName:string):boolean {
376
- // OINOLog.debug("OINODbApi.isFieldIncluded", {fieldName:fieldName, included:this.params.includeFields})
377
- const params = this.params
378
- return (
379
- ((params.excludeFieldPrefix == undefined) || (params.excludeFieldPrefix == "") || (fieldName.startsWith(params.excludeFieldPrefix) == false)) &&
380
- ((params.excludeFields == undefined) || (params.excludeFields.length == 0) || (params.excludeFields.indexOf(fieldName) < 0)) &&
381
- ((params.includeFields == undefined) || (params.includeFields.length == 0) || (params.includeFields.indexOf(fieldName) >= 0))
382
- )
383
- }
384
-
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINODataRow, OINODataCell, OINODbModelSet, OINOBenchmark, OINODbApiRequestParams, OINODbConfig, OINOHttpResult, OINOHtmlTemplate, OINONumberDataField, OINODbParser } from "./index.js"
8
+ import { OINOLog, OINOResult } from "@oino-ts/common";
9
+ import { OINOHashid } from "@oino-ts/hashid"
10
+
11
+ const API_EMPTY_PARAMS:OINODbApiRequestParams = { sqlParams: {} }
12
+
13
+ /**
14
+ * OINO API request result object with returned data and/or http status code/message and
15
+ * error / warning messages.
16
+ *
17
+ */
18
+ export class OINODbApiResult extends OINOResult {
19
+ /** DbApi request params */
20
+ params: OINODbApiRequestParams
21
+
22
+ /** Returned data if any */
23
+ data?: OINODbModelSet;
24
+
25
+ /**
26
+ * Constructor of OINODbApiResult.
27
+ *
28
+ * @param params DbApi request parameters
29
+ * @param data result data
30
+ *
31
+ */
32
+ constructor (params:OINODbApiRequestParams, data?:OINODbModelSet) {
33
+ super()
34
+ this.params = params
35
+ this.data = data
36
+ }
37
+
38
+ /**
39
+ * Creates a HTTP Response from API results.
40
+ *
41
+ * @param headers Headers to include in the response
42
+ *
43
+ */
44
+ async getResponse(headers:Record<string, string> = {}):Promise<Response> {
45
+ let response:Response|null = null
46
+ if (this.success && this.data) {
47
+ const body = await this.data.writeString(this.params.responseType)
48
+ response = new Response(body, {status:this.statusCode, statusText: this.statusMessage, headers: headers })
49
+ } else {
50
+ response = new Response(JSON.stringify(this, null, 3), {status:this.statusCode, statusText: this.statusMessage, headers: headers })
51
+ }
52
+ for (let i=0; i<this.messages.length; i++) {
53
+ response.headers.set('X-OINO-MESSAGE-' + i, this.messages[i])
54
+ }
55
+ return Promise.resolve(response)
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Specialized HTML template that can render ´OINODbApiResult´.
61
+ *
62
+ */
63
+ export class OINODbHtmlTemplate extends OINOHtmlTemplate {
64
+
65
+ /**
66
+ * Creates HTML Response from API modelset.
67
+ *
68
+ * @param modelset OINO API dataset
69
+ * @param overrideValues values to override in the data
70
+ *
71
+ */
72
+ async renderFromDbData(modelset:OINODbModelSet, overrideValues?:any):Promise<OINOHttpResult> {
73
+ OINOBenchmark.start("OINOHtmlTemplate", "renderFromDbData")
74
+ let html:string = ""
75
+ const dataset:OINODbDataSet|undefined = modelset.dataset
76
+ const datamodel:OINODbDataModel = modelset.datamodel
77
+ const api:OINODbApi = modelset.datamodel.api
78
+ const modified_index = datamodel.findFieldIndexByName(api.params.cacheModifiedField || "")
79
+ let last_modified:number = this.modified
80
+ // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
81
+
82
+ while (!dataset.isEof()) {
83
+ const row:OINODataRow = dataset.getRow()
84
+ if (modified_index >= 0) {
85
+ last_modified = Math.max(last_modified, new Date(row[modified_index] as Date).getTime())
86
+ // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
87
+ }
88
+ let row_id_seed:string = datamodel.getRowPrimarykeyValues(row).join(' ')
89
+ let primary_key_values:string[] = []
90
+ this.clearVariables()
91
+ this.setVariableFromValue(OINODbConfig.OINODB_ID_FIELD, "")
92
+ // let html_row:string = this.template.replaceAll('###' + OINODbConfig.OINODB_ID_FIELD + '###', '###createHtmlFromData_temporary_oinoid###')
93
+ for (let i=0; i<datamodel.fields.length; i++) {
94
+ const f:OINODbDataField = datamodel.fields[i]
95
+ let value:string|null|undefined = f.serializeCell(row[i])
96
+ if (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) {
97
+ if (value && (f instanceof OINONumberDataField) && (datamodel.api.hashid)) {
98
+ value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed)
99
+ }
100
+ if (f.fieldParams.isPrimaryKey) {
101
+ primary_key_values.push(value || "")
102
+ }
103
+ }
104
+ // OINOLog.debug("renderFromDbData replace field value", {field:f.name, value:value })
105
+ this.setVariableFromValue(f.name, value || "")
106
+ }
107
+ this.setVariableFromProperties(overrideValues)
108
+ this.setVariableFromValue(OINODbConfig.OINODB_ID_FIELD, OINODbConfig.printOINOId(primary_key_values))
109
+ // html_row = html_row.replaceAll('###createHtmlFromData_temporary_oinoid###', OINOStr.encode(OINODbConfig.printOINOId(primary_key_values), OINOContentType.html))
110
+ html += this._renderHtml() + "\r\n"
111
+ await dataset.next()
112
+ }
113
+ // OINOLog.debug("OINOHtmlTemplate.renderFromDbData", {last_modified:last_modified})
114
+ this.modified = last_modified
115
+ const result:OINOHttpResult = this._createHttpResult(html, false)
116
+ OINOBenchmark.end("OINOHtmlTemplate", "renderFromDbData")
117
+ return result
118
+ }
119
+
120
+ }
121
+
122
+
123
+ /**
124
+ * API class with method to process HTTP REST requests.
125
+ *
126
+ */
127
+ export class OINODbApi {
128
+ /** API database reference */
129
+ readonly db: OINODb
130
+
131
+ /** API datamodel */
132
+ readonly datamodel: OINODbDataModel
133
+
134
+ /** API parameters */
135
+ readonly params: OINODbApiParams
136
+
137
+ /** API hashid */
138
+ readonly hashid:OINOHashid|null
139
+
140
+ /**
141
+ * Constructor of API object.
142
+ * NOTE! OINODb.initDatamodel must be called if created manually instead of the factory.
143
+ *
144
+ * @param db database for the API
145
+ * @param params parameters for the API
146
+ *
147
+ */
148
+ constructor (db: OINODb, params:OINODbApiParams) {
149
+ // OINOLog.debug("OINODbApi.constructor", {db:db, tableName:tableName, params:params})
150
+ if (!params.tableName) {
151
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbApiParams needs to define a table name!")
152
+ }
153
+ this.db = db
154
+ this.params = params
155
+ this.datamodel = new OINODbDataModel(this)
156
+ if (this.params.hashidKey) {
157
+ this.hashid = new OINOHashid(this.params.hashidKey, this.db.name, this.params.hashidLength, this.params.hashidStaticIds)
158
+ } else {
159
+ this.hashid = null
160
+ }
161
+ }
162
+
163
+ private _validateRowValues(httpResult:OINODbApiResult, row:OINODataRow, requirePrimaryKey:boolean):void {
164
+ let field:OINODbDataField
165
+ for (let i=0; i<this.datamodel.fields.length; i++) {
166
+ field = this.datamodel.fields[i]
167
+ // OINOLog.debug("OINODbApi.validateHttpValues", {field:field})
168
+ const val:OINODataCell = row[i]
169
+ // OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
170
+ if ((val === null) && ((field.fieldParams.isNotNull)||(field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
171
+ httpResult.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues")
172
+
173
+ } else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
174
+ httpResult.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues")
175
+
176
+ } else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
177
+ httpResult.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues")
178
+
179
+ } else {
180
+ if ((field instanceof OINOStringDataField) && ((field.maxLength > 0))){
181
+ const str_val = val?.toString() || ""
182
+ // OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
183
+ if (str_val.length > field.maxLength) {
184
+ if (this.params.failOnOversizedValues) {
185
+ httpResult.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues")
186
+ } else {
187
+ httpResult.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues")
188
+ }
189
+ }
190
+ }
191
+
192
+ }
193
+ }
194
+ //logDebug("OINODbApi.validateHttpValues", {result:result})
195
+ }
196
+
197
+ private async _doGet(result:OINODbApiResult, id:string, params:OINODbApiRequestParams):Promise<void> {
198
+ let sql:string = ""
199
+ try {
200
+ sql = this.datamodel.printSqlSelect(id, params.sqlParams || {})
201
+ // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
202
+ const sql_res:OINODbDataSet = await this.db.sqlSelect(sql)
203
+ // OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
204
+ if (sql_res.hasErrors()) {
205
+ result.setError(500, sql_res.getFirstError(), "DoGet")
206
+ result.addDebug("OINO GET SQL [" + sql + "]", "DoPut")
207
+ } else {
208
+ result.data = new OINODbModelSet(this.datamodel, sql_res)
209
+ }
210
+ } catch (e:any) {
211
+ result.setError(500, "Unhandled exception in doGet: " + e.message, "DoGet")
212
+ result.addDebug("OINO GET SQL [" + sql + "]", "DoGet")
213
+ }
214
+ }
215
+
216
+ private async _doPost(result:OINODbApiResult, rows:OINODataRow[]):Promise<void> {
217
+ let sql:string = ""
218
+ try {
219
+ let i:number = 0
220
+ while (i<rows.length) {
221
+ this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey||false)
222
+ if (result.success) {
223
+ sql += this.datamodel.printSqlInsert(rows[i])
224
+ }
225
+ result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
226
+ i++
227
+ }
228
+ if (sql == "") {
229
+ result.setError(405, "No valid rows for POST!", "DoPost")
230
+ result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost")
231
+
232
+ } else {
233
+ // OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
234
+ const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
235
+ // OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
236
+ if (sql_res.hasErrors()) {
237
+ result.setError(500, sql_res.getFirstError(), "DoPost")
238
+ result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
239
+ result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
240
+ }
241
+ }
242
+ } catch (e:any) {
243
+ result.setError(500, "Unhandled exception in doPost: " + e.message, "DoPost")
244
+ result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
245
+ }
246
+ }
247
+
248
+ private async _doPut(result:OINODbApiResult, id:string, row:OINODataRow):Promise<void> {
249
+ let sql:string = ""
250
+ try {
251
+ this._validateRowValues(result, row, false)
252
+ if (result.success) {
253
+ sql = this.datamodel.printSqlUpdate(id, row)
254
+ // OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
255
+ const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
256
+ // OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
257
+ if (sql_res.hasErrors()) {
258
+ result.setError(500, sql_res.getFirstError(), "DoPut")
259
+ result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
260
+ result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
261
+ }
262
+ }
263
+ } catch (e:any) {
264
+ result.setError(500, "Unhandled exception: " + e.message, "DoPut")
265
+ result.addDebug("OINO POST SQL [" + sql + "]", "DoPut")
266
+ }
267
+ }
268
+
269
+ private async _doDelete(result:OINODbApiResult, id:string):Promise<void> {
270
+ let sql:string = ""
271
+ try {
272
+ sql = this.datamodel.printSqlDelete(id)
273
+ // OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
274
+ const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
275
+ // OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
276
+ if (sql_res.hasErrors()) {
277
+ result.setError(500, sql_res.getFirstError(), "DoDelete")
278
+ result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
279
+ result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
280
+ }
281
+ } catch (e:any) {
282
+ result.setError(500, "Unhandled exception: " + e.message, "DoDelete")
283
+ result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
289
+ * SQL select, insert, update and delete.
290
+ *
291
+ * @param method HTTP verb (uppercase)
292
+ * @param id URL id of the REST request
293
+ * @param body HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
294
+ * @param params HTTP URL parameters as key-value-pairs
295
+ *
296
+ */
297
+ async doRequest(method:string, id: string, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
298
+ OINOBenchmark.start("OINODbApi", "doRequest")
299
+ // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
300
+ let result:OINODbApiResult = new OINODbApiResult(params)
301
+ let rows:OINODataRow[] = []
302
+ if ((method == "POST") || (method == "PUT")) {
303
+ try {
304
+ if (Array.isArray(body)) {
305
+ rows = body as OINODataRow[]
306
+ } else {
307
+ rows = OINODbParser.createRows(this.datamodel, body, params)
308
+ }
309
+
310
+ } catch (e:any) {
311
+ result.setError(400, "Invalid data: " + e.message, "DoRequest")
312
+ }
313
+ // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
314
+ }
315
+ if (method == "GET") {
316
+ await this._doGet(result, id, params)
317
+
318
+ } else if (method == "PUT") {
319
+ if (!id) {
320
+ result.setError(400, "HTTP PUT method requires an URL ID for the row that is updated!", "DoRequest")
321
+
322
+ } else if (rows.length != 1) {
323
+ result.setError(400, "HTTP PUT method requires exactly one row in the body data!", "DoRequest")
324
+
325
+ } else {
326
+ try {
327
+ await this._doPut(result, id, rows[0])
328
+
329
+ } catch (e:any) {
330
+ result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest")
331
+ }
332
+ }
333
+ } else if (method == "POST") {
334
+ if (id) {
335
+ result.setError(400, "HTTP POST method must not have an URL ID as it does not target an existing row but creates a new one!", "DoRequest")
336
+
337
+ } else if (rows.length == 0) {
338
+ result.setError(400, "HTTP POST method requires at least one row in the body data!", "DoRequest")
339
+
340
+ } else {
341
+ try {
342
+ // OINOLog.debug("OINODbApi.doRequest / POST", {rows:rows})
343
+ await this._doPost(result, rows)
344
+
345
+ } catch (e:any) {
346
+ result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest")
347
+ }
348
+ }
349
+ } else if (method == "DELETE") {
350
+ if (!id) {
351
+ result.setError(400, "HTTP DELETE method requires an id!", "DoRequest")
352
+
353
+ } else {
354
+ try {
355
+ await this._doDelete(result, id)
356
+
357
+ } catch (e:any) {
358
+ result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest")
359
+ }
360
+ }
361
+ } else {
362
+ result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest")
363
+ }
364
+ OINOBenchmark.end("OINODbApi", "doRequest", method)
365
+ return Promise.resolve(result)
366
+ }
367
+
368
+ /**
369
+ * Method to check if a field is included in the API params.
370
+ *
371
+ * @param fieldName name of the field
372
+ *
373
+ */
374
+
375
+ public isFieldIncluded(fieldName:string):boolean {
376
+ // OINOLog.debug("OINODbApi.isFieldIncluded", {fieldName:fieldName, included:this.params.includeFields})
377
+ const params = this.params
378
+ return (
379
+ ((params.excludeFieldPrefix == undefined) || (params.excludeFieldPrefix == "") || (fieldName.startsWith(params.excludeFieldPrefix) == false)) &&
380
+ ((params.excludeFields == undefined) || (params.excludeFields.length == 0) || (params.excludeFields.indexOf(fieldName) < 0)) &&
381
+ ((params.includeFields == undefined) || (params.includeFields.length == 0) || (params.includeFields.indexOf(fieldName) >= 0))
382
+ )
383
+ }
384
+
385
385
  }