From 20564f7df74204d2720931c6160d1fe89cdeb04d Mon Sep 17 00:00:00 2001 From: Vahid Al Date: Fri, 4 Nov 2022 02:01:30 +0330 Subject: [PATCH 1/3] API docs for bulk endpoints added --- core/src/swagger/index.js | 9 ++ core/src/swagger/swagger.json | 161 ++++++++++++++++++++++++++++++++++ docs/api/rows-examples.md | 86 ++++++++++++++++++ 3 files changed, 256 insertions(+) diff --git a/core/src/swagger/index.js b/core/src/swagger/index.js index a55acf7..3f06825 100644 --- a/core/src/swagger/index.js +++ b/core/src/swagger/index.js @@ -110,6 +110,15 @@ const doc = { fields: [{ $ref: '#/definitions/Field' }], }, + BulkUpdateRowsRequestBody: { + pks: [1, 2, 3], + fields: [{ $ref: '#/definitions/Field' }], + }, + + BulkDeleteRowsRequestBody: { + pks: [1, 2, 3], + }, + TransactionRequestBody: { $ref: '#/definitions/Transaction', }, diff --git a/core/src/swagger/swagger.json b/core/src/swagger/swagger.json index 9cba910..b40f98b 100644 --- a/core/src/swagger/swagger.json +++ b/core/src/swagger/swagger.json @@ -459,6 +459,129 @@ } } } + }, + "/api/tables/{name}/rows/bulk": { + "put": { + "tags": [ + "Rows" + ], + "summary": "Bulk Update Rows", + "description": "Bulk Update rows by primary key", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "type": "string", + "description": "Table name" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/BulkUpdateRowsRequestBody" + } + }, + { + "name": "_lookup_field", + "in": "query", + "description": "If you want to update row by any other field than primary key, use this parameter", + "required": false, + "type": "string" + }, + { + "name": "in", + "in": "query", + "type": "string" + }, + { + "name": "required", + "in": "query", + "type": "string" + }, + { + "name": "type", + "in": "query", + "type": "string" + }, + { + "name": "schema", + "in": "query", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "delete": { + "tags": [ + "Rows" + ], + "summary": "Bulk Delete Rows", + "description": "Bulks Delete rows by primary key", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "type": "string", + "description": "Table name" + }, + { + "name": "_lookup_field", + "in": "query", + "description": "If you want to delete row by any other field than primary key, use this parameter", + "required": false, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/BulkDeleteRowsRequestBody" + } + }, + { + "name": "in", + "in": "query", + "type": "string" + }, + { + "name": "required", + "in": "query", + "type": "string" + }, + { + "name": "type", + "in": "query", + "type": "string" + }, + { + "name": "schema", + "in": "query", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } } }, "definitions": { @@ -702,6 +825,44 @@ } } }, + "BulkUpdateRowsRequestBody": { + "type": "object", + "properties": { + "pks": { + "type": "array", + "example": [ + 1, + 2, + 3 + ], + "items": { + "type": "number" + } + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/Field" + } + } + } + }, + "BulkDeleteRowsRequestBody": { + "type": "object", + "properties": { + "pks": { + "type": "array", + "example": [ + 1, + 2, + 3 + ], + "items": { + "type": "number" + } + } + } + }, "TransactionRequestBody": { "$ref": "#/definitions/Transaction" } diff --git a/docs/api/rows-examples.md b/docs/api/rows-examples.md index 64108a8..1183698 100644 --- a/docs/api/rows-examples.md +++ b/docs/api/rows-examples.md @@ -211,3 +211,89 @@ Response #### Query Params - `_lookup_field` e.g. `?_lookup_field=ArtistId`, to delete the row by the ArtistId field. If not provided, the default lookup field is the primary key of the table. + + +## Bulk Endpoints + +### 6. Bulk Update Rows + +To update a row call `/tables//rows/bulk/` endpoint with `PUT` method. + +```bash +curl --request PUT \ + --url http://localhost:8000/api/tables/Album/rows/bulk \ + --header 'Content-Type: application/json' \ + --data '{ + "pks": [1, 2, 3], + "fields": { + "Title": "FaceElevate" + } +}' +``` + +Response + +```json +{ + "message": "Rows updated", + "data": { + "changes": 3, + "lastInsertRowid": 0 + } +} +``` + +#### Query Params + +- `_lookup_field` e.g. `?_lookup_field=ArtistId`, to update the row by the ArtistId field. If not provided, the default lookup field is the primary key of the table. + +#### Body Params +- `pks` e.g. + +```json +"pks": [1, 2, 3] +``` +- `fields` e.g. + +```json +"fields": { + // fields values to update +} +``` + +### 7. Bulk Delete Rows + +To delete a row call `/tables//rows/bulk/` endpoint with `DELETE` method. + +```bash +curl --request DELETE \ + --url http://localhost:8000/api/tables/PlaylistTrack/rows/bulk \ + --header 'Content-Type: application/json' \ + --data '{ + "pks": [5, 6, 7] +}' +``` + +Response + +```json +{ + "message": "Rows deleted", + "data": { + "changes": 1477, + "lastInsertRowid": 0 + } +} +``` + +#### Query Params + +- `_lookup_field` e.g. `?_lookup_field=ArtistId`, to delete the row by the ArtistId field. If not provided, the default lookup field is the primary key of the table. + + +#### Body Params +- `pks` e.g. + +```json +"pks": [1, 2, 3] +``` \ No newline at end of file From 5bba6ff3bb82e7621786d21bf53ef1e36ccf6cb8 Mon Sep 17 00:00:00 2001 From: Vahid Al Date: Fri, 4 Nov 2022 02:01:41 +0330 Subject: [PATCH 2/3] bulk endpoints added. --- core/src/controllers/rows.js | 158 ++++++++++++++++++++++++++++- core/src/middlewares/validation.js | 3 +- core/src/routes/rows.js | 10 ++ core/src/schemas/rows.js | 15 +++ 4 files changed, 184 insertions(+), 2 deletions(-) diff --git a/core/src/controllers/rows.js b/core/src/controllers/rows.js index 0c7c20a..81e16da 100644 --- a/core/src/controllers/rows.js +++ b/core/src/controllers/rows.js @@ -578,10 +578,94 @@ const updateRowInTableByPK = async (req, res) => { const query = `UPDATE ${tableName} SET ${fieldsString} WHERE ${lookupField} = '${pk}'`; try { - db.prepare(query).run(); + const data = db.prepare(query).run(); res.json({ message: 'Row updated', + data, + }); + } catch (error) { + res.status(400).json({ + message: error.message, + error: error, + }); + } +}; + +// Update a row by pk +const bulkUpdateRowsInTableByPK = async (req, res) => { + /* + #swagger.tags = ['Rows'] + #swagger.summary = 'Bulk Update Rows' + #swagger.description = 'Bulk Update rows by primary key' + #swagger.parameters['name'] = { + in: 'path', + description: 'Table name', + required: true, + type: 'string' + } + + #swagger.parameters['body'] = { + in: 'body', + required: true, + type: 'object', + schema: { $ref: "#/definitions/BulkUpdateRowsRequestBody" } + } + + #swagger.parameters['_lookup_field'] = { + in: 'query', + description: 'If you want to update row by any other field than primary key, use this parameter', + required: false, + type: 'string' + } +*/ + + const { name: tableName } = req.params; + const { pks, fields } = req.body; + const { _lookup_field } = req.query; + + let lookupField = _lookup_field; + + if (!_lookup_field) { + // find the primary key of the table + try { + lookupField = db + .prepare(`PRAGMA table_info(${tableName})`) + .all() + .find((field) => field.pk === 1).name; + } catch (error) { + return res.status(400).json({ + message: error.message, + error: error, + }); + } + } + + // wrap text values in quotes + const fieldsString = Object.keys(fields) + .map((key) => { + let value = fields[key]; + if (typeof value === 'string') { + value = `'${value}'`; + } + return `${key} = ${value}`; + }) + .join(', '); + + if (fieldsString === '') { + return res.status(400).json({ + message: 'No fields provided', + error: 'no_fields_provided', + }); + } + + const query = `UPDATE ${tableName} SET ${fieldsString} WHERE ${lookupField} in (${pks})`; + try { + const data = db.prepare(query).run(); + + res.json({ + message: 'Rows updated', + data, }); } catch (error) { res.status(400).json({ @@ -659,10 +743,82 @@ const deleteRowInTableByPK = async (req, res) => { } }; +// Bulk Delete rows by id +const bulkDeleteRowsInTableByPK = async (req, res) => { + /* + #swagger.tags = ['Rows'] + #swagger.summary = 'Bulk Delete Rows' + #swagger.description = 'Bulks Delete rows by primary key' + #swagger.parameters['name'] = { + in: 'path', + description: 'Table name', + required: true, + type: 'string' + } + #swagger.parameters['_lookup_field'] = { + in: 'query', + description: 'If you want to delete row by any other field than primary key, use this parameter', + required: false, + type: 'string' + } + + #swagger.parameters['body'] = { + in: 'body', + required: true, + type: 'object', + schema: { $ref: "#/definitions/BulkDeleteRowsRequestBody" } + } + */ + const { name: tableName } = req.params; + const { _lookup_field } = req.query; + const { pks } = req.body; + + let lookupField = _lookup_field; + + if (!_lookup_field) { + // find the primary key of the table + try { + lookupField = db + .prepare(`PRAGMA table_info(${tableName})`) + .all() + .find((field) => field.pk === 1).name; + } catch (error) { + return res.status(400).json({ + message: error.message, + error: error, + }); + } + } + + const query = `DELETE FROM ${tableName} WHERE ${lookupField} in (${pks})`; + + try { + const data = db.prepare(query).run(); + + if (data.changes === 0) { + res.status(404).json({ + error: 'not_found', + }); + } else { + res.json({ + message: 'Rows deleted', + data, + }); + } + } catch (error) { + res.status(400).json({ + message: error.message, + error: error, + }); + } +}; + module.exports = { listTableRows, insertRowInTable, getRowInTableByPK, updateRowInTableByPK, + bulkUpdateRowsInTableByPK, deleteRowInTableByPK, + bulkDeleteRowsInTableByPK, }; diff --git a/core/src/middlewares/validation.js b/core/src/middlewares/validation.js index fccbf00..5c927c2 100644 --- a/core/src/middlewares/validation.js +++ b/core/src/middlewares/validation.js @@ -1,6 +1,7 @@ const validator = (schema) => (req, res, next) => { const data = { ...req.body, ...req.params, ...req.query }; - const { error } = schema.validate(data); + const { value, error } = schema.validate(data); + if (error) { res.status(400).json({ message: error.message, diff --git a/core/src/routes/rows.js b/core/src/routes/rows.js index ec3a23e..a1e2de0 100644 --- a/core/src/routes/rows.js +++ b/core/src/routes/rows.js @@ -21,11 +21,21 @@ router.get( validator(schema.getRowInTableByPK), controllers.getRowInTableByPK ); +router.put( + '/:name/rows/bulk', + validator(schema.bulkUpdateRowsInTableByPK), + controllers.bulkUpdateRowsInTableByPK +); router.put( '/:name/rows/:pk', validator(schema.updateRowInTableByPK), controllers.updateRowInTableByPK ); +router.delete( + '/:name/rows/bulk', + validator(schema.bulkDeleteRowsInTableByPK), + controllers.bulkDeleteRowsInTableByPK +); router.delete( '/:name/rows/:pk', validator(schema.deleteRowInTableByPK), diff --git a/core/src/schemas/rows.js b/core/src/schemas/rows.js index 8194efe..d2c395b 100644 --- a/core/src/schemas/rows.js +++ b/core/src/schemas/rows.js @@ -31,16 +31,31 @@ const updateRowInTableByPK = Joi.object({ _lookup_field: Joi.string().min(3).max(30), }); +const bulkUpdateRowsInTableByPK = Joi.object({ + name: Joi.string().min(3).max(30).required(), + pks: Joi.array().items(Joi.required()).required(), + fields: Joi.object().required(), + _lookup_field: Joi.string().min(3).max(30), +}); + const deleteRowInTableByPK = Joi.object({ name: Joi.string().min(3).max(30).required(), pk: Joi.string().required(), _lookup_field: Joi.string().min(3).max(30), }); +const bulkDeleteRowsInTableByPK = Joi.object({ + name: Joi.string().min(3).max(30).required(), + pks: Joi.array().items(Joi.required()).required(), + _lookup_field: Joi.string().min(3).max(30), +}); + module.exports = { listTableRows, insertRowInTable, getRowInTableByPK, updateRowInTableByPK, + bulkUpdateRowsInTableByPK, deleteRowInTableByPK, + bulkDeleteRowsInTableByPK, }; From 9cd55d012a904c64985f57988ce5e51bce035d98 Mon Sep 17 00:00:00 2001 From: Vahid Al Date: Fri, 4 Nov 2022 02:03:41 +0330 Subject: [PATCH 3/3] version bumped to v0.0.8 --- core/package.json | 2 +- core/src/swagger/swagger.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/package.json b/core/package.json index 96b77de..190ba1b 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "soul-cli", - "version": "0.0.7", + "version": "0.0.8", "description": "A SQLite RESTful server", "main": "src/server.js", "bin": { diff --git a/core/src/swagger/swagger.json b/core/src/swagger/swagger.json index b40f98b..18d9289 100644 --- a/core/src/swagger/swagger.json +++ b/core/src/swagger/swagger.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "0.0.7", + "version": "0.0.8", "title": "Soul API", "description": "API Documentation for Soul, a simple SQLite RESTful server. " },