From ba0f5e5b3072b5d7c904a3105e4e7c5bdd83316f Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sat, 31 Jul 2021 01:41:52 +0200
Subject: [PATCH] Further improve image APIs

---
 api/album_get.go                     | 13 ++++++++++++
 api/album_list.go                    | 16 +++++++++++++++
 api/albumimage_get.go                | 25 -----------------------
 api/albumimage_list.go               | 25 -----------------------
 cmd/frontend/main.go                 |  6 ------
 model/album.go                       | 13 ++++++------
 model/album_image.go                 | 11 ++++++++++
 ui/src/App.tsx                       |  2 +-
 ui/src/ImageList.tsx                 | 30 ----------------------------
 ui/src/api/model/Album.ts            |  3 +++
 ui/src/api/model/AlbumImage.ts       |  1 +
 ui/src/api/useGetAlbum.ts            | 20 +++++++++++++++++++
 ui/src/api/useGetImage.ts            | 20 +++++++++++++++++++
 ui/src/api/useListAlbums.ts          | 20 +++++++++++++++++++
 ui/src/api/useListImages.ts          |  2 +-
 ui/src/components/AlbumImageView.tsx | 17 ++++++++++++++++
 ui/src/components/AlbumList.tsx      | 22 ++++++++++++++++++++
 ui/src/components/AlbumView.tsx      | 28 ++++++++++++++++++++++++++
 ui/src/components/ImageList.tsx      | 22 ++++++++++++++++++++
 ui/src/components/ImageView.tsx      | 23 +++++++++++++++++++++
 20 files changed, 225 insertions(+), 94 deletions(-)
 delete mode 100644 api/albumimage_get.go
 delete mode 100644 api/albumimage_list.go
 delete mode 100644 ui/src/ImageList.tsx
 create mode 100644 ui/src/api/useGetAlbum.ts
 create mode 100644 ui/src/api/useGetImage.ts
 create mode 100644 ui/src/api/useListAlbums.ts
 create mode 100644 ui/src/components/AlbumImageView.tsx
 create mode 100644 ui/src/components/AlbumList.tsx
 create mode 100644 ui/src/components/AlbumView.tsx
 create mode 100644 ui/src/components/ImageList.tsx
 create mode 100644 ui/src/components/ImageView.tsx

diff --git a/api/album_get.go b/api/album_get.go
index 063b8e0..928b2b4 100644
--- a/api/album_get.go
+++ b/api/album_get.go
@@ -19,6 +19,19 @@ func GetAlbum(env environment.FrontendEnvironment) http.Handler {
 			http.Error(writer, err.Error(), http.StatusInternalServerError)
 			return
 		}
+		album.Images, err = env.Repositories.AlbumImages.List(album.Id)
+		if err != nil {
+			http.Error(writer, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		for i, image := range album.Images {
+			err = image.LoadUrl(env.Storage, env.Configuration.Storage)
+			if err != nil {
+				http.Error(writer, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			album.Images[i] = image
+		}
 
 		util.ReturnJson(writer, album)
 	})
diff --git a/api/album_list.go b/api/album_list.go
index c29898b..74d0f4e 100644
--- a/api/album_list.go
+++ b/api/album_list.go
@@ -22,6 +22,22 @@ func ListAlbums(env environment.FrontendEnvironment) http.Handler {
 			http.Error(writer, err.Error(), http.StatusInternalServerError)
 			return
 		}
+		for i, album := range albums {
+			album.Images, err = env.Repositories.AlbumImages.List(album.Id)
+			if err != nil {
+				http.Error(writer, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			for j, image := range album.Images {
+				err = image.LoadUrl(env.Storage, env.Configuration.Storage)
+				if err != nil {
+					http.Error(writer, err.Error(), http.StatusInternalServerError)
+					return
+				}
+				album.Images[j] = image
+			}
+			albums[i] = album
+		}
 
 		util.ReturnJson(writer, albums)
 	})
diff --git a/api/albumimage_get.go b/api/albumimage_get.go
deleted file mode 100644
index f85a592..0000000
--- a/api/albumimage_get.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package api
-
-import (
-	"database/sql"
-	"git.kuschku.de/justjanne/imghost-frontend/environment"
-	"git.kuschku.de/justjanne/imghost-frontend/util"
-	"github.com/gorilla/mux"
-	"net/http"
-)
-
-func GetAlbumImage(env environment.FrontendEnvironment) http.Handler {
-	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
-		vars := mux.Vars(request)
-		albumImage, err := env.Repositories.AlbumImages.Get(vars["albumId"], vars["imageId"])
-		if err == sql.ErrNoRows {
-			http.NotFound(writer, request)
-			return
-		} else if err != nil {
-			http.Error(writer, err.Error(), http.StatusInternalServerError)
-			return
-		}
-
-		util.ReturnJson(writer, albumImage)
-	})
-}
diff --git a/api/albumimage_list.go b/api/albumimage_list.go
deleted file mode 100644
index 9782852..0000000
--- a/api/albumimage_list.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package api
-
-import (
-	"database/sql"
-	"git.kuschku.de/justjanne/imghost-frontend/environment"
-	"git.kuschku.de/justjanne/imghost-frontend/util"
-	"github.com/gorilla/mux"
-	"net/http"
-)
-
-func ListAlbumImages(env environment.FrontendEnvironment) http.Handler {
-	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
-		vars := mux.Vars(request)
-		albumImages, err := env.Repositories.AlbumImages.List(vars["albumId"])
-		if err == sql.ErrNoRows {
-			http.NotFound(writer, request)
-			return
-		} else if err != nil {
-			http.Error(writer, err.Error(), http.StatusInternalServerError)
-			return
-		}
-
-		util.ReturnJson(writer, albumImages)
-	})
-}
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index 2fc245d..c050e6e 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -65,12 +65,6 @@ func main() {
 		api.DeleteAlbum(env)).Methods(http.MethodDelete, http.MethodOptions)
 
 	// Album Image API
