From 44e8a0481ecff986d11a8a6f7202b6a90f8b565d Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Fri, 30 Aug 2019 15:43:24 +0200
Subject: [PATCH] Add docker-compose with proxy

---
 .gitignore                       |  4 ++-
 api/src/index.js                 | 14 +++++---
 api/src/resolvers/photos.js      |  4 ++-
 api/src/routes/downloads.js      |  6 ++--
 api/src/routes/images.js         |  6 ++--
 docker-compose.proxy-example.yml | 57 ++++++++++++++++++++++++++++++++
 docker/nginx-proxy/default.conf  | 36 ++++++++++++++++++++
 7 files changed, 115 insertions(+), 12 deletions(-)
 create mode 100644 docker-compose.proxy-example.yml
 create mode 100644 docker/nginx-proxy/default.conf

diff --git a/.gitignore b/.gitignore
index 2be7de6..a577737 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
 # See https://help.github.com/ignore-files/ for more about ignoring files.
 
 /api/src/cache/
-/docker-compose.yml
 /photos_path
 
+# docker
+docker-compose.yml
+
 # dependencies
 node_modules/
 
diff --git a/api/src/index.js b/api/src/index.js
index 49340ff..a90d146 100644
--- a/api/src/index.js
+++ b/api/src/index.js
@@ -1,5 +1,5 @@
 import { ApolloServer } from 'apollo-server-express'
-import express from 'express'
+import express, { Router } from 'express'
 import bodyParser from 'body-parser'
 import cors from 'cors'
 import { v1 as neo4j } from 'neo4j-driver'
@@ -8,6 +8,7 @@ import PhotoScanner from './scanner/Scanner'
 import _ from 'lodash'
 import config from './config'
 import gql from 'graphql-tag'
+import path from 'path'
 
 import { getUserFromToken, getTokenFromBearer } from './token'
 
