@oino-ts/db 0.0.16 → 0.0.18

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/README.md CHANGED
@@ -59,20 +59,8 @@
59
59
 
60
60
  ## RESTfull
61
61
  OINO maps HTTP methods GET/POST/PUT/DELETE to SQL operations SELECT/INSERT/UPDATE/DELETE. The GET/POST requests can be made without URL ID to get all rows or insert new ones and others target a single row using URL ID.
62
-
63
- ### HTTP GET
64
- ```
65
- Request and response:
66
- > curl.exe -X GET http://localhost:3001/orderdetails/11077:77
67
- [
68
- {"_OINOID_":"11077:77","OrderID":11077,"ProductID":77,"UnitPrice":13,"Quantity":2,"Discount":0}
69
- ]
70
-
71
- SQL:
72
- SELECT "OrderID","ProductID","UnitPrice","Quantity","Discount" FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=77);
73
- ```
74
62
 
75
- ### HTTP POST
63
+ For example HTTP POST
76
64
  ```
77
65
  Request and response:
78
66
  > curl.exe -X POST http://localhost:3001/orderdetails -H "Content-Type: application/json" --data '[{\"OrderID\":11077,\"ProductID\":99,\"UnitPrice\":19,\"Quantity\":1,\"Discount\":0}]'
@@ -82,31 +70,12 @@
82
70
  INSERT INTO [OrderDetails] ("OrderID","ProductID","UnitPrice","Quantity","Discount") VALUES (11077,99,19,1,0);
83
71
  ```
84
72
 
85
- ### HTTP PUT
86
- ```
87
- Request and response:
88
- > curl.exe -X PUT http://localhost:3001/orderdetails/11077:99 -H "Content-Type: application/json" --data '[{\"UnitPrice\":20}]'
89
- {"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
90
73
 
91
- SQL:
92
- UPDATE [OrderDetails] SET "UnitPrice"=20 WHERE ("OrderID"=11077 AND "ProductID"=99);
93
- ```
94
-
95
- ### HTTP DELETE
96
- ```
97
- Request and response:
98
- > curl.exe -X DELETE http://localhost:3001/orderdetails/11077:99
99
- {"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
100
-
101
- SQL:
102
- DELETE FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=99);
103
- ```
104
-
105
74
  ## Universal Serialization
106
75
  OINO handles serialization of data to JSON/CSV/etc. and back based on the data model. It knows what columns exist, what is their data type and how to convert each to JSON/CSV and back. This allows also partial data to be sent, i.e. you can send only columns that need updating or even send extra columns and have them ignored.
107
76
 
108
77
  ### Features
109
- - Files can be sent to BLOB fields using BASE64 encoding.
78
+ - Files can be sent to BLOB fields using BASE64 or MIME multipart encoding. Also supports standard HTML form file submission to blob fields and returning them data url images.
110
79
  - Datetimes are (optionally) normalized to ISO 8601 format.
111
80
  - Extended JSON-encoding
112
81
  - Unquoted literal `undefined` can be used to represent non-existent values (leaving property out works too but preserving structure might be easier e.g. when translating data).
@@ -131,11 +100,11 @@
131
100
  - Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
132
101
  - Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
133
102
 
134
- ## Complex Keys
103
+ ## Composite Keys
135
104
  To support tables with multipart primary keys OINO generates a composite key `_OINOID_` that is included in the result and can be used as the REST ID. For example in the example above table `OrderDetails` has two primary keys `OrderID` and `ProductID` making the `_OINOID_` of form `11077:99`.
136
105
 
137
106
  ## Power Of SQL
