From 1334a3c858c83a730fad624351f946d3f5b680d7 Mon Sep 17 00:00:00 2001
From: Ole Langbehn <ole.langbehn@inoio.de>
Date: Wed, 6 Sep 2023 15:59:32 +0200
Subject: [PATCH] HOLI-3881 add fake mode for local development without secrets

---
 .envrc.local.template |  3 +--
 app/main.ts           | 25 +++++++++++++++++++------
 app/server.ts         | 31 +++++++++++++++++--------------
 3 files changed, 37 insertions(+), 22 deletions(-)

diff --git a/.envrc.local.template b/.envrc.local.template
index 67b19f6..149451c 100644
--- a/.envrc.local.template
+++ b/.envrc.local.template
@@ -1,2 +1 @@
-export GEOAPIFY_API_KEY=""
-
+export GEOAPIFY_API_KEY=""
\ No newline at end of file
diff --git a/app/main.ts b/app/main.ts
index 8673f1e..9080439 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -1,5 +1,5 @@
 import { logger, LogSeverity } from "./logging.ts";
-import { startServer } from "./server.ts";
+import { DEFAULT_CACHE_ENABLED, DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE, DEFAULT_PORT, startServer } from "./server.ts";
 
 const environment = Deno.env.get("ENVIRONMENT") || "development";
 
@@ -8,16 +8,29 @@ logger.setUpLogger(
   environment === "development" ? LogSeverity.DEFAULT : LogSeverity.INFO,
 );
 
+const required = <T>(name: string, t?: T, fallback?: T): T => {
+  if (t === undefined && fallback === undefined) {
+    throw Error(`Environment variable "${name}" is required`);
+  } else {
+    return t !== undefined ? t : fallback!;
+  }
+};
+
+
 const serverConfigFromEnv = () => {
   const asNumber = (str?: string) => (str ? Number(str) : undefined);
   const asBoolean = (str?: string) => (str ? Boolean(str) : undefined);
+  const fake = asBoolean(Deno.env.get("FAKE")) || false // For local development. If set, the API returns dummy data
   return {
-    port: asNumber(Deno.env.get("PORT")),
-    cacheEnabled: asBoolean(Deno.env.get("CACHE_ENABLED")),
-    cacheTtlMsPlacesAutocomplete: asNumber(
-      Deno.env.get("DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE"),
+    port: required("PORT", asNumber(Deno.env.get("PORT")), DEFAULT_PORT),
+    cacheEnabled: required("CACHE_ENABLED", asBoolean(Deno.env.get("CACHE_ENABLED")), DEFAULT_CACHE_ENABLED),
+    cacheTtlMsPlacesAutocomplete: required("DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE", asNumber(Deno.env.get("DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE")), DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE),
+    geoapifyApiKey: required(
+      "GEOAPIFY_API_KEY",
+      Deno.env.get("GEOAPIFY_API_KEY"),
+      fake ? 'dummy value' : undefined
     ),
-    geoapifyApiKey: Deno.env.get("GEOAPIFY_API_KEY"),
+    fake
   };
 };
 
diff --git a/app/server.ts b/app/server.ts
index 8d63bee..cecf5c4 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -32,7 +32,7 @@ const typeDefs = `
     
     type Query {
         placesAutocomplete(text: String!, limit: Int, level: String): [Place]!
-        placeDetails(id: String!): PlaceDetails!
+        placeDetails(id: String!): PlaceDetails
     }
 `;
 
@@ -50,24 +50,23 @@ const createResolvers = (config: ServerConfig) => ({
       _parent: any,
       parameters: PlacesAutocompleteParameters,
       context: GraphQLContext,
-    ) =>
+    ) => config.fake ? Promise.resolve([]) : 
       fetchPlaces(
         parameters,
         getLanguage(context.language),
-        config.geoapifyApiKey || "",
+        config.geoapifyApiKey,
       ),
     placeDetails: (
       // deno-lint-ignore no-explicit-any
       _parent: any,
       parameters: PlacesDetailsParameters,
       context: GraphQLContext,
-    ) => {
-      return fetchPlaceDetails(
+    ) => config.fake ? Promise.resolve(undefined) : 
+      fetchPlaceDetails(
         parameters,
         getLanguage(context.language),
-        config.geoapifyApiKey || "",
-      );
-    },
+        config.geoapifyApiKey,
+      ),
   },
 });
 
@@ -76,10 +75,11 @@ export const DEFAULT_CACHE_ENABLED = true;
 export const DEFAULT_CACHE_TTL_MS_GEOAPIFY_AUTOCOMPLETE = 60_000 * 60 * 24;
 
 export interface ServerConfig {
-  port?: number; // default: 8003
-  cacheEnabled?: boolean; // default: true
-  cacheTtlMsPlacesAutocomplete?: number; // default: 24 hours
-  geoapifyApiKey?: string;
+  port: number; // default: 8003
+  cacheEnabled: boolean; // default: true
+  cacheTtlMsPlacesAutocomplete: number; // default: 24 hours
+  geoapifyApiKey: string;
+  fake: boolean;
 }
 
 export type GraphQLContext = {
@@ -94,7 +94,7 @@ export type GraphQLContext = {
 };
 
 export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
-  const plugins = config.cacheEnabled || DEFAULT_CACHE_ENABLED
+  const plugins = config.cacheEnabled
     ? [
       useResponseCache({
         // global cache per language, shared by all users
@@ -133,13 +133,16 @@ export type GraphQLServer = any;
 export const startServer = (config: ServerConfig): Promise<void> => {
   const graphQLServer: GraphQLServer = createGraphQLServer(config);
   return serve(graphQLServer.handleRequest, {
-    port: config.port || DEFAULT_PORT,
+    port: config.port,
     onListen({ port, hostname }) {
       logger.info(
         `Server started at http://${
           hostname === "0.0.0.0" ? "localhost" : hostname
         }:${port}/graphql`,
       );
+      if(config.fake) {
+        logger.info(`Server is serving fake data due to FAKE env var set to true`)
+      }
     },
   });
 };
-- 
GitLab