From 5c731998c3b6e809c8a2f3ecca6c2c9b0a592dfc Mon Sep 17 00:00:00 2001 From: Giuseppe Di Pinto Date: Tue, 23 Apr 2024 17:38:05 +0200 Subject: [PATCH 1/4] add service /features api mock --- src/config.ts | 1 + .../services/payloads/get-featured-items.ts | 101 ++++++++++++++++++ src/features/services/routers/featured.ts | 27 +++++ src/features/services/routers/index.ts | 1 + src/features/services/types/configuration.ts | 1 + src/persistence/services.ts | 4 + 6 files changed, 135 insertions(+) create mode 100644 src/features/services/payloads/get-featured-items.ts create mode 100644 src/features/services/routers/featured.ts diff --git a/src/config.ts b/src/config.ts index 0484a2dd..46d3c5f0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -191,6 +191,7 @@ const defaultConfig: IoDevServerConfig = { }, service: { response: { + featuredItemsResponseCode: 200, institutionsResponseCode: 200 } }, diff --git a/src/features/services/payloads/get-featured-items.ts b/src/features/services/payloads/get-featured-items.ts new file mode 100644 index 00000000..f88326f2 --- /dev/null +++ b/src/features/services/payloads/get-featured-items.ts @@ -0,0 +1,101 @@ +import { nonEmptyArray } from "fp-ts"; +import * as A from "fp-ts/Array"; +import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import { FeaturedItem } from "../../../../generated/definitions/services/FeaturedItem"; +import { FeaturedItems } from "../../../../generated/definitions/services/FeaturedItems"; +import ServicesDB from "../../../persistence/services"; +import { getInstitutionsResponsePayload } from "./get-institutions"; + +/** + * Returns a random ordered array subset. + * @param array starting array of type T + * @param size array subset size (if `size` greater than `array`, it returns empty array subset) + * @returns + */ +const getRandomArraySubset = (array: T[], size: number): T[] => + pipe( + O.some(array), + O.fromPredicate(arr => O.isSome(arr) && size <= array.length), + O.fold( + () => [], + () => { + const remainingItems = [...array]; + return pipe( + nonEmptyArray.range(1, size), + A.map(() => { + const randomIndex = Math.floor( + Math.random() * remainingItems.length + ); + const selectedItem = remainingItems[randomIndex]; + // eslint-disable-next-line functional/immutable-data + remainingItems.splice(randomIndex, 1); + return selectedItem; + }) + ); + } + ) + ); + +export const getFeaturedItemsResponsePayload = (): FeaturedItems => { + // take some casual national service + const selectedNationalServices = getRandomArraySubset( + ServicesDB.getNationalServices(), + 1 + ); + // take some casual special service + const selectedSpecialServices = getRandomArraySubset( + ServicesDB.getSpecialServices(), + 3 + ); + // take some casual institutions + const featuredIntitutions = getRandomArraySubset( + Array.from(getInstitutionsResponsePayload().institutions), + 1 + ); + + /** + * Reduced national services to FeaturedService[] (add organization_name for layout testing purpose) + */ + const featuredNationalServices: FeaturedItem[] = pipe( + selectedNationalServices, + A.reduce([] as FeaturedItem[], (accumulator, service) => [ + ...accumulator, + { + id: service.service_id, + name: service.service_name, + version: service.version, + organization_name: service.organization_name + } + ]) + ); + + /** + * Reduce special services to FeaturedService[] + */ + const featuredSpecialServices: FeaturedItem[] = pipe( + selectedSpecialServices, + A.reduce([] as FeaturedItem[], (accumulator, service) => [ + ...accumulator, + { + id: service.service_id, + name: service.service_name, + version: service.version + } + ]) + ); + + // returns randomly ordered featured items + const featuredItems = pipe( + [ + ...featuredSpecialServices, + ...featuredIntitutions, + ...featuredNationalServices + ], + arr => getRandomArraySubset(arr, arr.length) + ); + + return { + items: featuredItems + }; +}; diff --git a/src/features/services/routers/featured.ts b/src/features/services/routers/featured.ts new file mode 100644 index 00000000..ab841b0e --- /dev/null +++ b/src/features/services/routers/featured.ts @@ -0,0 +1,27 @@ +import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import { ioDevServerConfig } from "../../../config"; +import { addHandler } from "../../../payloads/response"; +import { getFeaturedItemsResponsePayload } from "../payloads/get-featured-items"; +import { addApiV2Prefix, serviceRouter } from "./router"; + +const serviceConfig = ioDevServerConfig.features.service; + +// Retrieve featured items +addHandler(serviceRouter, "get", addApiV2Prefix("/featured"), (_, res) => + pipe( + serviceConfig.response.featuredItemsResponseCode, + O.fromPredicate(statusCode => statusCode !== 200), + O.fold( + () => + pipe( + O.of(getFeaturedItemsResponsePayload()), + O.fold( + () => res.status(404), + featuredItems => res.status(200).json(featuredItems) + ) + ), + statusCode => res.sendStatus(statusCode) + ) + ) +); diff --git a/src/features/services/routers/index.ts b/src/features/services/routers/index.ts index 9e98f1bc..a4aed809 100644 --- a/src/features/services/routers/index.ts +++ b/src/features/services/routers/index.ts @@ -1,3 +1,4 @@ +import "./featured"; import "./institutions"; export { serviceRouter } from "./router"; diff --git a/src/features/services/types/configuration.ts b/src/features/services/types/configuration.ts index af7eba9c..8abc5122 100644 --- a/src/features/services/types/configuration.ts +++ b/src/features/services/types/configuration.ts @@ -5,6 +5,7 @@ export const ServiceConfiguration = t.interface({ // configure some API response error code response: t.interface({ // 200 success with payload + featuredItemsResponseCode: HttpResponseCode, institutionsResponseCode: HttpResponseCode }) }); diff --git a/src/persistence/services.ts b/src/persistence/services.ts index 26562c1e..bb8bd073 100644 --- a/src/persistence/services.ts +++ b/src/persistence/services.ts @@ -97,6 +97,8 @@ const getAllServices = () => [ ]; const getLocalServices = () => localServices.map(ls => ({ ...ls })); +const getNationalServices = () => nationalServices.map(ls => ({ ...ls })); +const getSpecialServices = () => specialServices.map(ls => ({ ...ls })); const getPreference = ( serviceId: ServiceId @@ -166,6 +168,8 @@ export default { deleteServices, getAllServices, getLocalServices, + getNationalServices, + getSpecialServices, getPreference, getService, getSummaries, From d9e02088cd4bbd71e16918fe12e14d95ce1de94f Mon Sep 17 00:00:00 2001 From: Giuseppe Di Pinto Date: Tue, 23 Apr 2024 18:32:24 +0200 Subject: [PATCH 2/4] add featuredItemsSize config param --- src/config.ts | 1 + src/features/services/payloads/get-featured-items.ts | 9 ++++++--- src/features/services/types/configuration.ts | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/config.ts b/src/config.ts index 46d3c5f0..977d0981 100644 --- a/src/config.ts +++ b/src/config.ts @@ -190,6 +190,7 @@ const defaultConfig: IoDevServerConfig = { sessionTTLinMS: 60000 }, service: { + featuredItemsSize: 5, response: { featuredItemsResponseCode: 200, institutionsResponseCode: 200 diff --git a/src/features/services/payloads/get-featured-items.ts b/src/features/services/payloads/get-featured-items.ts index f88326f2..1a8da33a 100644 --- a/src/features/services/payloads/get-featured-items.ts +++ b/src/features/services/payloads/get-featured-items.ts @@ -1,12 +1,15 @@ import { nonEmptyArray } from "fp-ts"; -import * as A from "fp-ts/Array"; +import * as A from "fp-ts/lib/Array"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import { FeaturedItem } from "../../../../generated/definitions/services/FeaturedItem"; import { FeaturedItems } from "../../../../generated/definitions/services/FeaturedItems"; +import { ioDevServerConfig } from "../../../config"; import ServicesDB from "../../../persistence/services"; import { getInstitutionsResponsePayload } from "./get-institutions"; +const featuredItemsSize = ioDevServerConfig.features.service.featuredItemsSize; + /** * Returns a random ordered array subset. * @param array starting array of type T @@ -16,7 +19,7 @@ import { getInstitutionsResponsePayload } from "./get-institutions"; const getRandomArraySubset = (array: T[], size: number): T[] => pipe( O.some(array), - O.fromPredicate(arr => O.isSome(arr) && size <= array.length), + O.fromPredicate(arr => O.isSome(arr) && size > 0 && size <= array.length), O.fold( () => [], () => { @@ -92,7 +95,7 @@ export const getFeaturedItemsResponsePayload = (): FeaturedItems => { ...featuredIntitutions, ...featuredNationalServices ], - arr => getRandomArraySubset(arr, arr.length) + arr => getRandomArraySubset(arr, featuredItemsSize) ); return { diff --git a/src/features/services/types/configuration.ts b/src/features/services/types/configuration.ts index 8abc5122..a06b1a1c 100644 --- a/src/features/services/types/configuration.ts +++ b/src/features/services/types/configuration.ts @@ -1,9 +1,12 @@ +import { WithinRangeNumber } from "@pagopa/ts-commons/lib/numbers"; import * as t from "io-ts"; import { HttpResponseCode } from "../../../types/httpResponseCode"; -export const ServiceConfiguration = t.interface({ +export const ServiceConfiguration = t.type({ + // configure number of featured items + featuredItemsSize: WithinRangeNumber(0, 6), // configure some API response error code - response: t.interface({ + response: t.type({ // 200 success with payload featuredItemsResponseCode: HttpResponseCode, institutionsResponseCode: HttpResponseCode From 859d6c7aa1296b6bdfbbf35c8f2f63b4d4f73516 Mon Sep 17 00:00:00 2001 From: Giuseppe Di Pinto Date: Wed, 24 Apr 2024 11:35:56 +0200 Subject: [PATCH 3/4] code improvements --- .../services/payloads/get-featured-items.ts | 71 +++++-------------- 1 file changed, 17 insertions(+), 54 deletions(-) diff --git a/src/features/services/payloads/get-featured-items.ts b/src/features/services/payloads/get-featured-items.ts index 1a8da33a..224c50cb 100644 --- a/src/features/services/payloads/get-featured-items.ts +++ b/src/features/services/payloads/get-featured-items.ts @@ -1,7 +1,6 @@ -import { nonEmptyArray } from "fp-ts"; import * as A from "fp-ts/lib/Array"; -import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; +import _ from "lodash"; import { FeaturedItem } from "../../../../generated/definitions/services/FeaturedItem"; import { FeaturedItems } from "../../../../generated/definitions/services/FeaturedItems"; import { ioDevServerConfig } from "../../../config"; @@ -10,67 +9,34 @@ import { getInstitutionsResponsePayload } from "./get-institutions"; const featuredItemsSize = ioDevServerConfig.features.service.featuredItemsSize; -/** - * Returns a random ordered array subset. - * @param array starting array of type T - * @param size array subset size (if `size` greater than `array`, it returns empty array subset) - * @returns - */ -const getRandomArraySubset = (array: T[], size: number): T[] => - pipe( - O.some(array), - O.fromPredicate(arr => O.isSome(arr) && size > 0 && size <= array.length), - O.fold( - () => [], - () => { - const remainingItems = [...array]; - return pipe( - nonEmptyArray.range(1, size), - A.map(() => { - const randomIndex = Math.floor( - Math.random() * remainingItems.length - ); - const selectedItem = remainingItems[randomIndex]; - // eslint-disable-next-line functional/immutable-data - remainingItems.splice(randomIndex, 1); - return selectedItem; - }) - ); - } - ) - ); - export const getFeaturedItemsResponsePayload = (): FeaturedItems => { // take some casual national service - const selectedNationalServices = getRandomArraySubset( + const selectedNationalServices = _.sampleSize( ServicesDB.getNationalServices(), 1 ); // take some casual special service - const selectedSpecialServices = getRandomArraySubset( + const selectedSpecialServices = _.sampleSize( ServicesDB.getSpecialServices(), 3 ); // take some casual institutions - const featuredIntitutions = getRandomArraySubset( + const featuredIntitutions = _.sampleSize( Array.from(getInstitutionsResponsePayload().institutions), 1 ); /** - * Reduced national services to FeaturedService[] (add organization_name for layout testing purpose) + * Map national services to FeaturedService[] (add organization_name for layout testing purpose) */ const featuredNationalServices: FeaturedItem[] = pipe( selectedNationalServices, - A.reduce([] as FeaturedItem[], (accumulator, service) => [ - ...accumulator, - { - id: service.service_id, - name: service.service_name, - version: service.version, - organization_name: service.organization_name - } - ]) + A.map(service => ({ + id: service.service_id, + name: service.service_name, + version: service.version, + organization_name: service.organization_name + })) ); /** @@ -78,14 +44,11 @@ export const getFeaturedItemsResponsePayload = (): FeaturedItems => { */ const featuredSpecialServices: FeaturedItem[] = pipe( selectedSpecialServices, - A.reduce([] as FeaturedItem[], (accumulator, service) => [ - ...accumulator, - { - id: service.service_id, - name: service.service_name, - version: service.version - } - ]) + A.map(service => ({ + id: service.service_id, + name: service.service_name, + version: service.version + })) ); // returns randomly ordered featured items @@ -95,7 +58,7 @@ export const getFeaturedItemsResponsePayload = (): FeaturedItems => { ...featuredIntitutions, ...featuredNationalServices ], - arr => getRandomArraySubset(arr, featuredItemsSize) + arr => _.sampleSize(arr, featuredItemsSize) ); return { From f3534dd7835e13921faf466b969b66dcd5488304 Mon Sep 17 00:00:00 2001 From: Giuseppe Di Pinto Date: Wed, 24 Apr 2024 13:10:09 +0200 Subject: [PATCH 4/4] code refactor --- src/features/services/payloads/get-featured-items.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/services/payloads/get-featured-items.ts b/src/features/services/payloads/get-featured-items.ts index 224c50cb..80a81aed 100644 --- a/src/features/services/payloads/get-featured-items.ts +++ b/src/features/services/payloads/get-featured-items.ts @@ -52,13 +52,13 @@ export const getFeaturedItemsResponsePayload = (): FeaturedItems => { ); // returns randomly ordered featured items - const featuredItems = pipe( + const featuredItems = _.sampleSize( [ ...featuredSpecialServices, ...featuredIntitutions, ...featuredNationalServices ], - arr => _.sampleSize(arr, featuredItemsSize) + featuredItemsSize ); return {