From 56b9c52528d1e514d4ec15f1da7517cd83bcf5ff Mon Sep 17 00:00:00 2001
From: Ben Busby <noreply+git@benbusby.com>
Date: Mon, 8 Nov 2021 17:08:19 -0700
Subject: [PATCH] Display list of available instances on home page

This introduces a number of new changes:
- Services are now inserted into redis with a prefix prepended to the
key name. This allows for easier filtering to get only live instances.
- The home page now uses an eex template for displaying all live
instances for every service, determined by the last update
- A "last_updated" field was added
- farside.ex was added to contain all functionality related to querying
for instances (WIP)
- Other improvements
---
 config/config.exs     |  4 +++-
 index.eex             | 50 +++++++++++++++++++++++++++++++++++++++++++
 lib/farside.ex        | 45 ++++++++++++++++++++++++++++++++++++++
 lib/farside/router.ex | 18 ++++++++++++++--
 mix.lock              |  2 ++
 services.json         | 10 ++++-----
 update.exs            | 14 ++++++++----
 7 files changed, 131 insertions(+), 12 deletions(-)
 create mode 100644 index.eex
 create mode 100644 lib/farside.ex

diff --git a/config/config.exs b/config/config.exs
index 11fa98f..3b9dfe6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -4,4 +4,6 @@ config :farside,
   redis_conn: "redis://localhost:6379",
   fallback_str: "-fallback",
   update_file: ".update-results",
-  services_json: "services.json"
+  service_prefix: "service-",
+  services_json: "services.json",
+  index: "index.eex"
diff --git a/index.eex b/index.eex
new file mode 100644
index 0000000..c55e587
--- /dev/null
+++ b/index.eex
@@ -0,0 +1,50 @@
+<head>
+  <title>Farside</title>
+  <style>
+    html {
+      font-family: monospace;
+      font-size: 16px;
+      color: #66397C;
+    }
+    #parent-div {
+      text-align: center;
+    }
+    #child-div {
+      text-align: left;
+      width: 50%;
+      display: inline-block;
+    }
+    hr {
+      border: 1px dashed;
+    }
+    a:link, a:visited {
+      color: #66397C; 
+    }
+    @media only screen and (max-width: 1000px) {
+      #child-div {
+        width: 90%;
+      }
+    }
+  </style>
+</head>
+<body>
+  <div id="parent-div">
+    <div id="child-div">
+      <h1>Farside | <a href="https://github.com/benbusby/farside">View on GitHub</a></h1>
+      <hr>
+      <h3>Last synced <%= last_updated %> UTC</h2>
+      <div>
+        <ul>
+          <%= for {service, instance_list} <- services do %>
+            <li><a href="/<%= service %>"><%= service %></a></li>
+            <ul>
+              <%= for url <- instance_list do %>
+                <li><a href="<%= url %>"><%= url %></a></li>
+              <% end%>
+            </ul>
+          <% end %>
+        </ul>
+      </div>
+    </div>
+  </div>
+</body>
diff --git a/lib/farside.ex b/lib/farside.ex
new file mode 100644
index 0000000..9432406
--- /dev/null
+++ b/lib/farside.ex
@@ -0,0 +1,45 @@
+defmodule Farside do
+  @service_prefix Application.fetch_env!(:farside, :service_prefix)
+
+  def get_services_map do
+    {:ok, redis_keys} = Redix.command(:redix, ["KEYS", "*"])
+
+    # Extract only service related keys
+    service_list =
+      Enum.filter(
+        redis_keys,
+        fn key ->
+          String.starts_with?(key, @service_prefix)
+        end
+      )
+
+    # Match service name to list of available instances
+    Enum.reduce(service_list, %{}, fn service, acc ->
+      {:ok, instance_list} =
+        Redix.command(
+          :redix,
+          ["LRANGE", service, "0", "-1"]
+        )
+
+      Map.put(
+        acc,
+        String.replace_prefix(
+          service,
+          @service_prefix,
+          ""
+        ),
+        instance_list
+      )
+    end)
+  end
+
+  def get_last_updated do
+    {:ok, last_updated} =
+      Redix.command(
+        :redix,
+        ["GET", "last_updated"]
+      )
+
+    last_updated
+  end
+end
diff --git a/lib/farside/router.ex b/lib/farside/router.ex
index 95d9b41..82b3757 100644
--- a/lib/farside/router.ex
+++ b/lib/farside/router.ex
@@ -1,5 +1,7 @@
 defmodule Farside.Router do
