From 99bd04a3030da342628a0e4766f145cd063085ad Mon Sep 17 00:00:00 2001 From: Han Date: Thu, 5 Sep 2024 12:48:04 +0100 Subject: [PATCH] TOP-203 add in support for start_time, end_time and day filters in ORUK --- __tests__/unit/lib/filters.test.js | 91 +++++++++++++++ .../v1/services/routes/get-services.test.js | 106 ++++++++++++++++-- .../v1/services/routes/get-services.js | 39 ++++++- src/lib/filters.js | 44 +++++++- src/lib/index.js | 13 +++ src/lib/queries.js | 7 +- 6 files changed, 284 insertions(+), 16 deletions(-) diff --git a/__tests__/unit/lib/filters.test.js b/__tests__/unit/lib/filters.test.js index 345b0b9..cc9374e 100644 --- a/__tests__/unit/lib/filters.test.js +++ b/__tests__/unit/lib/filters.test.js @@ -208,3 +208,94 @@ describe("filterDays", () => { expect(filters.filterDays(days)).toEqual(expectedQuery) }) }) + +describe("filterStartTimeEndTimeDay", () => { + it("should return a query object if start_time, end_time, and day are provided", () => { + const startTime = ["22:00"] + const endTime = ["22:30"] + const day = ["Monday"] + const expectedQuery = { + $or: [ + { + "regular_schedules.opens_at": { $gte: "22:00" }, + "regular_schedules.closes_at": { $lte: "22:30" }, + "regular_schedules.weekday": "Monday", + }, + ], + } + expect(filters.filterStartTimeEndTimeDay(startTime, endTime, day)).toEqual( + expectedQuery + ) + }) + + it("should return a query object if only start_time is provided", () => { + const startTime = ["22:00"] + const endTime = [] + const day = [] + const expectedQuery = { + $or: [ + { + "regular_schedules.opens_at": { $gte: "22:00" }, + }, + ], + } + expect(filters.filterStartTimeEndTimeDay(startTime, endTime, day)).toEqual( + expectedQuery + ) + }) + + it("should return a query object if only end_time is provided", () => { + const startTime = [] + const endTime = ["22:30"] + const day = [] + const expectedQuery = { + $or: [ + { + "regular_schedules.closes_at": { $lte: "22:30" }, + }, + ], + } + expect(filters.filterStartTimeEndTimeDay(startTime, endTime, day)).toEqual( + expectedQuery + ) + }) + + it("should return a query object if only day is provided", () => { + const startTime = [] + const endTime = [] + const day = ["Monday"] + const expectedQuery = { + $or: [ + { + "regular_schedules.weekday": "Monday", + }, + ], + } + expect(filters.filterStartTimeEndTimeDay(startTime, endTime, day)).toEqual( + expectedQuery + ) + }) + + it("should return a query object if multiple sets of start_time, end_time, and day are provided", () => { + const startTime = ["22:00", "22:00"] + const endTime = ["22:30", "22:30"] + const day = ["Monday", "Monday"] + const expectedQuery = { + $or: [ + { + "regular_schedules.opens_at": { $gte: "22:00" }, + "regular_schedules.closes_at": { $lte: "22:30" }, + "regular_schedules.weekday": "Monday", + }, + { + "regular_schedules.opens_at": { $gte: "22:00" }, + "regular_schedules.closes_at": { $lte: "22:30" }, + "regular_schedules.weekday": "Monday", + }, + ], + } + expect(filters.filterStartTimeEndTimeDay(startTime, endTime, day)).toEqual( + expectedQuery + ) + }) +}) diff --git a/__tests__/unit/v1/services/routes/get-services.test.js b/__tests__/unit/v1/services/routes/get-services.test.js index 5a80894..23d95a3 100644 --- a/__tests__/unit/v1/services/routes/get-services.test.js +++ b/__tests__/unit/v1/services/routes/get-services.test.js @@ -28,7 +28,10 @@ describe("get-services", () => { taxonomies: [], needs: [], suitabilities: [], - days: [], + daysDeprecated: [], + startTime: [], + endTime: [], + day: [], accessibilities: [], only: [], minAge: undefined, @@ -268,22 +271,109 @@ describe("get-services", () => { }) }) - describe("days", () => { + describe("daysDeprecated", () => { it("should return undefined if days are not provided", async () => { - const { days } = await parseRequestParameters({}) - expect(days).toEqual([]) + const { daysDeprecated } = await parseRequestParameters({}) + expect(daysDeprecated).toEqual([]) }) it("should return a unique array multiple targets are passed through", async () => { - const { days } = await parseRequestParameters({ + const { daysDeprecated } = await parseRequestParameters({ days: ["a", "b,a"], }) - expect(new Set(days)).toEqual(new Set(["a", "b"])) + expect(new Set(daysDeprecated)).toEqual(new Set(["a", "b"])) }) it("should return a unique array one target is passed through", async () => { - const { days } = await parseRequestParameters({ + const { daysDeprecated } = await parseRequestParameters({ days: ["a,b"], }) - expect(new Set(days)).toEqual(new Set(["a", "b"])) + expect(new Set(daysDeprecated)).toEqual(new Set(["a", "b"])) + }) + }) + + describe("startTime", () => { + it("should return undefined if days are not provided", async () => { + const { startTime } = await parseRequestParameters({}) + expect(startTime).toEqual([]) + }) + it("should return a non unique array multiple targets are passed through", async () => { + const { startTime } = await parseRequestParameters({ + start_time: ["10:00", "10:00,11:00"], + }) + expect(startTime).toEqual(["10:00", "10:00", "11:00"]) + }) + it("should return a non unique array one target is passed through", async () => { + const { startTime } = await parseRequestParameters({ + start_time: ["10:00,10:00,11:00"], + }) + expect(startTime).toEqual(["10:00", "10:00", "11:00"]) + }) + }) + + describe("endTime", () => { + it("should return undefined if days are not provided", async () => { + const { endTime } = await parseRequestParameters({}) + expect(endTime).toEqual([]) + }) + it("should return a non unique array multiple targets are passed through", async () => { + const { endTime } = await parseRequestParameters({ + end_time: ["10:00", "10:00,11:00"], + }) + expect(endTime).toEqual(["10:00", "10:00", "11:00"]) + }) + it("should return a non unique array one target is passed through", async () => { + const { endTime } = await parseRequestParameters({ + end_time: ["10:00,10:00,11:00"], + }) + expect(endTime).toEqual(["10:00", "10:00", "11:00"]) + }) + }) + + describe("day", () => { + it("should return undefined if day are not provided", async () => { + const { day } = await parseRequestParameters({}) + expect(day).toEqual([]) + }) + it("should return a non unique array multiple targets are passed through", async () => { + const { day } = await parseRequestParameters({ + day: ["MO", "MO,TU"], + }) + expect(day).toEqual(["Monday", "Monday", "Tuesday"]) + }) + it("should return a non unique array one target is passed through", async () => { + const { day } = await parseRequestParameters({ + day: ["MO,MO,TU"], + }) + expect(day).toEqual(["Monday", "Monday", "Tuesday"]) + }) + }) + + describe("startTime, endTime, and day validation", () => { + it("should not throw an error if start_time and end_time are of equal lengths", async () => { + const params = { + start_time: ["10:00,11:00"], + end_time: ["12:00,13:00"], + } + await expect(parseRequestParameters(params)).resolves.not.toThrow() + }) + + it("should not throw an error if start_time, end_time, and day are of equal lengths", async () => { + const params = { + start_time: ["10:00,11:00"], + end_time: ["12:00,13:00"], + day: ["MO,TU"], + } + await expect(parseRequestParameters(params)).resolves.not.toThrow() + }) + + it("should throw an error if start_time, end_time, and day are of unequal lengths", async () => { + const params = { + start_time: ["10:00,11:00"], + end_time: ["12:00"], + day: ["MO,TU"], + } + await expect(parseRequestParameters(params)).rejects.toThrow( + "The number of start_time, end_time, and day parameters must be equal if more than one is provided" + ) }) }) diff --git a/src/controllers/v1/services/routes/get-services.js b/src/controllers/v1/services/routes/get-services.js index 3b04f42..48df58c 100644 --- a/src/controllers/v1/services/routes/get-services.js +++ b/src/controllers/v1/services/routes/get-services.js @@ -1,6 +1,11 @@ const filters = require("../../../../lib/filters") const queries = require("../../../../lib/queries") -const { calculateDistance, geocode, projection } = require("../../../../lib") +const { + calculateDistance, + geocode, + projection, + dayMapping, +} = require("../../../../lib") const { db } = require("../../../../db") const logger = require("../../../../../utils/logger") const locations = require("../../../../lib/locations") @@ -32,11 +37,18 @@ module.exports = { let accessibilities = queryParams?.accessibilities ? [].concat(queryParams.accessibilities) : [] - let days = queryParams?.days ? [].concat(queryParams.days) : [] + // days = days=Monday&days=Tuesday - deprecated + let daysDeprecated = queryParams?.days ? [].concat(queryParams.days) : [] let only = queryParams?.only ? [].concat(queryParams.only) : [] const minAge = parseInt(queryParams.min_age) || undefined const maxAge = parseInt(queryParams.max_age) || undefined + let startTime = queryParams?.start_time + ? [].concat(queryParams.start_time) + : [] + let endTime = queryParams?.end_time ? [].concat(queryParams.end_time) : [] + let day = queryParams?.day ? [].concat(queryParams.day) : [] + // not a param but we want to save on requests let interpreted_location @@ -51,9 +63,25 @@ module.exports = { accessibilities = [ ...new Set(accessibilities.flatMap(str => str.split(","))), ] - days = [...new Set(days.flatMap(str => str.split(",")))] + daysDeprecated = [...new Set(daysDeprecated.flatMap(str => str.split(",")))] only = [...new Set(only.flatMap(str => str.split(",")))] + // we dont de-dupe these as they are used in pairs + startTime = [...startTime.flatMap(str => str.split(","))] + endTime = [...endTime.flatMap(str => str.split(","))] + day = [...day.flatMap(str => str.split(","))] + // Convert day abbreviations to full names + day = day.map(d => dayMapping[d] || d) + + const lengths = [startTime.length, endTime.length, day.length].filter( + len => len > 0 + ) + if (lengths.length > 1 && !lengths.every(len => len === lengths[0])) { + throw new Error( + "The number of start_time, end_time, and day parameters must be equal if more than one is provided" + ) + } + // if we have a location then we can find lat lng if (location && !(lat && lng)) { try { @@ -80,7 +108,10 @@ module.exports = { taxonomies, needs, suitabilities, - days, + daysDeprecated, + startTime, + endTime, + day, accessibilities, only, minAge, diff --git a/src/lib/filters.js b/src/lib/filters.js index 3b43c40..51c89d4 100644 --- a/src/lib/filters.js +++ b/src/lib/filters.js @@ -181,9 +181,9 @@ module.exports = { /** * Days - * this has changed from previous iterations since the results returned wouldn't be accurate - * @TODO test http://localhost:3001/api/v1/services?accessibilities=accessible-toilet-facilities - * @TODO test http://localhost:3001/api/v1/services?accessibilities=accessible-toilet-facilities&accessibilities=wheelchair-accessible-entrance + * Returns the regular_schedules.weekday for the days + * @TODO test http://localhost:3001/api/v1/services?days=Monday + * @TODO test http://localhost:3001/api/v1/services?days=Monday&days=Tuesday * @param {*} needs * @returns */ @@ -195,4 +195,42 @@ module.exports = { } return {} }, + + /** + * Filters by opens_at, closes_at and day + * @TODO test http://localhost:3002/api/v1/services?start_time=22:00&end_time=22:30&day=MO + * @TODO test http://localhost:3002/api/v1/services?start_time=22:00 + * @TODO test http://localhost:3002/api/v1/services?end_time=22:30 + * @TODO test http://localhost:3002/api/v1/services?day=MO + * @TODO test http://localhost:3002/api/v1/services?start_time=22:00&end_time=22:30&day=MO&start_time=22:00&end_time=22:30&day=MO + * @param {*} startTime + * @param {*} endTime + * @param {*} day + * @returns + */ + filterStartTimeEndTimeDay: (startTime, endTime, day) => { + let orConditions = [] + const maxLength = Math.max(startTime.length, endTime.length, day.length) + + for (let i = 0; i < maxLength; i++) { + let condition = {} + if (startTime[i]) { + condition["regular_schedules.opens_at"] = { $gte: startTime[i] } + } + if (endTime[i]) { + condition["regular_schedules.closes_at"] = { $lte: endTime[i] } + } + if (day[i]) { + condition["regular_schedules.weekday"] = day[i] + } + orConditions.push(condition) + } + + let query = {} + if (orConditions.length > 0) { + query.$or = orConditions + } + + return query + }, } diff --git a/src/lib/index.js b/src/lib/index.js index 6373404..b9f3311 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -43,4 +43,17 @@ module.exports = { visible_from: 0, visible_to: 0, }, + + /** + * Used to map the day abbreviation to the full day names + */ + dayMapping: { + SU: "Sunday", + MO: "Monday", + TU: "Tuesday", + WE: "Wednesday", + TH: "Thursday", + FR: "Friday", + SA: "Saturday", + }, } diff --git a/src/lib/queries.js b/src/lib/queries.js index 176cf0e..c116939 100644 --- a/src/lib/queries.js +++ b/src/lib/queries.js @@ -81,7 +81,12 @@ module.exports = { filters.filterNeeds(parameters.needs), filters.filterSuitabilities(parameters.suitabilities), filters.filterAccessibilities(parameters.accessibilities), - filters.filterDays(parameters.days) + filters.filterDays(parameters.daysDeprecated), + filters.filterStartTimeEndTimeDay( + parameters.startTime, + parameters.endTime, + parameters.day + ) ) // clear empty values