From e171bf1b0b9d8f4f09eddd7a33119ed44b6eaf0c Mon Sep 17 00:00:00 2001
From: Bastien Abadie <bastien@nextcairn.com>
Date: Mon, 20 Jan 2020 13:08:18 +0000
Subject: [PATCH] Render cached index.html to serve frontend from CDN

---
 Dockerfile                    |  3 +++
 arkindex/project/settings.py  | 18 +++++++++++++++++-
 arkindex/project/urls.py      |  9 +++++++--
 arkindex/project/views.py     | 22 +++++++++++++++++++++-
 arkindex/templates/index.html | 18 ++++++++++++++++++
 5 files changed, 66 insertions(+), 4 deletions(-)
 create mode 100644 arkindex/templates/index.html

diff --git a/Dockerfile b/Dockerfile
index 04afc9c287..6e4127c413 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,6 +32,9 @@ RUN pip install /tmp/arkindex.tar.gz && rm /tmp/arkindex.tar.gz
 RUN mkdir -p /logs
 RUN chown -R ark:teklia /logs
 
+# Copy Version file
+COPY VERSION /etc/arkindex.version
+
 # Run with Daphne
 EXPOSE 80
 CMD [ \
diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py
index 0dc280b53f..438a72da5e 100644
--- a/arkindex/project/settings.py
+++ b/arkindex/project/settings.py
@@ -45,6 +45,13 @@ ADMINS = [('', address) for address in env2list('ADMIN_EMAIL')]
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 ML_CLASSIFIERS_DIR = os.environ.get('ML_CLASSIFIERS_DIR', os.path.join(BASE_DIR, '../../ml-classifiers'))
 
+# Read Version either from Docker static file or local file
+_version = '/etc/arkindex.version' \
+    if os.path.exists('/etc/arkindex.version') \
+    else os.path.join(os.path.dirname(BASE_DIR), 'VERSION')
+with open(_version) as f:
+    VERSION = f.read().strip()
+
 # Local IIIF server
 LOCAL_IMAGESERVER_ID = int(os.environ.get('LOCAL_IMAGESERVER_ID', 1))
 
@@ -245,9 +252,12 @@ elif os.environ.get('CACHE_DIR'):
         }
     }
 else:
+    # On dev, use a dummy cache
+    # On prod, use at least a local memory cache
+    _cache = 'django.core.cache.backends.dummy.DummyCache' if DEBUG else 'django.core.cache.backends.locmem.LocMemCache'
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+            'BACKEND': _cache
         }
     }
 
@@ -425,6 +435,12 @@ ARKINDEX_TASKS_IMAGE = os.environ.get('ARKINDEX_TASKS_IMAGE', 'arkindex-tasks')
 # User groups with special permissions
 INTERNAL_GROUP_ID = 2
 
+# CDN Assets URL to use for arkindex remote CSS/JS/Images assets
+CDN_ASSETS_URL = os.environ.get('CDN_ASSETS_URL')
+if CDN_ASSETS_URL is not None:
+    CDN_ASSETS_URL = CDN_ASSETS_URL.rstrip('/')
+    STATIC_URL = f"{CDN_ASSETS_URL}/{VERSION}/static/"
+
 # Optional unit tests runner with code coverage
 try:
     import django_nose # noqa
diff --git a/arkindex/project/urls.py b/arkindex/project/urls.py
index 37a0d594a3..3edb949eca 100644
--- a/arkindex/project/urls.py
+++ b/arkindex/project/urls.py
@@ -1,8 +1,8 @@
-from django.urls import path, include
+from django.urls import path, include, re_path
 from django.conf import settings
 from django.contrib import admin
 from arkindex.project.api_v1 import api
-from arkindex.project.views import FrontendView
+from arkindex.project.views import FrontendView, CdnHome
 
 urlpatterns = [
     path('api/v1/', include((api, 'api'), namespace='api')),
@@ -17,3 +17,8 @@ if 'debug_toolbar' in settings.INSTALLED_APPS:
     urlpatterns = [
         path('__debug__/', include(debug_toolbar.urls)),
     ] + urlpatterns
+
+# Add index.html using CDN assets
+# It's served as a full fallback on 404 to support loading specific frontend urls
+if settings.CDN_ASSETS_URL is not None:
+    urlpatterns.append(re_path(r'^.*$', CdnHome.as_view(), name="home"))
diff --git a/arkindex/project/views.py b/arkindex/project/views.py
index 00b8c7746e..3238334f3c 100644
--- a/arkindex/project/views.py
+++ b/arkindex/project/views.py
@@ -1,4 +1,6 @@
-from django.views.generic import View
+from django.views.generic import View, TemplateView
+from django.views.decorators.cache import cache_page
+from django.conf import settings
 
 
 class FrontendView(View):
@@ -6,3 +8,21 @@ class FrontendView(View):
     View that show the frontend's router
     TODO: Get rid of this view once the frontend is fully split
     """
+
+
+class CdnHome(TemplateView):
+    """
+    Index template to use frontend assets from a CDN
+    """
+    template_name = 'index.html'
+
+    def dispatch(self, *args, **kwargs):
+        # Cache rendered page
+        # On dev, by default a dummy cache is enabled
+        return cache_page(60 * 10)(super().dispatch)(*args, **kwargs)
+
+    def get_context_data(self, *args, **kwargs):
+        ctx = super().get_context_data(*args, **kwargs)
+        ctx['cdn_assets_url'] = settings.CDN_ASSETS_URL
+        ctx['version'] = settings.VERSION
+        return ctx
diff --git a/arkindex/templates/index.html b/arkindex/templates/index.html
new file mode 100644
index 0000000000..31e750fa0f
--- /dev/null
+++ b/arkindex/templates/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <meta name=viewport content="width=device-width,initial-scale=1">
+    <meta name="version" content="{{ version }}">
+    <meta name="assets_url" content="{{ cdn_assets_url }}/{{ version }}">
+    <meta name="api_base_url" content="/api/v1">
+    <title>ArkIndex {{ version }}</title>
+    <link href="{{ cdn_assets_url }}/{{ version }}/arkindex-vendors-main-{{ version }}.css" rel="stylesheet">
+    <link href="{{ cdn_assets_url }}/{{ version }}/arkindex-{{ version }}.css" rel="stylesheet">
+  </head>
+  <body>
+    <div id=app></div>
+    <script type="text/javascript" src="{{ cdn_assets_url }}/{{ version }}/arkindex-vendors-main-{{ version }}.js"></script>
+    <script type="text/javascript" src="{{ cdn_assets_url }}/{{ version }}/arkindex-{{ version }}.js"></script>
+</body>
+</html>
-- 
GitLab