-	router.Handle(
-		"/api/v1/albums/{albumId}/images",
-		api.ListAlbumImages(env)).Methods(http.MethodGet, http.MethodOptions)
-	router.Handle(
-		"/api/v1/albums/{albumId}/images/{imageId}",
-		api.GetAlbumImage(env)).Methods(http.MethodGet, http.MethodOptions)
 	router.Handle(
 		"/api/v1/albums/{albumId}/images/{imageId}",
 		api.UpdateAlbumImage(env)).Methods(http.MethodPost, http.MethodOptions)
diff --git a/model/album.go b/model/album.go
index 75da1e6..ba560d1 100644
--- a/model/album.go
+++ b/model/album.go
@@ -6,12 +6,13 @@ import (
 )
 
 type Album struct {
-	Id          string    `json:"id" db:"id"`
-	Owner       string    `json:"owner" db:"owner"`
-	Title       string    `json:"title" db:"title"`
-	Description string    `json:"description" db:"description"`
-	CreatedAt   time.Time `json:"created_at" db:"created_at"`
-	UpdatedAt   time.Time `json:"updated_at" db:"updated_at"`
+	Id          string       `json:"id" db:"id"`
+	Owner       string       `json:"owner" db:"owner"`
+	Title       string       `json:"title" db:"title"`
+	Description string       `json:"description" db:"description"`
+	CreatedAt   time.Time    `json:"created_at" db:"created_at"`
+	UpdatedAt   time.Time    `json:"updated_at" db:"updated_at"`
+	Images      []AlbumImage `json:"images"`
 }
 
 func (album Album) VerifyOwner(user User) error {
diff --git a/model/album_image.go b/model/album_image.go
index b757f7f..5d3b09f 100644
--- a/model/album_image.go
+++ b/model/album_image.go
@@ -1,8 +1,19 @@
 package model
 
+import (
+	"git.kuschku.de/justjanne/imghost-frontend/configuration"
+	"git.kuschku.de/justjanne/imghost-frontend/storage"
+)
+
 type AlbumImage struct {
 	Album       string `json:"album" db:"album"`
 	Image       string `json:"image" db:"image"`
 	Title       string `json:"title" db:"title"`
 	Description string `json:"description" db:"description"`
+	Url         string `json:"url"`
+}
+
+func (image *AlbumImage) LoadUrl(storage storage.Storage, config configuration.StorageConfiguration) (err error) {
+	image.Url = storage.UrlFor(config.ImageBucket, image.Image).String()
+	return
 }
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 76da868..8fae9dd 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import './App.css';
-import ImageList from "./ImageList";
+import ImageList from "./components/ImageList";
 import {BaseUrlProvider} from './api/baseUrlContext';
 import {QueryClient, QueryClientProvider} from "react-query";
 
diff --git a/ui/src/ImageList.tsx b/ui/src/ImageList.tsx
deleted file mode 100644
index ca22322..0000000
--- a/ui/src/ImageList.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import './App.css';
-import {useListImages} from "./api/useListImages";
-
-export default function ImageList() {
-    const {status, data, error} = useListImages();
-    return (
-        <div>
-            <p>{status}</p>
-            <p>{error as string}</p>
-            <ul>
-                {data?.map(info => (
-                    <li key={info.id}>
-                        <p>{info.id}</p>
-                        <p>{info.owner}</p>
-                        <p>{info.title}</p>
-                        <p>{info.description}</p>
-                        <p>{info.original_name}</p>
-                        <p>{info.mime_type}</p>
-                        <p>{info.created_at}</p>
-                        <p>{info.updated_at}</p>
-                        <p>{info.state}</p>
-                        <p>{info.url}</p>
-                        <img src={info.url+"t"} alt=""/>
-                    </li>
-                ))}
-            </ul>
-        </div>
-    );
-}
diff --git a/ui/src/api/model/Album.ts b/ui/src/api/model/Album.ts
index a612891..fcd24df 100644
--- a/ui/src/api/model/Album.ts
+++ b/ui/src/api/model/Album.ts
@@ -1,3 +1,5 @@
+import {AlbumImage} from "./AlbumImage";
+
 export interface Album {
     id: string,
     owner: string,
@@ -5,4 +7,5 @@ export interface Album {
     description: string,
     created_at: string,
     updated_at: string,
+    images: AlbumImage[],
 }
diff --git a/ui/src/api/model/AlbumImage.ts b/ui/src/api/model/AlbumImage.ts
index 01e165a..787d4d9 100644
--- a/ui/src/api/model/AlbumImage.ts
+++ b/ui/src/api/model/AlbumImage.ts
@@ -3,4 +3,5 @@ export interface AlbumImage {
     image: string,
     title: string,
     description: string,
+    url: string,
 }
diff --git a/ui/src/api/useGetAlbum.ts b/ui/src/api/useGetAlbum.ts
new file mode 100644
index 0000000..79c34a7
--- /dev/null
+++ b/ui/src/api/useGetAlbum.ts
@@ -0,0 +1,20 @@
+import {useQuery} from "react-query";
+import axios from "axios";
+import {useBaseUrl} from "./baseUrlContext";
+import {Album} from "./model/Album";
+
+export const useGetAlbum = (albumId: string) => {
+    const baseUrl = useBaseUrl();
+    return useQuery(
+        ["album", albumId],
+        () => axios.get<Album>(
+            `api/v1/albums/${albumId}`,
+            {
+                baseURL: baseUrl
+            }
+        ).then(it => it.data),
+        {
+            keepPreviousData: true
+        }
+    );
+}
diff --git a/ui/src/api/useGetImage.ts b/ui/src/api/useGetImage.ts
new file mode 100644
index 0000000..6f6fc1b
--- /dev/null
+++ b/ui/src/api/useGetImage.ts
@@ -0,0 +1,20 @@
+import {useQuery} from "react-query";
+import axios from "axios";
+import {useBaseUrl} from "./baseUrlContext";
+import {Image} from "./model/Image";
+
+export const useGetImage = (imageId: string) => {
+    const baseUrl = useBaseUrl();
+    return useQuery(
+        ["image", imageId],
+        () => axios.get<Image>(
+            `api/v1/images/${imageId}`,
+            {
+                baseURL: baseUrl
+            }
+        ).then(it => it.data),
+        {
+            keepPreviousData: true
+        }
+    );
+}
diff --git a/ui/src/api/useListAlbums.ts b/ui/src/api/useListAlbums.ts
new file mode 100644
index 0000000..6f40830
--- /dev/null
+++ b/ui/src/api/useListAlbums.ts
@@ -0,0 +1,20 @@
+import {useQuery} from "react-query";
+import axios from "axios";
+import {useBaseUrl} from "./baseUrlContext";
+import {Album} from "./model/Album";
+
+export const useListAlbums = () => {
+    const baseUrl = useBaseUrl();
+    return useQuery(
+        "albums",
+        () => axios.get<Album[]>(
+            "api/v1/albums",
+            {
+                baseURL: baseUrl
+            }
+        ).then(it => it.data),
+        {
+            keepPreviousData: true
+        }
+    );
+}
diff --git a/ui/src/api/useListImages.ts b/ui/src/api/useListImages.ts
index d0e7ccf..76d74e0 100644
--- a/ui/src/api/useListImages.ts
+++ b/ui/src/api/useListImages.ts
@@ -6,7 +6,7 @@ import {Image} from "./model/Image";
 export const useListImages = () => {
     const baseUrl = useBaseUrl();
     return useQuery(
-        "connector-deployments",
+        "images",
         () => axios.get<Image[]>(
             "api/v1/images",
             {
diff --git a/ui/src/components/AlbumImageView.tsx b/ui/src/components/AlbumImageView.tsx
new file mode 100644
index 0000000..a2de68b
--- /dev/null
+++ b/ui/src/components/AlbumImageView.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import {AlbumImage} from "../api/model/AlbumImage";
+
+export interface AlbumImageProps {
+    image: AlbumImage
+}
+
+export default function AlbumImageView({image}: AlbumImageProps) {
+    return (
+        <div>
+            <p>{image.image}</p>
+            <p>{image.title}</p>
+            <p>{image.description}</p>
+            <img src={image.url + "t"} alt=""/>
+        </div>
+    )
+}
diff --git a/ui/src/components/AlbumList.tsx b/ui/src/components/AlbumList.tsx
new file mode 100644
index 0000000..6b827d3
--- /dev/null
+++ b/ui/src/components/AlbumList.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import '../App.css';
+import {useListAlbums} from "../api/useListAlbums";
+import AlbumView from "./AlbumView";
+
+export default function AlbumList() {
+    const {status, data, error} = useListAlbums();
+    return (
+        <div>
+            <p>{status}</p>
+            <p>{error as string}</p>
+            <ul>
+                {data?.map(album => (
+                    <AlbumView
+                        key={album.id}
+                        album={album}
+                    />
+                ))}
+            </ul>
+        </div>
+    );
+}
diff --git a/ui/src/components/AlbumView.tsx b/ui/src/components/AlbumView.tsx
new file mode 100644
index 0000000..96546c1
--- /dev/null
+++ b/ui/src/components/AlbumView.tsx
@@ -0,0 +1,28 @@
+import AlbumImageView from "./AlbumImageView";
+import React from "react";
+import {Album} from "../api/model/Album";
+
+export interface AlbumProps {
+    album: Album
+}
+
+export default function AlbumView({album}: AlbumProps) {
+    return (
+        <div>
+            <p>{album.id}</p>
+            <p>{album.owner}</p>
+            <p>{album.title}</p>
+            <p>{album.description}</p>
+            <p>{album.created_at}</p>
+            <p>{album.updated_at}</p>
+            <ul>
+                {album.images.map(image => (
+                    <AlbumImageView
+                        key={image.image}
+                        image={image}
+                    />
+                ))}
+            </ul>
+        </div>
+    );
+}
diff --git a/ui/src/components/ImageList.tsx b/ui/src/components/ImageList.tsx
new file mode 100644
index 0000000..f7b025d
--- /dev/null
+++ b/ui/src/components/ImageList.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import '../App.css';
+import {useListImages} from "../api/useListImages";
+import ImageView from "./ImageView";
+
+export default function ImageList() {
+    const {status, data, error} = useListImages();
+    return (
+        <div>
+            <p>{status}</p>
+            <p>{error as string}</p>
+            <ul>
+                {data?.map(image => (
+                    <ImageView
+                        key={image.id}
+                        image={image}
+                    />
+                ))}
+            </ul>
+        </div>
+    );
+}
diff --git a/ui/src/components/ImageView.tsx b/ui/src/components/ImageView.tsx
new file mode 100644
index 0000000..83bb5c5
--- /dev/null
+++ b/ui/src/components/ImageView.tsx
@@ -0,0 +1,23 @@
+import {Image} from "../api/model/Image";
+import React from "react";
+
+export interface ImageProps {
+    image: Image
+}
+
+export default function ImageView({image}: ImageProps) {
+    return (
+        <div>
+            <p>{image.id}</p>
+            <p>{image.owner}</p>
+            <p>{image.title}</p>
+            <p>{image.description}</p>
+            <p>{image.original_name}</p>
+            <p>{image.mime_type}</p>
+            <p>{image.created_at}</p>
+            <p>{image.updated_at}</p>
+            <p>{image.state}</p>
+            <img src={image.url + "t"} alt=""/>
+        </div>
+    )
+}
-- 
GitLab