138
- Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html) and order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
107
+ Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html), order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html) and limits/paging with [`OINOSqlLimit`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlLimit.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
139
108
 
140
109
  ## Swagger Support
141
110
  Swagger is great as long as the definitions are updated and with OINO you can automatically get a Swagger definition including a data model schema.
@@ -152,7 +121,7 @@
152
121
  ## HTMX support
153
122
  OINO is [htmx.org](https://htmx.org) friendly, allowing easy translation of [`OINODataRow`](https://pragmatta.github.io/oino-ts/types/db_src.OINODataRow.html) to HTML output using templates (cf. the [htmx sample app](https://github.com/pragmatta/oino-ts/tree/main/samples/htmxApp)).
154
123
 
155
- ### Hashids
124
+ ## Hashids
156
125
  Autoinc numeric id's are very pragmatic and fit well with OINO (e.g. using a form without primary key fields to insert new rows with database assigned ids). However it's not always sensible to share information about the sequence. Hashids solve this by masking the original values by encrypting the ids using AES-128 and some randomness. Length of the hashid can be chosen from 12-32 characters where longer ids provide more security. However this should not be considereded a cryptographic solution for keeping ids secret but rather making it infeasible to iterate all ids.
157
126
 
158
127
 
@@ -164,9 +133,6 @@
164
133
 
165
134
  ### Realistic app
166
135
  There needs to be a realistic app built on top of OINO to get a better grasp of the edge cases.
167
-
168
- ### Security review
169
- Handling of SQL-injection attacks needs a thorough review, what are the relevant attack vectors are for OINO and what protections are still needed.
170
136
 
171
137
  ## Roadmap
172
138
  Things that need to happen in some order before beta-status are at least following:
@@ -179,8 +145,8 @@
179
145
  ### Batch updates
180
146
  Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature.
181
147
 
182
- ### Aggregation and limits
183
- Similar to filtering and ordering, aggregation and limits can be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
148
+ ### Aggregation
149
+ Similar to filtering, ordering and limits, aggregation could be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
184
150
 
185
151
  ### Streaming
186
152
  One core idea is to be efficient in not making unnecessary copies of the data and minimizing garbage collection debt. This can be taken further by implementing streaming, allowing large dataset to be written to HTTP response as SQL result rows are received.
@@ -222,4 +188,3 @@
222
188
 
223
189
  ## SQL Scripts
224
190
  The SQL scripts for creating the sample Northwind database are based on [Google Code archive](https://code.google.com/archive/p/northwindextended/downloads) and have been further customized to ensure they would have identical data (in the scope of the automated testing).
225
-
@@ -23,5 +23,31 @@ class OINODb {
23
23
  this._params = params;
24
24
  this.name = params.database;
25
25
  }
26
+ /**
27
+ * Print SQL select statement with DB specific formatting.
28
+ *
29
+ * @param tableName - The name of the table to select from.
30
+ * @param columnNames - The columns to be selected.
31
+ * @param whereCondition - The WHERE clause to filter the results.
32
+ * @param orderCondition - The ORDER BY clause to sort the results.
33
+ * @param limitCondition - The LIMIT clause to limit the number of results.
34
+ *
35
+ */
36
+ printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
37
+ let result = "SELECT " + columnNames + " FROM " + tableName;
38
+ // OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
39
+ if (whereCondition != "") {
40
+ result += " WHERE " + whereCondition;
41
+ }
42
+ if (orderCondition != "") {
43
+ result += " ORDER BY " + orderCondition;
44
+ }
45
+ if (limitCondition != "") {
46
+ result += " LIMIT " + limitCondition;
47
+ }
48
+ result += ";";
49
+ // OINOLog.debug("OINODb.printSqlSelect", {result:result})
50
+ return result;
51
+ }
26
52
  }
27
53
  exports.OINODb = OINODb;
@@ -9,6 +9,7 @@ exports.OINODbApi = exports.OINODbHtmlTemplate = exports.OINODbApiResult = void
9
9
  const index_js_1 = require("./index.js");
10
10
  const types_1 = require("@oino-ts/types");
11
11
  const hashid_1 = require("@oino-ts/hashid");
12
+ const types_2 = require("@oino-ts/types");
12
13
  const API_EMPTY_PARAMS = { sqlParams: {} };
13
14
  /**
14
15
  * OINO API request result object with returned data and/or http status code/message and
@@ -38,7 +39,7 @@ class OINODbApiResult extends types_1.OINOResult {
38
39
  * @param headers Headers to include in the response
39
40
  *
40
41
  */
41
- async createResponseFromResult(headers = {}) {
42
+ async getResponse(headers = {}) {
42
43
  let response = null;
43
44
  if (this.success && this.data) {
44
45
  const body = await this.data.writeString(this.params.responseType);
@@ -89,11 +90,13 @@ class OINODbHtmlTemplate extends index_js_1.OINOHtmlTemplate {
89
90
  for (let i = 0; i < datamodel.fields.length; i++) {
90
91
  const f = datamodel.fields[i];
91
92
  let value = f.serializeCell(row[i]);
92
- if (f.fieldParams.isPrimaryKey) {
93
+ if (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) {
93
94
  if (value && (f instanceof index_js_1.OINONumberDataField) && (datamodel.api.hashid)) {
94
95
  value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed);
95
96
  }
96
- primary_key_values.push(value || "");
97
+ if (f.fieldParams.isPrimaryKey) {
98
+ primary_key_values.push(value || "");
99
+ }
97
100
  }
98
101
  // OINOLog.debug("renderFromDbData replace field value", {field:f.name, value:value })
99
102
  this.setVariableFromValue(f.name, value || "");
@@ -182,9 +185,10 @@ class OINODbApi {
182
185
  //logDebug("OINODbApi.validateHttpValues", {result:result})
183
186
  }
184
187
  async _doGet(result, id, params) {
185
- const sql = this.datamodel.printSqlSelect(id, params.sqlParams || {});
186
- // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
188
+ let sql = "";
187
189
  try {
190
+ sql = this.datamodel.printSqlSelect(id, params.sqlParams || {});
191
+ // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
188
192
  const sql_res = await this.db.sqlSelect(sql);
189
193
  // OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
190
194
  if (sql_res.hasErrors()) {
@@ -287,14 +291,16 @@ class OINODbApi {
287
291
  let result = new OINODbApiResult(params);
288
292
  let rows = [];
289
293
  if ((method == "POST") || (method == "PUT")) {
290
- if (Array.isArray(body)) {
291
- rows = body;
292
- }
293
- else if (typeof (body) == "object") {
294
- rows = [index_js_1.OINODbFactory.createRowFromObject(this.datamodel, body)];
294
+ try {
295
+ if (Array.isArray(body)) {
296
+ rows = body;
297
+ }
298
+ else {
299
+ rows = types_2.OINOParser.createRows(this.datamodel, body, params);
300
+ }
295
301
  }
296
- else if (typeof (body) == "string") {
297
- rows = index_js_1.OINODbFactory.createRows(this.datamodel, body, params);
302
+ catch (e) {
303
+ result.setError(400, "Invalid data: " + e.message, "DoRequest");
298
304
  }
299
305
  // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
300
306
  }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.OINODatetimeDataField = exports.OINOBlobDataField = exports.OINONumberDataField = exports.OINOBooleanDataField = exports.OINOStringDataField = exports.OINODbDataField = void 0;
9
+ const index_js_1 = require("./index.js");
9
10
  /**
10
11
  * Base class for a column of data responsible for appropriatelly serializing/deserializing the data.
11
12
  *
@@ -54,6 +55,9 @@ class OINODbDataField {
54
55
  if (this.fieldParams.isPrimaryKey) {
55
56
  params += "PK ";
56
57
  }
58
+ if (this.fieldParams.isForeignKey) {
59
+ params += "FK ";
60
+ }
57
61
  if (this.fieldParams.isAutoInc) {
58
62
  params += "AUTOINC ";
59
63
  }
@@ -242,7 +246,12 @@ class OINONumberDataField extends OINODbDataField {
242
246
  return null;
243
247
  }
244
248
  else {
245
- return Number.parseFloat(value);
249
+ const result = parseFloat(value);
250
+ if (isNaN(result)) {
251
+ index_js_1.OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", { value: value });
252
+ throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'"); // incorrectly formatted data could be a security risk, abort processing
253
+ }
254
+ return result;
246
255
  }
247
256
  }
248
257
  }
@@ -291,7 +300,7 @@ class OINOBlobDataField extends OINODbDataField {
291
300
  */
292
301
  deserializeCell(value) {
293
302
  if (value == null) {
294
- return new Buffer(0);
303
+ return Buffer.alloc(0);
295
304
  }
296
305
  else {
297
306
  return Buffer.from(value, 'base64'); // Blob-field data is base64 encoded and converted internally to UInt8Array / Buffer
@@ -220,27 +220,22 @@ class OINODbDataModel {
220
220
  *
221
221
  */
222
222
  printSqlSelect(id, params) {
223
- let result = "SELECT " + this._printSqlColumnNames() + " FROM " + this.api.db.printSqlTablename(this.api.params.tableName);
224
- const filter_sql = params.filter?.toSql(this) || "";
223
+ const column_names = this._printSqlColumnNames();
225
224
  const order_sql = params.order?.toSql(this) || "";
226
225
  const limit_sql = params.limit?.toSql(this) || "";
227
- // OINOLog.debug("OINODbDataModel.printSqlSelect", {select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
226
+ const filter_sql = params.filter?.toSql(this) || "";
227
+ let where_sql = "";
228
+ // OINOLog.debug("OINODbDataModel.printSqlSelect", {id:id, select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
228
229
  if ((id != null) && (id != "") && (filter_sql != "")) {
229
- result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql;
230
+ where_sql = this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql;
230
231
  }
231
232
  else if ((id != null) && (id != "")) {
232
- result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id);
233
+ where_sql = this._printSqlPrimaryKeyCondition(id);
233
234
  }
234
235
  else if (filter_sql != "") {
235
- result += "\nWHERE " + filter_sql;
236
- }
237
- if (order_sql) {
238
- result += "\nORDER BY " + order_sql;
239
- }
240
- if (limit_sql) {
241
- result += "\nLIMIT " + limit_sql;
236
+ where_sql = filter_sql;
242
237
  }
243
- result += ";";
238
+ const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql);
244
239
  // OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
245
240
  return result;
246
241
  }
@@ -111,361 +111,5 @@ class OINODbFactory {
111
111
  // OINOLog.debug("createParamsFromRequest", {params:result})
112
112
  return result;
113
113
  }
114
- static _findCsvLineEnd(csvData, start) {
115
- const n = csvData.length;
116
- if (start >= n) {
117
- return start;
118
- }
119
- let end = start;
120
- let quote_open = false;
121
- while (end < n) {
122
- if (csvData[end] == "\"") {
123
- if (!quote_open) {
124
- quote_open = true;
125
- }
126
- else if ((end < n - 1) && (csvData[end + 1] == "\"")) {
127
- end++;
128
- }
129
- else {
130
- quote_open = false;
131
- }
132
- }
133
- else if ((!quote_open) && (csvData[end] == "\r")) {
134
- return end;
135
- }
136
- end++;
137
- }
138
- return n;
139
- }
140
- static _parseCsvLine(csvLine) {
141
- let result = [];
142
- const n = csvLine.length;
143
- let start = 0;
144
- let end = 0;
145
- let quote_open = false;
146
- let has_quotes = false;
147
- let has_escaped_quotes = false;
148
- let found_field = false;
149
- while (end < n) {
150
- if (csvLine[end] == "\"") {
151
- if (!quote_open) {
152
- quote_open = true;
153
- }
154
- else if ((end < n - 1) && (csvLine[end + 1] == "\"")) {
155
- end++;
156
- has_escaped_quotes = true;
157
- }
158
- else {
159
- has_quotes = true;
160
- quote_open = false;
161
- }
162
- }
163
- if ((!quote_open) && ((end == n - 1) || (csvLine[end] == ","))) {
164
- found_field = true;
165
- if (end == n - 1) {
166
- end++;
167
- }
168
- }
169
- if (found_field) {
170
- // console.log("OINODB_csvParseLine: next field=" + csvLine.substring(start,end) + ", start="+start+", end="+end)
171
- let field_str;
172
- if (has_quotes) {
173
- field_str = csvLine.substring(start + 1, end - 1);
174
- }
175
- else if (start == end) {
176
- field_str = undefined;
177
- }
178
- else {
179
- field_str = csvLine.substring(start, end);
180
- if (field_str == "null") {
181
- field_str = null;
182
- }
183
- }
184
- result.push(field_str);
185
- has_quotes = false;
186
- has_escaped_quotes = true;
187
- found_field = false;
188
- start = end + 1;
189
- }
190
- end++;
191
- }
192
- return result;
193
- }
194
- static createRowFromCsv(datamodel, data) {
195
- let result = [];
196
- const n = data.length;
197
- let start = 0;
198
- let end = this._findCsvLineEnd(data, start);
199
- const header_str = data.substring(start, end);
200
- const headers = this._parseCsvLine(header_str);
201
- let field_to_header_mapping = new Array(datamodel.fields.length);
202
- let headers_found = false;
203
- for (let i = 0; i < field_to_header_mapping.length; i++) {
204
- field_to_header_mapping[i] = headers.indexOf(datamodel.fields[i].name);
205
- headers_found = headers_found || (field_to_header_mapping[i] >= 0);
206
- }
207
- // OINOLog.debug("createRowFromCsv", {headers:headers, field_to_header_mapping:field_to_header_mapping})
208
- if (!headers_found) {
209
- return result;
210
- }
211
- start = end + 1;
212
- end = start;
213
- while (end < n) {
214
- while ((start < n) && ((data[start] == "\r") || (data[start] == "\n"))) {
215
- start++;
216
- }
217
- if (start >= n) {
218
- return result;
219
- }
220
- end = this._findCsvLineEnd(data, start);
221
- const row_data = this._parseCsvLine(data.substring(start, end));
222
- const row = new Array(field_to_header_mapping.length);
223
- for (let i = 0; i < datamodel.fields.length; i++) {
224
- const field = datamodel.fields[i];
225
- let j = field_to_header_mapping[i];
226
- let value = row_data[j];
227
- if ((value === undefined) || (value === null)) { // null/undefined-decoding built into the parser
228
- row[i] = value;
229
- }
230
- else if ((j >= 0) && (j < row_data.length)) {
231
- value = index_js_1.OINOStr.decode(value, index_js_1.OINOContentType.csv);
232
- if (value && field.fieldParams.isPrimaryKey && (field instanceof index_js_1.OINONumberDataField) && (datamodel.api.hashid)) {
233
- value = datamodel.api.hashid.decode(value);
234
- }
235
- row[i] = field.deserializeCell(value);
236
- }
237
- else {
238
- row[i] = undefined;
239
- }
240
- }
241
- // console.log("createRowFromCsv: next row=" + row)
242
- result.push(row);
243
- start = end;
244
- end = start;
245
- }
246
- return result;
247
- }
248
- static _createRowFromJsonObj(obj, datamodel) {
249
- // console.log("createRowFromJsonObj: obj=" + JSON.stringify(obj))
250
- const fields = datamodel.fields;
251
- let result = new Array(fields.length);
252
- // console.log("createRowFromJsonObj: " + result)
253
- for (let i = 0; i < fields.length; i++) {
254
- const field = fields[i];
255
- let value = obj[field.name];
256
- // console.log("createRowFromJsonObj: key=" + field.name + ", val=" + val)
257
- if ((value === null) || (value === undefined)) { // must be checed first as null is an object
258
- result[i] = value;
259
- }
260
- else if (Array.isArray(value) || typeof value === "object") {
261
- result[i] = JSON.stringify(value).replaceAll("\"", "\\\""); // only single level deep objects, rest is handled as JSON-strings
262
- }
263
- else if (typeof value === "string") {
264
- value = index_js_1.OINOStr.decode(value, index_js_1.OINOContentType.json);
265
- if (value && field.fieldParams.isPrimaryKey && (field instanceof index_js_1.OINONumberDataField) && (datamodel.api.hashid)) {
266
- value = datamodel.api.hashid.decode(value);
267
- }
268
- result[i] = field.deserializeCell(value);
269
- }
270
- else {
271
- result[i] = value; // value types are passed as-is
272
- }
273
- // console.log("createRowFromJsonObj: result["+i+"]=" + result[i])
274
- }
275
- // console.log("createRowFromJsonObj: " + result)
276
- return result;
277
- }
278
- static _createRowFromJson(datamodel, data) {
279
- try {
280
- let result = [];
281
- // console.log("OINORowFactoryJson: data=" + data)
282
- const obj = JSON.parse(data);
283
- if (Array.isArray(obj)) {
284
- obj.forEach(row => {
285
- result.push(this._createRowFromJsonObj(row, datamodel));
286
- });
287
- }
288
- else {
289
- result.push(this._createRowFromJsonObj(obj, datamodel));
290
- }
291
- return result;
292
- }
293
- catch (e) {
294
- return [];
295
- }
296
- }
297
- static _findMultipartBoundary(formData, multipartBoundary, start) {
298
- let n = formData.indexOf(multipartBoundary, start);
299
- if (n >= 0) {
300
- n += multipartBoundary.length + 2;
301
- }
302
- else {
303
- n = formData.length;
304
- }
305
- return n;
306
- }
307
- static _parseMultipartLine(csvData, start) {
308
- let line_end = csvData.indexOf('\r\n', start);
309
- if (line_end >= start) {
310
- return csvData.substring(start, line_end);
311
- }
312
- else {
313
- return '';
314
- }
315
- }
316
- static _multipartHeaderRegex = /Content-Disposition\: (form-data|file); name=\"([^\"]+)\"(; filename=.*)?/i;
317
- static createRowFromFormdata(datamodel, data, multipartBoundary) {
318
- let result = [];
319
- const n = data.length;
320
- let start = this._findMultipartBoundary(data, multipartBoundary, 0);
321
- let end = this._findMultipartBoundary(data, multipartBoundary, start);
322
- // OINOLog.debug("createRowFromFormdata: enter", {start:start, end:end, multipartBoundary:multipartBoundary})
323
- const row = new Array(datamodel.fields.length);
324
- while (end < n) {
325
- // OINOLog.debug("createRowFromFormdata: next block", {start:start, end:end, block:data.substring(start, end)})
326
- let block_ok = true;
327
- let l = this._parseMultipartLine(data, start);
328
- // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
329
- start += l.length + 2;
330
- const header_matches = OINODbFactory._multipartHeaderRegex.exec(l);
331
- if (!header_matches) {
332
- index_js_1.OINOLog.warning("OINODbFactory.createRowFromFormdata: unsupported block skipped!", { header_line: l });
333
- block_ok = false;
334
- }
335
- else {
336
- const field_name = header_matches[2];
337
- const is_file = header_matches[3] != null;
338
- let is_base64 = false;
339
- const field_index = datamodel.findFieldIndexByName(field_name);
340
- // OINOLog.debug("createRowFromFormdata: header", {field_name:field_name, field_index:field_index, is_file:is_file})
341
- if (field_index < 0) {
342
- index_js_1.OINOLog.warning("OINODbFactory.createRowFromFormdata: form field not found and skipped!", { field_name: field_name });
343
- block_ok = false;
344
- }
345
- else {
346
- const field = datamodel.fields[field_index];
347
- l = this._parseMultipartLine(data, start);
348
- // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
349
- while (block_ok && (l != '')) {
350
- if (l.startsWith('Content-Type:') && (l.indexOf('multipart/mixed') >= 0)) {
351
- index_js_1.OINOLog.warning("OINODbFactory.createRowFromFormdata: mixed multipart files not supported and skipped!", { header_line: l });
352
- block_ok = false;
353
- }
354
- else if (l.startsWith('Content-Transfer-Encoding:') && (l.indexOf('BASE64') >= 0)) {
355
- is_base64 = true;
356
- }
357
- start += l.length + 2;
358
- l = this._parseMultipartLine(data, start);
359
- // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
360
- }
361
- start += 2;
362
- if (!block_ok) {
363
- index_js_1.OINOLog.warning("OINODbFactory.createRowFromFormdata: invalid block skipped", { field_name: field_name });
364
- }
365
- else if (start + multipartBoundary.length + 2 >= end) {
366
- // OINOLog.debug("OINODbFactory.createRowFromFormdata: null value", {field_name:field_name})
367
- row[field_index] = null;
368
- }
369
- else if (is_file) {
370
- const value = this._parseMultipartLine(data, start).trim();
371
- if (is_base64) {
372
- row[field_index] = field.deserializeCell(index_js_1.OINOStr.decode(value, index_js_1.OINOContentType.formdata));
373
- }
374
- else {
375
- row[field_index] = Buffer.from(value, "binary");
376
- }
377
- }
378
- else {
379
- let value = index_js_1.OINOStr.decode(this._parseMultipartLine(data, start).trim(), index_js_1.OINOContentType.formdata);
380
- // OINOLog.debug("OINODbFactory.createRowFromFormdata: parse form field", {field_name:field_name, value:value})
381
- if (value && field.fieldParams.isPrimaryKey && (field instanceof index_js_1.OINONumberDataField) && (datamodel.api.hashid)) {
382
- value = datamodel.api.hashid.decode(value);
383
- }
384
- row[field_index] = field.deserializeCell(value);
385
- }
386
- }
387
- }
388
- start = end;
389
- end = this._findMultipartBoundary(data, multipartBoundary, start);
390
- }
391
- // OINOLog.debug("createRowFromFormdata: next row", {row:row})
392
- result.push(row);
393
- return result;
394
- }
395
- static createRowFromUrlencoded(datamodel, data) {
396
- // OINOLog.debug("createRowFromUrlencoded: enter", {data:data})
397
- let result = [];
398
- const row = new Array(datamodel.fields.length);
399
- const data_parts = data.trim().split('&');
400
- for (let i = 0; i < data_parts.length; i++) {
401
- const param_parts = data_parts[i].split('=');
402
- // OINOLog.debug("createRowFromUrlencoded: next param", {param_parts:param_parts})
403
- if (param_parts.length == 2) {
404
- const key = index_js_1.OINOStr.decodeUrlencode(param_parts[0]) || "";
405
- const field_index = datamodel.findFieldIndexByName(key);
406
- if (field_index < 0) {
407
- index_js_1.OINOLog.info("createRowFromUrlencoded: param field not found", { field: key });
408
- }
409
- else {
410
- const field = datamodel.fields[field_index];
411
- let value = index_js_1.OINOStr.decode(param_parts[1], index_js_1.OINOContentType.urlencode);
412
- if (value && field.fieldParams.isPrimaryKey && (field instanceof index_js_1.OINONumberDataField) && (datamodel.api.hashid)) {
413
- value = datamodel.api.hashid.decode(value);
414
- }
415
- row[field_index] = field.deserializeCell(value);
416
- }
417
- }
418
- // const value = requestParams[]
419
- }
420
- // console.log("createRowFromUrlencoded: next row=" + row)
421
- result.push(row);
422
- return result;
423
- }
424
- /**
425
- * Create data rows from request body based on the datamodel.
426
- *
427
- * @param datamodel datamodel of the api
428
- * @param data data as a string
429
- * @param requestParams parameters
430
- *
431
- */
432
- static createRows(datamodel, data, requestParams) {
433
- if ((requestParams.requestType == index_js_1.OINOContentType.json) || (requestParams.requestType == undefined)) {
434
- return this._createRowFromJson(datamodel, data);
435
- }
436
- else if (requestParams.requestType == index_js_1.OINOContentType.csv) {
437
- return this.createRowFromCsv(datamodel, data);
438
- }
439
- else if (requestParams.requestType == index_js_1.OINOContentType.formdata) {
440
- return this.createRowFromFormdata(datamodel, data, requestParams.multipartBoundary || "");
441
- }
442
- else if (requestParams.requestType == index_js_1.OINOContentType.urlencode) {
443
- return this.createRowFromUrlencoded(datamodel, data);
444
- }
445
- else if (requestParams.requestType == index_js_1.OINOContentType.html) {
446
- index_js_1.OINOLog.error("HTML can't be used as an input content type!", { contentType: index_js_1.OINOContentType.html });
447
- return [];
448
- }
449
- else {
450
- index_js_1.OINOLog.error("Unrecognized input content type!", { contentType: requestParams.requestType });
451
- return [];
452
- }
453
- }
454
- /**
455
- * Create one data row from javascript object based on the datamodel.
456
- * NOTE! Data assumed to be unserialized i.e. of the native type (string, number, boolean, Buffer)
457
- *
458
- * @param datamodel datamodel of the api
459
- * @param data data as javascript object
460
- *
461
- */
462
- static createRowFromObject(datamodel, data) {
463
- const fields = datamodel.fields;
464
- let result = new Array(fields.length);
465
- for (let i = 0; i < fields.length; i++) {
466
- result[i] = data[fields[i].name];
467
- }
468
- return result;
469
- }
470
114
  }
471
115
  exports.OINODbFactory = OINODbFactory;