From cfe63e96509ed8b5ca47475ebfb187f3929fac45 Mon Sep 17 00:00:00 2001
From: Taha Abdelmoutaleb Cherfia <taha.cherfia@holi.team>
Date: Fri, 18 Aug 2023 11:30:00 +0200
Subject: [PATCH] HOLI-5558: deprecate engagements query

---
 app/api_types.ts       |   3 +
 app/server.ts          |  37 ++++++++++--
 app/types.ts           |  19 ++++++-
 app/voltastics.ts      | 124 ++++++++++++++++++++++++++++++++++++++---
 app/voltastics_test.ts |  23 ++++----
 5 files changed, 183 insertions(+), 23 deletions(-)

diff --git a/app/api_types.ts b/app/api_types.ts
index 5c4d1b0..315a012 100644
--- a/app/api_types.ts
+++ b/app/api_types.ts
@@ -40,3 +40,6 @@ export enum ApiDefaults {
   MIN_RADIUS = 5,
   MAX_RADIUS = 50, // The largest radius in kilometers that the Voltastics API is capable of handling.
 }
+
+// TODO: Delete the following when app version 1.5.1 is no longer supported.
+export type ApiCitiesResponse = string[];
diff --git a/app/server.ts b/app/server.ts
index 0a8cb04..bb82b37 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -1,4 +1,5 @@
 import {
+  EngagementOpportunitiesParameters,
   EngagementParameters,
   EngagementsParameters,
   TrackEngagementViewParameters,
@@ -6,7 +7,9 @@ import {
 import { createSchema, createYoga, serve, useResponseCache } from "./deps.ts";
 import {
   fetchCategories,
+  fetchCities,
   fetchEngagement,
+  fetchEngagementOpportunities,
   fetchEngagements,
   trackEngagementView,
 } from "./voltastics.ts";
@@ -53,11 +56,22 @@ const typeDefs = `
     
     scalar GeoJSON
 
+    type City {
+      name: String!
+    }
+
+    type CitiesResponse {
+      data: [City]!
+    }
+
+  
     type Query {
         # uses offset-based pagination as described in https://www.apollographql.com/docs/react/pagination/offset-based
-        engagements(offset: Int!, limit: Int!, location: GeoJSON, category: String): EngagementsResponse!
+        engagementOpportunities(offset: Int!, limit: Int!, location: GeoJSON, category: String): EngagementsResponse!
         engagement(id: String!): Engagement
         categories: CategoriesResponse!
+        engagements(offset: Int!, limit: Int!, location: String, category: String, longitude: Float, latitude: Float, radiusKm: Float): EngagementsResponse! @deprecated(reason: "Use engagementOpportunities instead, as its support will be discontinued after 1.5.1.")
+        cities: CitiesResponse! @deprecated(reason: "It will no longer be supported after 1.5.1.")
     }
 
     type Mutation {
@@ -67,11 +81,11 @@ const typeDefs = `
 
 const createResolvers = (config: ServerConfig) => ({
   Query: {
-    engagements: (
+    engagementOpportunities: (
       // deno-lint-ignore no-explicit-any
       _parent: any,
-      parameters: EngagementsParameters,
-    ) => fetchEngagements(config)(parameters),
+      parameters: EngagementOpportunitiesParameters,
+    ) => fetchEngagementOpportunities(config)(parameters),
     engagement: (
       // deno-lint-ignore no-explicit-any
       _parent: any,
@@ -81,6 +95,15 @@ const createResolvers = (config: ServerConfig) => ({
       // deno-lint-ignore no-explicit-any
       _parent: any,
     ) => fetchCategories(config.voltastics),
+    engagements: (
+      // deno-lint-ignore no-explicit-any
+      _parent: any,
+      parameters: EngagementsParameters,
+    ) => fetchEngagements(config)(parameters),
+    cities: (
+      // deno-lint-ignore no-explicit-any
+      _parent: any,
+    ) => fetchCities(config.voltastics),
   },
   Mutation: {
     trackEngagementView: (
@@ -113,12 +136,16 @@ export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
       useResponseCache({
         session: () => null, // global cache, shared by all users
         ttlPerSchemaCoordinate: {
-          "Query.engagements": config.cacheTtlMsVoltastics ||
+          "Query.engagementOpportunities": config.cacheTtlMsVoltastics ||
             DEFAULT_CACHE_TTL_MS_VOLTASTICS,
           "Query.engagement": config.cacheTtlMsVoltastics ||
             DEFAULT_CACHE_TTL_MS_VOLTASTICS,
           "Query.categories": config.cacheTtlMsVoltastics ||
             DEFAULT_CACHE_TTL_MS_VOLTASTICS,
+          "Query.engagements": config.cacheTtlMsVoltastics ||
+            DEFAULT_CACHE_TTL_MS_VOLTASTICS,
+          "Query.cities": config.cacheTtlMsVoltastics ||
+            DEFAULT_CACHE_TTL_MS_VOLTASTICS,
         },
       }),
     ]
diff --git a/app/types.ts b/app/types.ts
index 908dc41..c160bac 100644
--- a/app/types.ts
+++ b/app/types.ts
@@ -32,7 +32,7 @@ export type EngagementsResponse = {
   data: Engagement[];
 };
 
-export type EngagementsParameters = {
+export type EngagementOpportunitiesParameters = {
   limit: number;
   offset: number;
   location?: PlaceDetails;
@@ -72,3 +72,20 @@ export type GeolocationGeoJSON = {
   properties: Place;
   geometry: GeolocationGeometry;
 };
+
+// TODO: Delete the following when app version 1.5.1 is no longer supported.
+export type City = {
+  name: string;
+};
+
+export type CitiesResponse = { data: City[] };
+
+export type EngagementsParameters = {
+  limit: number;
+  offset: number;
+  latitude?: number;
+  longitude?: number;
+  radiusKm?: number;
+  location?: string;
+  category?: string;
+};
diff --git a/app/voltastics.ts b/app/voltastics.ts
index e225900..8bc99e9 100644
--- a/app/voltastics.ts
+++ b/app/voltastics.ts
@@ -1,17 +1,22 @@
 import {
   ApiCategoriesResponse,
+  ApiCitiesResponse,
   ApiDefaults,
   ApiEngagementResponse,
   ApiRoutes,
   ApiSearchEngagement,
   ApiSearchEngagementsResponse,
 } from "./api_types.ts";
+import { sortCitiesAlphabetically } from "./helpers.ts";
 import { logger } from "./logging.ts";
 import { ServerConfig, VoltasticsConfig } from "./server.ts";
 import {
   CategoriesResponse,
   Category,
+  CitiesResponse,
+  City,
   Engagement,
+  EngagementOpportunitiesParameters,
   EngagementParameters,
   EngagementResponse,
   EngagementsParameters,
@@ -113,12 +118,12 @@ const fetchFromVoltasticsApi = (
   });
 };
 
-const buildVoltasticsEngagementsSearchParams = ({
+const buildVoltasticsEngagementOpportunitiesSearchParams = ({
   limit = 5,
   offset = 0,
   location,
   category,
-}: EngagementsParameters) => {
+}: EngagementOpportunitiesParameters) => {
   const params = new URLSearchParams();
   params.append("limit", limit.toString());
   params.append("offset", offset.toString());
@@ -142,12 +147,16 @@ const buildVoltasticsEngagementsSearchParams = ({
   return params;
 };
 
-export const fetchEngagements =
+export const fetchEngagementOpportunities =
   (config: ServerConfig) =>
-  (params: EngagementsParameters): Promise<EngagementsResponse> => {
-    const searchParams = buildVoltasticsEngagementsSearchParams(params);
+  (params: EngagementOpportunitiesParameters): Promise<EngagementsResponse> => {
+    const searchParams = buildVoltasticsEngagementOpportunitiesSearchParams(
+      params,
+    );
     const start = Date.now();
-    logger.info(`fetching engagements from ${config.voltastics.baseUrl}`);
+    logger.info(
+      `fetching engagement opportunities from ${config.voltastics.baseUrl}`,
+    );
 
     return fetchFromVoltasticsApi(
       config.voltastics,
@@ -158,7 +167,7 @@ export const fetchEngagements =
       .then(transformEngagementsResponse(config.imageProxyBaseUrl))
       .then((result) => {
         const duration = Date.now() - start;
-        logger.debug(`fetching engagements took ${duration} ms`);
+        logger.debug(`fetching engagement opportunities took ${duration} ms`);
         return result;
       })
       .catch((e) => {
@@ -251,3 +260,104 @@ export const fetchCategories = (
       throw e;
     });
 };
+
+// TODO: Delete the following when app version 1.5.1 is no longer supported.
+const transformCity = (city: string): City => {
+  return {
+    name: city,
+  };
+};
+
+const transformCitiesResponse = (
+  citiesResponse: ApiCitiesResponse,
+): CitiesResponse => {
+  return {
+    data: citiesResponse.sort(sortCitiesAlphabetically).map(transformCity),
+  };
+};
+
+const buildVoltasticsEngagementsSearchParams = ({
+  limit = 5,
+  offset = 0,
+  location = ApiDefaults.CITY,
+  category,
+  latitude,
+  longitude,
+  radiusKm,
+}: EngagementsParameters) => {
+  const params = new URLSearchParams();
+  params.append("limit", limit.toString());
+  params.append("offset", offset.toString());
+  params.append("city", location);
+
+  if (latitude) {
+    params.append("lat", latitude.toString());
+  }
+
+  if (longitude) {
+    params.append("lon", longitude.toString());
+  }
+
+  if (radiusKm) {
+    params.append("radius", radiusKm.toString());
+  }
+
+  if (category) {
+    params.append("use", category);
+  }
+
+  return params;
+};
+
+export const fetchEngagements =
+  (config: ServerConfig) =>
+  (params: EngagementsParameters): Promise<EngagementsResponse> => {
+    const searchParams = buildVoltasticsEngagementsSearchParams(params);
+    const start = Date.now();
+    logger.info(`fetching engagements from ${config.voltastics.baseUrl}`);
+
+    return fetchFromVoltasticsApi(
+      config.voltastics,
+      ApiRoutes.SEARCH_ENGAGEMENTS,
+      searchParams,
+    )
+      .then((result) => result.json())
+      .then(transformEngagementsResponse(config.imageProxyBaseUrl))
+      .then((result) => {
+        const duration = Date.now() - start;
+        logger.debug(`fetching engagements took ${duration} ms`);
+        return result;
+      })
+      .catch((e) => {
+        const duration = Date.now() - start;
+        logger.error(
+          `Error performing request to ${config.voltastics.baseUrl} after ${duration} ms: ${e.message}`,
+          { duration },
+        );
+        throw e;
+      });
+  };
+
+export const fetchCities = (
+  voltasticsConfig: VoltasticsConfig,
+): Promise<CitiesResponse> => {
+  const start = Date.now();
+  logger.info(`fetching cities from ${voltasticsConfig.baseUrl}`);
+
+  return fetchFromVoltasticsApi(voltasticsConfig, ApiRoutes.CITIES)
+    .then((result) => result.json())
+    .then(transformCitiesResponse)
+    .then((result) => {
+      const duration = Date.now() - start;
+      logger.debug(`fetching cities took ${duration} ms`);
+      return result;
+    })
+    .catch((e) => {
+      const duration = Date.now() - start;
+      logger.error(
+        `Error performing request to ${voltasticsConfig.baseUrl} after ${duration} ms: ${e.message}`,
+        { duration },
+      );
+      throw e;
+    });
+};
diff --git a/app/voltastics_test.ts b/app/voltastics_test.ts
index ef22f16..cae725f 100644
--- a/app/voltastics_test.ts
+++ b/app/voltastics_test.ts
@@ -35,7 +35,7 @@ import {
 import {
   fetchCategories,
   fetchEngagement,
-  fetchEngagements,
+  fetchEngagementOpportunities,
 } from "./voltastics.ts";
 
 const emptyResponse = {
@@ -66,14 +66,14 @@ const categoryFragment = `
 name
 `;
 
-const queryEngagements = async (
+const queryEngagementOpportunities = async (
   graphQLServer: GraphQLServer,
 ): Promise<EngagementsResponse> => {
   const promise = processGqlRequest(
     graphQLServer,
     `
-      query engagements($limit:Int!, $offset: Int!, $location: GeoJSON) {
-        engagements(limit: $limit, offset: $offset, location: $location) {
+      query engagementOpportunities($limit:Int!, $offset: Int!, $location: GeoJSON) {
+        engagementOpportunities(limit: $limit, offset: $offset, location: $location) {
           totalResults
           data {
             ${engagementFragment}
@@ -83,7 +83,7 @@ const queryEngagements = async (
     { limit: 10, offset: 20, lat: 48.1371079, lon: 11.5753822, radius: 14 },
   );
 
-  return (await promise)?.engagements as EngagementsResponse;
+  return (await promise)?.engagementOpportunities as EngagementsResponse;
 };
 
 const queryEngagement = async (
@@ -145,7 +145,7 @@ describe("voltastics", () => {
     it("calls api with correct parameters", async () => {
       fetchStub = stubFetch(emptyResponse);
 
-      await fetchEngagements(serverConfigMock)({
+      await fetchEngagementOpportunities(serverConfigMock)({
         limit: 10,
         offset: 20,
         location: Munich,
@@ -164,7 +164,10 @@ describe("voltastics", () => {
     it("calls api with default location parameter", async () => {
       fetchStub = stubFetch(emptyResponse);
 
-      await fetchEngagements(serverConfigMock)({ limit: 10, offset: 20 });
+      await fetchEngagementOpportunities(serverConfigMock)({
+        limit: 10,
+        offset: 20,
+      });
 
       const expectedUrl = new URL(
         "https://test.com/api/searchengagement?limit=10&offset=20&city=ALL_CITIES",
@@ -180,7 +183,7 @@ describe("voltastics", () => {
     it("correctly parses empty response", async () => {
       fetchStub = stubFetch(emptyResponse);
 
-      const result = await fetchEngagements(serverConfigMock)({
+      const result = await fetchEngagementOpportunities(serverConfigMock)({
         limit: 0,
         offset: 0,
         location: Munich,
@@ -198,7 +201,7 @@ describe("voltastics", () => {
         imageProxyBaseUrl: serverConfigMock.imageProxyBaseUrl,
       });
 
-      const result = await queryEngagements(graphQLServer);
+      const result = await queryEngagementOpportunities(graphQLServer);
 
       assertEquals(result, engagementsResponse);
     });
@@ -211,7 +214,7 @@ describe("voltastics", () => {
       );
 
       await assertRejects(() =>
-        fetchEngagements(serverConfigMock)({
+        fetchEngagementOpportunities(serverConfigMock)({
           limit: 0,
           offset: 0,
           location: Munich,
-- 
GitLab