@@ -41,7 +42,8 @@ app.use((req, res, next) => {
 setInterval(scanner.scanAll, 1000 * 60 * 60 * 4)
 
 // Specify port and path for GraphQL endpoint
-const graphPath = '/graphql'
+const graphPath = new URL(path.join(config.host.toString(), '/graphql'))
+  .pathname
 
 app.use(graphPath, (req, res, next) => {
   if (req.body.query) {
@@ -102,6 +104,7 @@ const server = new ApolloServer({
   introspection: true,
   playground: !process.env.PRODUCTION,
   subscriptions: {
+    path: graphPath,
     onConnect: async (connectionParams, webSocket) => {
       const token = getTokenFromBearer(connectionParams.Authorization)
       const user = await getUserFromToken(token, driver)
@@ -115,12 +118,15 @@ const server = new ApolloServer({
 })
 
 server.applyMiddleware({ app, path: graphPath })
+const router = new Router()
 
 import loadImageRoutes from './routes/images'
 import loadDownloadRoutes from './routes/downloads'
 
-loadImageRoutes(app)
-loadDownloadRoutes(app)
+loadImageRoutes(router)
+loadDownloadRoutes(router)
+
+app.use(config.host.pathname, router)
 
 const httpServer = http.createServer(app)
 server.installSubscriptionHandlers(httpServer)
diff --git a/api/src/resolvers/photos.js b/api/src/resolvers/photos.js
index 381072b..0f551b8 100644
--- a/api/src/resolvers/photos.js
+++ b/api/src/resolvers/photos.js
@@ -1,4 +1,5 @@
 import { cypherQuery } from 'neo4j-graphql-js'
+import path from 'path'
 import config from '../config'
 
 function injectAt(query, index, injection) {
@@ -126,7 +127,8 @@ const Query = {
 
 const urlResolve = {
   url(root, args, ctx, info) {
-    let url = new URL(root.url, config.host)
+    let url = new URL(path.join(config.host.href, root.url))
+
     if (ctx.shareToken) url.search = `?token=${ctx.shareToken}`
     return url.href
   },
diff --git a/api/src/routes/downloads.js b/api/src/routes/downloads.js
index e18f16b..e74d56c 100644
--- a/api/src/routes/downloads.js
+++ b/api/src/routes/downloads.js
@@ -18,9 +18,9 @@ async function sendDownload(req, res) {
   throw new RequestError(404, 'Image could not be found')
 }
 
-const loadDownloadRoutes = app => {
-  app.use('/download/:id/:image', getImageFromRequest)
-  app.use('/download/:id/:image', sendDownload)
+const loadDownloadRoutes = router => {
+  router.use('/download/:id/:image', getImageFromRequest)
+  router.use('/download/:id/:image', sendDownload)
 }
 
 export default loadDownloadRoutes
diff --git a/api/src/routes/images.js b/api/src/routes/images.js
index 4989699..ad1f2e3 100644
--- a/api/src/routes/images.js
+++ b/api/src/routes/images.js
@@ -177,9 +177,9 @@ async function verifyShareToken({ shareToken, id, driver }) {
   }
 }
 
-function loadImageRoutes(app) {
-  app.use('/images/:id/:image', getImageFromRequest)
-  app.use('/images/:id/:image', sendImage)
+function loadImageRoutes(router) {
+  router.use('/images/:id/:image', getImageFromRequest)
+  router.use('/images/:id/:image', sendImage)
 }
 
 export default loadImageRoutes
diff --git a/docker-compose.proxy-example.yml b/docker-compose.proxy-example.yml
new file mode 100644
index 0000000..ae3f002
--- /dev/null
+++ b/docker-compose.proxy-example.yml
@@ -0,0 +1,57 @@
+version: '3'
+
+services:
+  neo4j:
+    build: ./docker/neo4j
+    expose:
+      - 7474
+      - 7687
+    environment:
+      - NEO4J_dbms_security_procedures_unrestricted=apoc.*
+      - NEO4J_apoc_import_file_enabled=true
+      - NEO4J_apoc_export_file_enabled=true
+      - NEO4J_dbms_shell_enabled=true
+    volumes:
+      - db_data:/data
+
+  api:
+    build: ./api
+    expose:
+      - 80
+    depends_on:
+    - neo4j
+    environment:
+      - NEO4J_URI=bolt://neo4j:7687
+      - PHOTO_CACHE=/app/cache
+      # Change This: The publicly exposed url for the api
+      - API_ENDPOINT=http://localhost:8080/api
+      - GRAPHQL_LISTEN_PORT=80
+    volumes:
+      # Change This: Link photo paths from the host machine
+      - ./photos_path:/photos
+      - api_cache:/app/cache
+
+  ui:
+    build:
+      context: ./ui
+      args:
+        # Change This: The publicly exposed url for the graphql api
+        GRAPHQL_ENDPOINT: http://localhost:8080/api/graphql
+    expose:
+      - 80
+    depends_on:
+      - api
+
+  proxy:
+    image: nginx
+    volumes:
+      - ./docker/nginx-proxy/default.conf:/etc/nginx/conf.d/default.conf
+    ports:
+      - 8080:80
+    depends_on:
+      - api
+      - ui
+
+volumes:
+  db_data:
+  api_cache:
\ No newline at end of file
diff --git a/docker/nginx-proxy/default.conf b/docker/nginx-proxy/default.conf
new file mode 100644
index 0000000..37b3571
--- /dev/null
+++ b/docker/nginx-proxy/default.conf
@@ -0,0 +1,36 @@
+server {
+  listen 80 default_server;
+
+  location /api {
+      proxy_pass http://api;
+      proxy_set_header Host $host;
+      proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_set_header X-Forwarded-Proto $scheme;
+
+      # Required for Websocket
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "upgrade";
+
+      #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
+      client_max_body_size 0;
+
+      access_log /var/log/nginx/photoview-api.access.log;
+      error_log /var/log/nginx/photoview-api.error.log;
+  }
+
+  location / {
+        proxy_pass http://ui;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
+        client_max_body_size 0;
+
+        access_log /var/log/nginx/photoview-ui.access.log;
+        error_log /var/log/nginx/photoview-ui.error.log;
+    }
+
+}
\ No newline at end of file
