Merge pull request #132 from TahaKhanAbdalli/filter_array_in_query

added feature to also include array as filter in GET /:name/rows route
This commit is contained in:
Vahid Al
2023-08-17 15:33:32 +03:30
committed by GitHub
4 changed files with 63 additions and 36 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "soul-cli",
"version": "0.5.2",
"version": "0.6.0",
"description": "A SQLite REST and Realtime server",
"main": "src/server.js",
"bin": {

View File

@@ -66,7 +66,7 @@ const listTableRows = async (req, res) => {
_ordering,
_schema,
_extend,
_filters = '',
_filters = ''
} = req.query;
const page = parseInt(_page);
@@ -78,8 +78,11 @@ const listTableRows = async (req, res) => {
// e.g. ?_filters=name:John,age:20
// will filter by name like '%John%' and age like '%20%'
let filters = [];
// split the filters by comma(,) except when in an array
const re = /(\w+:?(\[.*?\]|\w+)?)/g;
try {
filters = _filters.split(',').map((filter) => {
filters = _filters.match(re)?.map((filter) => {
let [key, value] = filter.split(':');
let field = key.split('__')[0];
@@ -89,7 +92,7 @@ const listTableRows = async (req, res) => {
fieldOperator = 'eq';
} else if (!operators[fieldOperator]) {
throw new Error(
`Invalid field operator "${fieldOperator}" for field "${field}". You can only use the following operators after the "${field}" field: __lt, __gt, __lte, __gte, __eq, __neq.`
`Invalid field operator '${fieldOperator}' for field '${field}'. You can only use the following operators after the '${field}' field: __lt, __gt, __lte, __gte, __eq, __neq.`
);
}
@@ -103,7 +106,7 @@ const listTableRows = async (req, res) => {
} catch (error) {
return res.status(400).json({
message: error.message,
error: error,
error: error
});
}
@@ -114,15 +117,18 @@ const listTableRows = async (req, res) => {
whereString += ' WHERE ';
whereString += filters
.map((filter) => {
let query;
if (filter.value != null) {
query = `${tableName}.${filter.field} ${filter.operator} ?`;
whereStringValues.push(filter.value);
if (filter.value) {
if (filter.value.startsWith('[') && filter.value.endsWith(']')) {
const arrayValues = filter.value.slice(1, -1).split(',');
return `${tableName}.${filter.field} IN (${arrayValues
.map((val) => `'${val}'`)
.join(', ')})`;
} else {
return `${tableName}.${filter.field} ${filter.operator} '${filter.value}'`;
}
} else {
query = `${tableName}.${filter.field} ${filter.operator}`;
return `${tableName}.${filter.field} ${filter.operator}`;
}
return query;
})
.join(' AND ');
params = `_filters=${_filters}&`;
@@ -149,7 +155,7 @@ const listTableRows = async (req, res) => {
} catch (error) {
return res.status(400).json({
message: error.message,
error: error,
error: error
});
}
}
@@ -265,7 +271,7 @@ const listTableRows = async (req, res) => {
if (foreignKeyError.error) {
return res.status(400).json({
message: foreignKeyError.message,
error: foreignKeyError.error,
error: foreignKeyError.error
});
}
}
@@ -319,12 +325,12 @@ const listTableRows = async (req, res) => {
data,
total,
next,
previous,
previous
});
} catch (error) {
res.status(400).json({
message: error.message,
error: error,
error: error
});
}
};
@@ -346,7 +352,7 @@ const insertRowInTable = async (req, res, next) => {
in: 'body',
required: true,
type: 'object',
schema: { $ref: "#/definitions/InsertRowRequestBody" }
schema: { $ref: '#/definitions/InsertRowRequestBody' }
}
*/
@@ -365,7 +371,7 @@ const insertRowInTable = async (req, res, next) => {
#swagger.responses[201] = {
description: 'Row inserted successfully',
schema: {
$ref: "#/definitions/InsertRowSuccessResponse"
$ref: '#/definitions/InsertRowSuccessResponse'
}
}
*/
@@ -386,7 +392,7 @@ const insertRowInTable = async (req, res, next) => {
#swagger.responses[400] = {
description: 'Bad request',
schema: {
$ref: "#/definitions/InsertRowErrorResponse"
$ref: '#/definitions/InsertRowErrorResponse'
}
}
*/
@@ -613,7 +619,7 @@ const updateRowInTableByPK = async (req, res, next) => {
in: 'body',
required: true,
type: 'object',
schema: { $ref: "#/definitions/UpdateRowRequestBody" }
schema: { $ref: '#/definitions/UpdateRowRequestBody' }
}
#swagger.parameters['_lookup_field'] = {

View File

@@ -102,19 +102,40 @@ describe('Rows Endpoints', () => {
expect(res.type).toEqual(expect.stringContaining('json'));
});
it('POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null', async () => {
const res = await requestWithSupertest
.post("/api/tables/users/rows")
.send({
fields: {
firstName: null,
lastName: "Doe",
email: null,
username: "Jane"
}
});
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining('json'));
expect(res.body).toHaveProperty('data');
});
it('POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null', async () => {
const res = await requestWithSupertest
.post('/api/tables/users/rows')
.send({
fields: {
firstName: null,
lastName: 'Doe',
email: null,
username: 'Jane'
}
});
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining('json'));
expect(res.body).toHaveProperty('data');
});
it('GET /tables/:name/rows should return values if any of the IDs from the array match the user ID.', async () => {
const res = await requestWithSupertest.get(
'/api/tables/users/rows?_filters=id:[2,3]'
);
expect(res.status).toEqual(200);
expect(res.body).toHaveProperty('data');
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data.length).toEqual(2);
});
it('GET /tables/:name/rows should return values if the provided ID matches the user ID.', async () => {
const res = await requestWithSupertest.get(
'/api/tables/users/rows?_filters=id:2,firstName:Michael,lastName:Lee'
);
expect(res.status).toEqual(200);
expect(res.body).toHaveProperty('data');
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data.length).toEqual(1);
});
});

View File

@@ -35,7 +35,7 @@ Response
- `_ordering` e.g. `?_ordering=-Title`, to order rows by title descending, or without `-` to sort ascending, e.g. `?_ordering=Title`
- `_schema` e.g. `?_schema=Title,ArtistId`, to get only the Title and ArtistId columns.
- `_extend` e.g. `?_extend=ArtistId`, to get the Artist object related to the Album.
- `_filters` e.g. `?_filters=ArtistId:1,Title:Rock`, to get only the rows where the ArtistId is 1 and the Title is Rock.
- `_filters` e.g. `?_filters=ArtistId:[1,2,3],Title:Rock`, to get only the rows where the ArtistId can be 1,2 or 3 and the Title is Rock.
NOTE: If you want to use comparison operators in the filter, you can use these operators after the field: `__eq, __neq, __lt, __gt, __lte, __gte, __null, __notNull` . For example, you can use `/invoices/rows?_filters=InvoiceId__neq:1,Total__gte:5,BillingPostalCode__notNull`
Example with query params