+  @index Application.fetch_env!(:farside, :index)
   @fallback_str Application.fetch_env!(:farside, :fallback_str)
+  @service_prefix Application.fetch_env!(:farside, :service_prefix)
 
   use Plug.Router
 
@@ -7,7 +9,14 @@ defmodule Farside.Router do
   plug(:dispatch)
 
   get "/" do
-    send_resp(conn, 200, "")
+    resp =
+      EEx.eval_file(
+        @index,
+        last_updated: Farside.get_last_updated(),
+        services: Farside.get_services_map()
+      )
+
+    send_resp(conn, 200, resp)
   end
 
   get "/ping" do
@@ -22,7 +31,12 @@ defmodule Farside.Router do
     {:ok, instances} =
       Redix.command(
         :redix,
-        ["LRANGE", service, "0", "-1"]
+        [
+          "LRANGE",
+          "#{@service_prefix}#{service}",
+          "0",
+          "-1"
+        ]
       )
 
     # Either pick a random available instance, 
diff --git a/mix.lock b/mix.lock
index 1ada2e4..bb98ecc 100644
--- a/mix.lock
+++ b/mix.lock
@@ -10,7 +10,9 @@
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
   "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
+  "mustache": {:hex, :mustache, "0.3.1", "4c6ee79b13aae954035fe31b83c94480ddc7b536d09c44d4c65e61a9ead38d6b", [:mix], [], "hexpm", "8dc92b9b92a0d7449628f4fc981f8018a16a5b8c9907249e59db461482dac143"},
   "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+  "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
   "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
   "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
   "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
diff --git a/services.json b/services.json
index 1a938f4..cee766f 100644
--- a/services.json
+++ b/services.json
@@ -35,12 +35,12 @@
     },
     {
         "type": "bibliogram",
-        "test_url": "/taylorswift",
-        "fallback": "https://bibliogram.art/u",
+        "test_url": "/u/taylorswift",
+        "fallback": "https://bibliogram.art",
         "instances": [
-            "https://bibliogram.art/u",
-            "https://bibliogram.snopyta.org/u",
-            "https://bibliogram.pussthecat.org/u",
+            "https://bibliogram.art",
+            "https://bibliogram.snopyta.org",
+            "https://bibliogram.pussthecat.org",
             "https://bibliogram.1d4.us",
             "https://insta.trom.tf",
             "https://bibliogram.hamster.dance",
diff --git a/update.exs b/update.exs
index fe1445b..4e95392 100644
--- a/update.exs
+++ b/update.exs
@@ -11,6 +11,7 @@ defmodule Instances do
   @fallback_str Application.fetch_env!(:farside, :fallback_str)
   @update_file Application.fetch_env!(:farside, :update_file)
   @services_json Application.fetch_env!(:farside, :services_json)
+  @service_prefix Application.fetch_env!(:farside, :service_prefix)
 
   def init() do
     File.rename(@update_file, "#{@update_file}-prev")
@@ -45,18 +46,16 @@ defmodule Instances do
   end
 
   def add_to_redis(service, instances) do
-    IO.puts "         --------"
-    IO.inspect "OK: " <> instances
     # Remove previous list of instances
     Redix.command(:redix, [
       "DEL",
-      service.type
+      "#{@service_prefix}#{service.type}"
     ])
 
     # Update with new list of available instances
     Redix.command(:redix, [
       "LPUSH",
-      service.type
+      "#{@service_prefix}#{service.type}"
     ] ++ instances)
 
     # Set fallback to one of the available instances,
@@ -84,3 +83,10 @@ defmodule Instances do
 end
 
 Instances.init()
+
+# Add UTC time of last update
+Redix.command(:redix, [
+  "SET",
+  "last_updated",
+  Calendar.strftime(DateTime.utc_now(), "%c")
+])