From 30b505aa8fa4f8b0640f3a233acb0d51fe5d4c32 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 17 Jan 2024 08:34:15 +0100 Subject: [PATCH 1/5] refs #6694 feat: add filter for postcodes --- back/models/postcode.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/back/models/postcode.js b/back/models/postcode.js index b08fdaa40..4c1bf7fd7 100644 --- a/back/models/postcode.js +++ b/back/models/postcode.js @@ -1,6 +1,38 @@ let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { + Self.remoteMethod('filter', { + description: + 'Find all postcodes of the model matched by postcode, town, province or country.', + accessType: 'READ', + returns: { + type: ['object'], + root: true, + }, + http: { + path: `/filter`, + verb: 'GET', + }, + }); + Self.filter = async(ctx, filter, options) => { + const {Postcode} = Self.app.models; + let {value} = ctx.where; + let limit = ctx.where?.limit ?? 30; + return await Postcode.rawSql(` + SELECT pc.code, t.name as town, p.name as province , c.country + FROM postCode pc + JOIN town t on t.id = pc.townFk + JOIN province p on p.id = t.provinceFk + JOIN country c on c.id = p.countryFk + WHERE + pc.code like '%${value}%' + or t.name like '%${value}%' + or p.name like '%${value}%' + or c.country like '%${value}%' + LIMIT ${limit} + `); + }; + Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') return new UserError(`This postcode already exists`); -- 2.40.1 From 71f387f626add3952755e0c83b6ead88bd435941 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 17 Jan 2024 14:15:21 +0100 Subject: [PATCH 2/5] refs #6694 feat: move filter remote method to own file --- back/methods/postcode/filter.js | 34 +++++++++++++++++++++++++++++++++ back/models/postcode.js | 33 +------------------------------- 2 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 back/methods/postcode/filter.js diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js new file mode 100644 index 000000000..53f25b7d4 --- /dev/null +++ b/back/methods/postcode/filter.js @@ -0,0 +1,34 @@ + +module.exports = Self => { + Self.remoteMethod('filter', { + description: + 'Find all postcodes of the model matched by postcode, town, province or country.', + accessType: 'READ', + returns: { + type: ['object'], + root: true, + }, + http: { + path: `/filter`, + verb: 'GET', + }, + }); + Self.filter = async(ctx, filter, options) => { + const {Postcode} = Self.app.models; + let {value} = ctx.where; + let limit = ctx.where?.limit ?? 30; + return await Postcode.rawSql(` + SELECT pc.code, t.name as town, p.name as province , c.country + FROM postCode pc + JOIN town t on t.id = pc.townFk + JOIN province p on p.id = t.provinceFk + JOIN country c on c.id = p.countryFk + WHERE + pc.code like '%${value}%' + or t.name like '%${value}%' + or p.name like '%${value}%' + or c.country like '%${value}%' + LIMIT ${limit} + `); + }; +}; diff --git a/back/models/postcode.js b/back/models/postcode.js index 4c1bf7fd7..63fd0657f 100644 --- a/back/models/postcode.js +++ b/back/models/postcode.js @@ -1,38 +1,7 @@ let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethod('filter', { - description: - 'Find all postcodes of the model matched by postcode, town, province or country.', - accessType: 'READ', - returns: { - type: ['object'], - root: true, - }, - http: { - path: `/filter`, - verb: 'GET', - }, - }); - Self.filter = async(ctx, filter, options) => { - const {Postcode} = Self.app.models; - let {value} = ctx.where; - let limit = ctx.where?.limit ?? 30; - return await Postcode.rawSql(` - SELECT pc.code, t.name as town, p.name as province , c.country - FROM postCode pc - JOIN town t on t.id = pc.townFk - JOIN province p on p.id = t.provinceFk - JOIN country c on c.id = p.countryFk - WHERE - pc.code like '%${value}%' - or t.name like '%${value}%' - or p.name like '%${value}%' - or c.country like '%${value}%' - LIMIT ${limit} - `); - }; - + require('../methods/postcode/filter.js')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') return new UserError(`This postcode already exists`); -- 2.40.1 From 92a3e841ed3fe6ec34ea6ff44339808bd6a71e30 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 17 Jan 2024 14:16:09 +0100 Subject: [PATCH 3/5] refs #6694 perf: change let to const --- back/methods/postcode/filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js index 53f25b7d4..0c97c61c5 100644 --- a/back/methods/postcode/filter.js +++ b/back/methods/postcode/filter.js @@ -15,8 +15,8 @@ module.exports = Self => { }); Self.filter = async(ctx, filter, options) => { const {Postcode} = Self.app.models; - let {value} = ctx.where; - let limit = ctx.where?.limit ?? 30; + const {value} = ctx.where; + const limit = ctx.where?.limit ?? 30; return await Postcode.rawSql(` SELECT pc.code, t.name as town, p.name as province , c.country FROM postCode pc -- 2.40.1 From 38c93e8c769ad79387b6a0e30bb92e036a7ffb7a Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 17 Jan 2024 14:49:35 +0100 Subject: [PATCH 4/5] refs #6694 test: add test --- back/methods/postcode/filter.js | 67 +++++++++++--- back/methods/postcode/specs/filter.spec.js | 103 +++++++++++++++++++++ 2 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 back/methods/postcode/specs/filter.spec.js diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js index 0c97c61c5..dcf11f80b 100644 --- a/back/methods/postcode/filter.js +++ b/back/methods/postcode/filter.js @@ -1,3 +1,6 @@ +const {ParameterizedSQL} = require('loopback-connector'); +const {buildFilter, mergeFilters} = require('vn-loopback/util/filter'); +// const {models} = require('vn-loopback/server/server'); module.exports = Self => { Self.remoteMethod('filter', { @@ -8,27 +11,67 @@ module.exports = Self => { type: ['object'], root: true, }, + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + http: {source: 'query'} + }, + { + arg: 'search', + type: 'string', + description: 'Value to filter', + http: {source: 'query'} + }, + ], http: { path: `/filter`, verb: 'GET', }, }); Self.filter = async(ctx, filter, options) => { - const {Postcode} = Self.app.models; - const {value} = ctx.where; - const limit = ctx.where?.limit ?? 30; - return await Postcode.rawSql(` - SELECT pc.code, t.name as town, p.name as province , c.country - FROM postCode pc + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const conn = Self.dataSource.connector; + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return {or: [ + {'pc.code': {like: `%${value}%`}}, + {'t.name': {like: `%${value}%`}}, + {'p.name': {like: `%${value}%`}}, + {'c.country': {like: `%${value}%`}} + ] + }; + } + }) ?? {}; + + filter = mergeFilters(ctx.args.filter, {where}); + + const stmts = []; + let stmt; + stmt = new ParameterizedSQL(` + SELECT + pc.code, + t.name as town, + p.name as province, + c.country + FROM + postCode pc JOIN town t on t.id = pc.townFk JOIN province p on p.id = t.provinceFk JOIN country c on c.id = p.countryFk - WHERE - pc.code like '%${value}%' - or t.name like '%${value}%' - or p.name like '%${value}%' - or c.country like '%${value}%' - LIMIT ${limit} `); + + stmt.merge(conn.makeSuffix(filter)); + const itemsIndex = stmts.push(stmt) - 1; + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + return itemsIndex === 0 ? result : result[itemsIndex]; }; }; diff --git a/back/methods/postcode/specs/filter.spec.js b/back/methods/postcode/specs/filter.spec.js new file mode 100644 index 000000000..c393b629a --- /dev/null +++ b/back/methods/postcode/specs/filter.spec.js @@ -0,0 +1,103 @@ +const {models} = require('vn-loopback/server/server'); + +describe('Postcode filter()', () => { + it('should retrieve with no filter', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toBeGreaterThan(0); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as postcode', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 46, + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(4); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as city', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'Alz', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(1); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as province', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'one', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(4); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as country', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'Ec', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(1); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); -- 2.40.1 From 145e72022a76e4686f711c3f7b4915218e849707 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 17 Jan 2024 14:58:55 +0100 Subject: [PATCH 5/5] refs #6694 perf: handle minor errors --- back/methods/postcode/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js index dcf11f80b..9986a16c9 100644 --- a/back/methods/postcode/filter.js +++ b/back/methods/postcode/filter.js @@ -50,7 +50,7 @@ module.exports = Self => { } }) ?? {}; - filter = mergeFilters(ctx.args.filter, {where}); + filter = mergeFilters(ctx.args?.filter ?? {}, {where}); const stmts = []; let stmt; -- 2.40.1