From 2f1fad28073810b0f5238b0e6639ebb0c0a0eb25 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sun, 6 Oct 2024 17:28:44 +0800 Subject: [PATCH 1/3] Add COE API for date ranges --- src/v1/routes/coe.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/v1/routes/coe.ts b/src/v1/routes/coe.ts index e8cbd9b..a73d2fe 100644 --- a/src/v1/routes/coe.ts +++ b/src/v1/routes/coe.ts @@ -5,7 +5,7 @@ import type { COEResult } from "../../types"; import { Collection, OrderBy } from "../../types"; import redis from "../../config/redis"; import { getLatestMonth } from "../../lib/getLatestMonth"; -import { parse } from "date-fns"; +import { isValid, parse } from "date-fns"; type QueryParams = { sort?: string; @@ -20,7 +20,7 @@ const app = new Hono(); app.get("/", async (c) => { const query = c.req.query() as QueryParams; - const { sort, orderBy, ...filterQuery } = query; + const { sort, orderBy, from, to, ...filterQuery } = query; const CACHE_KEY = `coe:${JSON.stringify(query)}`; const cachedData = await redis.get(CACHE_KEY); @@ -35,7 +35,23 @@ app.get("/", async (c) => { } const mongoQuery: Filter = {}; - if (!filterQuery.month) { + if (from || to) { + mongoQuery.month = {}; + + if (from) { + const fromDate = parse(from, "yyyy-MM", new Date()); + if (isValid(fromDate)) { + mongoQuery.month.$gte = from; + } + } + + if (to) { + const toDate = parse(to, "yyyy-MM", new Date()); + if (isValid(toDate)) { + mongoQuery.month.$lte = to; + } + } + } else if (!filterQuery.month) { const latestMonth = parse( await getLatestMonth(Collection.COE), "yyyy-MM", From 171b54d219d2f91ff79a07858f2c09633ee86444 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sun, 6 Oct 2024 17:42:05 +0800 Subject: [PATCH 2/3] Reduce cognitive complexity for the endpoints --- src/v1/routes/coe.ts | 88 ++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/src/v1/routes/coe.ts b/src/v1/routes/coe.ts index a73d2fe..b4ee395 100644 --- a/src/v1/routes/coe.ts +++ b/src/v1/routes/coe.ts @@ -18,40 +18,32 @@ const CACHE_TTL = 60 * 60 * 24; // 1 day in seconds const app = new Hono(); -app.get("/", async (c) => { - const query = c.req.query() as QueryParams; - const { sort, orderBy, from, to, ...filterQuery } = query; +const getCachedData = (cacheKey: string) => redis.get(cacheKey); - const CACHE_KEY = `coe:${JSON.stringify(query)}`; - const cachedData = await redis.get(CACHE_KEY); - if (cachedData) { - return c.json(cachedData); - } +const setCachedData = (cacheKey: string, data: T) => + redis.set(cacheKey, data, { ex: CACHE_TTL }); - let sortQuery: Sort = { month: -1, bidding_no: 1, vehicle_class: 1 }; +const buildSortQuery = (sort?: string, orderBy?: OrderBy): Sort => { if (sort) { const sortDirection = orderBy === OrderBy.DESC ? -1 : 1; - sortQuery = { [sort]: sortDirection } as Sort; + return { [sort]: sortDirection } as Sort; } + return { month: -1, bidding_no: 1, vehicle_class: 1 }; +}; + +const buildMongoQuery = async (query: QueryParams): Promise> => { + const { from, to, month, ...filterQuery } = query; + const mongoQuery: Filter = {}; - const mongoQuery: Filter = {}; if (from || to) { mongoQuery.month = {}; - - if (from) { - const fromDate = parse(from, "yyyy-MM", new Date()); - if (isValid(fromDate)) { - mongoQuery.month.$gte = from; - } + if (from && isValid(parse(from, "yyyy-MM", new Date()))) { + mongoQuery.month.$gte = from; } - - if (to) { - const toDate = parse(to, "yyyy-MM", new Date()); - if (isValid(toDate)) { - mongoQuery.month.$lte = to; - } + if (to && isValid(parse(to, "yyyy-MM", new Date()))) { + mongoQuery.month.$lte = to; } - } else if (!filterQuery.month) { + } else if (!month) { const latestMonth = parse( await getLatestMonth(Collection.COE), "yyyy-MM", @@ -62,46 +54,44 @@ app.get("/", async (c) => { latestMonth.getMonth() + 1, 1, ); - - const pastYearFormatted = pastYear.toISOString().slice(0, 7); // YYYY-MM format - const currentMonthFormatted = latestMonth.toISOString().slice(0, 7); // YYYY-MM format - mongoQuery.month = { - $gte: pastYearFormatted, - $lte: currentMonthFormatted, + $gte: pastYear.toISOString().slice(0, 7), + $lte: latestMonth.toISOString().slice(0, 7), }; } - for (const [key, value] of Object.entries(filterQuery)) { - mongoQuery[key] = value; - } + return { ...mongoQuery, ...filterQuery }; +}; + +const fetchData = (mongoQuery: Filter, sortQuery: Sort) => + db.collection(Collection.COE).find(mongoQuery).sort(sortQuery).toArray(); - const result = await db - .collection(Collection.COE) - .find(mongoQuery) - .sort(sortQuery) - .toArray(); +app.get("/", async (c) => { + const query = c.req.query() as QueryParams; + const CACHE_KEY = `coe:${JSON.stringify(query)}`; + const cachedData = await getCachedData(CACHE_KEY); + if (cachedData) return c.json(cachedData); - await redis.set(CACHE_KEY, result, { ex: CACHE_TTL }); + const sortQuery = buildSortQuery(query.sort, query.orderBy); + const mongoQuery = await buildMongoQuery(query); + const result = await fetchData(mongoQuery, sortQuery); + await setCachedData(CACHE_KEY, result); return c.json(result); }); app.get("/latest", async (c) => { const CACHE_KEY = `coe:latest`; - const cachedData = await redis.get(CACHE_KEY); - if (cachedData) { - return c.json(cachedData); - } + const cachedData = await getCachedData(CACHE_KEY); + if (cachedData) return c.json(cachedData); const latestMonth = await getLatestMonth(Collection.COE); - const result = await db - .collection(Collection.COE) - .find({ month: latestMonth }) - .sort({ bidding_no: 1, vehicle_class: 1 }) - .toArray(); + const result = await fetchData( + { month: latestMonth }, + { bidding_no: 1, vehicle_class: 1 }, + ); - await redis.set(CACHE_KEY, result, { ex: CACHE_TTL }); + await setCachedData(CACHE_KEY, result); return c.json(result); }); From 6fb0eb5b831e3aad9857e9a92b3a323fcb9f53c1 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sun, 6 Oct 2024 18:19:16 +0800 Subject: [PATCH 3/3] Fix filter and sorting not returning data properly --- src/v1/routes/coe.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/v1/routes/coe.ts b/src/v1/routes/coe.ts index b4ee395..a0964b0 100644 --- a/src/v1/routes/coe.ts +++ b/src/v1/routes/coe.ts @@ -23,16 +23,21 @@ const getCachedData = (cacheKey: string) => redis.get(cacheKey); const setCachedData = (cacheKey: string, data: T) => redis.set(cacheKey, data, { ex: CACHE_TTL }); -const buildSortQuery = (sort?: string, orderBy?: OrderBy): Sort => { +const buildSortQuery = ( + sort?: string, + orderBy: OrderBy = OrderBy.DESC, +): Sort => { + const defaultSort: Sort = { month: -1, bidding_no: 1, vehicle_class: 1 }; + if (sort) { - const sortDirection = orderBy === OrderBy.DESC ? -1 : 1; - return { [sort]: sortDirection } as Sort; + return { ...defaultSort, [sort]: orderBy === OrderBy.ASC ? 1 : -1 } as Sort; } - return { month: -1, bidding_no: 1, vehicle_class: 1 }; + + return defaultSort; }; const buildMongoQuery = async (query: QueryParams): Promise> => { - const { from, to, month, ...filterQuery } = query; + const { sort, orderBy, from, to, ...filterQuery } = query; const mongoQuery: Filter = {}; if (from || to) { @@ -43,7 +48,7 @@ const buildMongoQuery = async (query: QueryParams): Promise> => { if (to && isValid(parse(to, "yyyy-MM", new Date()))) { mongoQuery.month.$lte = to; } - } else if (!month) { + } else if (!filterQuery.month) { const latestMonth = parse( await getLatestMonth(Collection.COE), "yyyy-MM",