From fc585bffcc7a68c19bdf57a0409875c95acd2045 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Skyler=20M=C3=A4ntysaari?= <samip5@users.noreply.github.com>
Date: Tue, 4 Apr 2023 04:16:45 +0300
Subject: [PATCH] feat(server): Support TypeSense High-Availibility
 configuration (#2146)

* feat(server): Support TypeSense High-Availibility configuration.

* Lint fixes

* Address comments.
---
 docker/example.env                    |  8 +++++
 server/libs/infra/src/infra.config.ts | 45 +++++++++++++++++++--------
 2 files changed, 40 insertions(+), 13 deletions(-)

diff --git a/docker/example.env b/docker/example.env
index 357d338a59..010facf1cc 100644
--- a/docker/example.env
+++ b/docker/example.env
@@ -47,6 +47,14 @@ UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_ba
 ###################################################################################
 TYPESENSE_API_KEY=some-random-text
 # TYPESENSE_ENABLED=false
+# TYPESENSE_URL uses base64 encoding for the nodes json.
+# Example JSON that was used:
+# [
+#      { 'host': 'typesense-1.example.net', 'port': '443', 'protocol': 'https' },
+#      { 'host': 'typesense-2.example.net', 'port': '443', 'protocol': 'https' },
+#      { 'host': 'typesense-3.example.net', 'port': '443', 'protocol': 'https' },
+#  ]
+# TYPESENSE_URL=ha://WwogICAgeyAnaG9zdCc6ICd0eXBlc2Vuc2UtMS5leGFtcGxlLm5ldCcsICdwb3J0JzogJzQ0MycsICdwcm90b2NvbCc6ICdodHRwcycgfSwKICAgIHsgJ2hvc3QnOiAndHlwZXNlbnNlLTIuZXhhbXBsZS5uZXQnLCAncG9ydCc6ICc0NDMnLCAncHJvdG9jb2wnOiAnaHR0cHMnIH0sCiAgICB7ICdob3N0JzogJ3R5cGVzZW5zZS0zLmV4YW1wbGUubmV0JywgJ3BvcnQnOiAnNDQzJywgJ3Byb3RvY29sJzogJ2h0dHBzJyB9LApd
 
 ###################################################################################
 # Reverse Geocoding
diff --git a/server/libs/infra/src/infra.config.ts b/server/libs/infra/src/infra.config.ts
index 33be44b4e0..fe593b15dd 100644
--- a/server/libs/infra/src/infra.config.ts
+++ b/server/libs/infra/src/infra.config.ts
@@ -37,16 +37,35 @@ export const bullConfig: BullModuleOptions = {
 
 export const bullQueues: BullModuleOptions[] = Object.values(QueueName).map((name) => ({ name }));
 
-export const typesenseConfig: ConfigurationOptions = {
-  nodes: [
-    {
-      host: process.env.TYPESENSE_HOST || 'typesense',
-      port: Number(process.env.TYPESENSE_PORT) || 8108,
-      protocol: process.env.TYPESENSE_PROTOCOL || 'http',
-    },
-  ],
-  apiKey: process.env.TYPESENSE_API_KEY as string,
-  numRetries: 15,
-  retryIntervalSeconds: 4,
-  connectionTimeoutSeconds: 10,
-};
+function parseTypeSenseConfig(): ConfigurationOptions {
+  const typesenseURL = process.env.TYPESENSE_URL;
+  const common = {
+    apiKey: process.env.TYPESENSE_API_KEY as string,
+    numRetries: 15,
+    retryIntervalSeconds: 4,
+    connectionTimeoutSeconds: 10,
+  };
+  if (typesenseURL && typesenseURL.startsWith('ha://')) {
+    try {
+      const decodedString = Buffer.from(typesenseURL.slice(5), 'base64').toString();
+      return {
+        nodes: JSON.parse(decodedString),
+        ...common,
+      };
+    } catch (error) {
+      throw new Error(`Failed to decode typesense options: ${error}`);
+    }
+  }
+  return {
+    nodes: [
+      {
+        host: process.env.TYPESENSE_HOST || 'typesense',
+        port: Number(process.env.TYPESENSE_PORT) || 8108,
+        protocol: process.env.TYPESENSE_PROTOCOL || 'http',
+      },
+    ],
+    ...common,
+  };
+}
+
+export const typesenseConfig: ConfigurationOptions = parseTypeSenseConfig();