From 69326f74c1a02ec39b581d1da5b0446ade4f51d9 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sat, 31 Jul 2021 01:14:28 +0200
Subject: [PATCH] Improve image APIs

---
 api/image_get.go               | 20 ++----------
 api/image_list.go              | 11 +++----
 api/image_upload.go            |  2 +-
 environment/backend.go         |  3 --
 environment/frontend.go        |  3 --
 environment/repositories.go    |  1 -
 model/image.go                 |  9 ++++++
 model/image_info.go            |  7 -----
 repo/image_state.go            | 56 ----------------------------------
 repo/images.go                 | 43 +++++++++++++++++++++-----
 storage/storage.go             | 14 +++------
 task/image_resize_processor.go | 14 ++++-----
 ui/src/ImageList.tsx           | 20 ++++++------
 ui/src/api/model/Image.ts      |  2 ++
 ui/src/api/model/ImageInfo.ts  |  7 -----
 ui/src/api/useListImages.ts    |  4 +--
 16 files changed, 79 insertions(+), 137 deletions(-)
 delete mode 100644 model/image_info.go
 delete mode 100644 repo/image_state.go
 delete mode 100644 ui/src/api/model/ImageInfo.ts

diff --git a/api/image_get.go b/api/image_get.go
index 68552ea..d1ff4d2 100644
--- a/api/image_get.go
+++ b/api/image_get.go
@@ -1,29 +1,13 @@
 package api
 
 import (
-	"context"
 	"database/sql"
 	"git.kuschku.de/justjanne/imghost-frontend/environment"
-	"git.kuschku.de/justjanne/imghost-frontend/model"
 	"git.kuschku.de/justjanne/imghost-frontend/util"
 	"github.com/gorilla/mux"
 	"net/http"
 )
 
-func EnrichImageInfo(env environment.FrontendEnvironment, image model.Image) (info model.ImageInfo, err error) {
-	info.Image = image
-	info.State, err = env.Repositories.ImageStates.Get(image.Id)
-	if err != nil {
-		return
-	}
-	imageUrl, err := env.Storage.UrlFor(context.Background(), env.Configuration.Storage.ImageBucket, info.Image.Id)
-	if err != nil {
-		return
-	}
-	info.Url = imageUrl.String()
-	return
-}
-
 func GetImage(env environment.FrontendEnvironment) http.Handler {
 	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
 		var err error
@@ -37,12 +21,12 @@ func GetImage(env environment.FrontendEnvironment) http.Handler {
 			http.Error(writer, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		info, err := EnrichImageInfo(env, image)
+		err = image.LoadUrl(env.Storage, env.Configuration.Storage)
 		if err != nil {
 			http.Error(writer, err.Error(), http.StatusInternalServerError)
 			return
 		}
 
-		util.ReturnJson(writer, info)
+		util.ReturnJson(writer, image)
 	})
 }
diff --git a/api/image_list.go b/api/image_list.go
index 3f08df0..eef39a5 100644
--- a/api/image_list.go
+++ b/api/image_list.go
@@ -3,7 +3,6 @@ package api
 import (
 	"git.kuschku.de/justjanne/imghost-frontend/auth"
 	"git.kuschku.de/justjanne/imghost-frontend/environment"
-	"git.kuschku.de/justjanne/imghost-frontend/model"
 	"git.kuschku.de/justjanne/imghost-frontend/util"
 	"net/http"
 )
@@ -15,15 +14,15 @@ func ListImages(env environment.FrontendEnvironment) http.Handler {
 			http.Error(writer, err.Error(), http.StatusUnauthorized)
 		}
 		images, err := env.Repositories.Images.List(user)
-		var infos []model.ImageInfo
-		for _, image := range images {
-			info, err := EnrichImageInfo(env, image)
+		for idx, image := range images {
+			err = image.LoadUrl(env.Storage, env.Configuration.Storage)
 			if err != nil {
 				http.Error(writer, err.Error(), http.StatusInternalServerError)
+				return
 			}
-			infos = append(infos, info)
+			images[idx] = image
 		}
 
-		util.ReturnJson(writer, infos)
+		util.ReturnJson(writer, images)
 	})
 }
diff --git a/api/image_upload.go b/api/image_upload.go
index 595a49d..4c8035e 100644
--- a/api/image_upload.go
+++ b/api/image_upload.go
@@ -139,7 +139,7 @@ func UploadImage(env environment.FrontendEnvironment) http.Handler {
 				return
 			}
 			println("Enqueued task")
-			err = env.Repositories.ImageStates.Update(image.Id, repo.StateQueued)
+			err = env.Repositories.Images.UpdateState(image.Id, repo.StateQueued)
 			if err != nil {
 				println("failed updating image state")
 				http.Error(writer, err.Error(), http.StatusInternalServerError)
diff --git a/environment/backend.go b/environment/backend.go
index cde2a74..73fd20a 100644
--- a/environment/backend.go
+++ b/environment/backend.go
@@ -24,9 +24,6 @@ func NewBackendEnvironment(config configuration.BackendConfiguration) (env Backe
 	if env.Repositories.Images, err = repo.NewImageRepo(env.Database); err != nil {
 		return
 	}
-	if env.Repositories.ImageStates, err = repo.NewImageStateRepo(env.Database); err != nil {
-		return
-	}
 	if env.Repositories.Albums, err = repo.NewAlbumRepo(env.Database); err != nil {
 		return
 	}
diff --git a/environment/frontend.go b/environment/frontend.go
index b29e552..607103d 100644
--- a/environment/frontend.go
+++ b/environment/frontend.go
@@ -26,9 +26,6 @@ func NewFrontendEnvironment(config configuration.FrontendConfiguration) (env Fro
 	if env.Repositories.Images, err = repo.NewImageRepo(env.Database); err != nil {
 		return
 	}
-	if env.Repositories.ImageStates, err = repo.NewImageStateRepo(env.Database); err != nil {
-		return
-	}
 	if env.Repositories.Albums, err = repo.NewAlbumRepo(env.Database); err != nil {
 		return
 	}
diff --git a/environment/repositories.go b/environment/repositories.go
index c307ff7..ff2a7ec 100644
--- a/environment/repositories.go
+++ b/environment/repositories.go
@@ -4,7 +4,6 @@ import "git.kuschku.de/justjanne/imghost-frontend/repo"
 
 type Repositories struct {
 	Images      repo.Images
-	ImageStates repo.ImageStates
 	Albums      repo.Albums
 	AlbumImages repo.AlbumImages
 }
diff --git a/model/image.go b/model/image.go
index f3ba2ed..a095a9e 100644
--- a/model/image.go
+++ b/model/image.go
@@ -2,6 +2,8 @@ package model
 
 import (
 	"errors"
+	"git.kuschku.de/justjanne/imghost-frontend/configuration"
+	"git.kuschku.de/justjanne/imghost-frontend/storage"
 	"time"
 )
 
@@ -14,6 +16,8 @@ type Image struct {
 	UpdatedAt    time.Time `json:"updated_at" db:"updated_at"`
 	OriginalName string    `json:"original_name" db:"original_name"`
 	MimeType     string    `json:"mime_type" db:"mime_type"`
+	State        string    `json:"state" db:"state"`
+	Url          string    `json:"url"`
 }
 
 func (image Image) VerifyOwner(user User) error {
@@ -23,3 +27,8 @@ func (image Image) VerifyOwner(user User) error {
 
 	return nil
 }
+
+func (image *Image) LoadUrl(storage storage.Storage, config configuration.StorageConfiguration) (err error) {
+	image.Url = storage.UrlFor(config.ImageBucket, image.Id).String()
+	return
+}
diff --git a/model/image_info.go b/model/image_info.go
deleted file mode 100644
index 00a62e5..0000000
--- a/model/image_info.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package model
-
-type ImageInfo struct {
-	Image Image  `json:"image"`
-	State string `json:"state"`
-	Url   string `json:"url"`
-}
diff --git a/repo/image_state.go b/repo/image_state.go
deleted file mode 100644
index 2c0026b..0000000
--- a/repo/image_state.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package repo
-
-import (
-	"github.com/jmoiron/sqlx"
-)
-
-const (
-	StateCreated    = "created"
-	StateQueued     = "queued"
-	StateInProgress = "in_progress"
-	StateDone       = "done"
-	StateError      = "error"
-)
-
-type ImageStates struct {
-	db         *sqlx.DB
-	queryGet   *sqlx.NamedStmt
-	stmtUpdate *sqlx.NamedStmt
-}
-
-func NewImageStateRepo(db *sqlx.DB) (repo ImageStates, err error) {
-	repo.db = db
-	repo.queryGet, err = db.PrepareNamed(`
-			SELECT state
-			FROM images
-			WHERE id = :imageId
-		`)
-	if err != nil {
-		return
-	}
-	repo.stmtUpdate, err = db.PrepareNamed(`
-			UPDATE images
-			SET state = :state
-			WHERE id = :imageId
-		`)
-	if err != nil {
-		return
-	}
-
-	return repo, nil
-}
-
-func (repo ImageStates) Get(imageId string) (state string, err error) {
-	err = repo.queryGet.Get(&state, map[string]interface{}{
-		"imageId": imageId,
-	})
-	return
-}
-
-func (repo ImageStates) Update(imageId string, state string) (err error) {
-	_, err = repo.stmtUpdate.Exec(map[string]interface{}{
-		"imageId": imageId,
-		"state":   state,
-	})
-	return
-}
diff --git a/repo/images.go b/repo/images.go
index a8b1531..57e3338 100644
--- a/repo/images.go
+++ b/repo/images.go
@@ -6,14 +6,23 @@ import (
 )
 
 type Images struct {
-	db         *sqlx.DB
-	queryList  *sqlx.NamedStmt
-	queryGet   *sqlx.NamedStmt
-	stmtCreate *sqlx.NamedStmt
-	stmtUpdate *sqlx.NamedStmt
-	stmtDelete *sqlx.NamedStmt
+	db              *sqlx.DB
+	queryList       *sqlx.NamedStmt
+	queryGet        *sqlx.NamedStmt
+	stmtCreate      *sqlx.NamedStmt
+	stmtUpdate      *sqlx.NamedStmt
+	stmtUpdateState *sqlx.NamedStmt
+	stmtDelete      *sqlx.NamedStmt
 }
 
+const (
+	StateCreated    = "created"
+	StateQueued     = "queued"
+	StateInProgress = "in_progress"
+	StateDone       = "done"
+	StateError      = "error"
+)
+
 func NewImageRepo(db *sqlx.DB) (repo Images, err error) {
 	repo.db = db
 	repo.queryList, err = db.PrepareNamed(`
@@ -23,7 +32,8 @@ func NewImageRepo(db *sqlx.DB) (repo Images, err error) {
 			       description,
 			       original_name,
 			       created_at,
-			       updated_at
+			       updated_at,
+			       state
 			FROM images
 			WHERE owner = :userId
 			ORDER BY created_at DESC
@@ -38,7 +48,8 @@ func NewImageRepo(db *sqlx.DB) (repo Images, err error) {
 			       description,
 			       original_name,
 			       created_at,
-			       updated_at
+			       updated_at,
+			       state
 			FROM images
 			WHERE id = :imageId
 		`)
@@ -62,6 +73,14 @@ func NewImageRepo(db *sqlx.DB) (repo Images, err error) {
 	if err != nil {
 		return
 	}
+	repo.stmtUpdateState, err = db.PrepareNamed(`
+			UPDATE images
+			SET state = :state
+			WHERE id = :imageId
+		`)
+	if err != nil {
+		return
+	}
 	repo.stmtDelete, err = db.PrepareNamed(`
 			DELETE FROM images
 			WHERE id = :imageId
@@ -120,6 +139,14 @@ func (repo Images) Update(changed model.Image) (err error) {
 	return
 }
 
+func (repo Images) UpdateState(imageId string, state string) (err error) {
+	_, err = repo.stmtUpdateState.Exec(map[string]interface{}{
+		"imageId": imageId,
+		"state":   state,
+	})
+	return
+}
+
 func (repo Images) Delete(changed model.Image) (err error) {
 	_, err = repo.stmtDelete.Exec(map[string]interface{}{
 		"imageId": changed.Id,
diff --git a/storage/storage.go b/storage/storage.go
index ab535a5..ffb3571 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -8,7 +8,7 @@ import (
 	"io"
 	"net/url"
 	"os"
-	"time"
+	"path/filepath"
 )
 
 type Storage struct {
@@ -60,12 +60,8 @@ func (storage Storage) DownloadFile(ctx context.Context, bucketName string, file
 	return
 }
 
-func (storage Storage) UrlFor(ctx context.Context, bucketName string, fileName string) (url *url.URL, err error) {
-	url, err = storage.s3client.PresignedGetObject(
-		ctx,
-		bucketName,
-		fileName,
-		7*24*time.Hour,
-		map[string][]string{})
-	return
+func (storage Storage) UrlFor(bucketName string, fileName string) *url.URL {
+	fileUrl := *storage.s3client.EndpointURL()
+	fileUrl.Path = filepath.Join(fileUrl.Path, bucketName, fileName)
+	return &fileUrl
 }
diff --git a/task/image_resize_processor.go b/task/image_resize_processor.go
index 20eaf98..8b79df8 100644
--- a/task/image_resize_processor.go
+++ b/task/image_resize_processor.go
@@ -31,7 +31,7 @@ func (processor *ImageProcessor) ProcessTask(ctx context.Context, task *asynq.Ta
 
 	println("parsed task: " + payload.ImageId)
 
-	if err = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateInProgress); err != nil {
+	if err = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateInProgress); err != nil {
 		println("failed to set image state: " + payload.ImageId)
 		println(err.Error())
 		return
@@ -44,7 +44,7 @@ func (processor *ImageProcessor) ProcessTask(ctx context.Context, task *asynq.Ta
 	if err != nil {
 		println("failed to create temp file: " + payload.ImageId)
 		println(err.Error())
-		_ = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateError)
+		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
 		return
 	}
 	err = processor.env.Storage.DownloadFile(
@@ -55,20 +55,20 @@ func (processor *ImageProcessor) ProcessTask(ctx context.Context, task *asynq.Ta
 	if err != nil {
 		println("failed to download file: " + sourceFile.Name())
 		println(err.Error())
-		_ = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateError)
+		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
 		return
 	}
 	if err = wand.ReadImage(sourceFile.Name()); err != nil {
 		println("failed to read file: " + sourceFile.Name())
 		println(err.Error())
-		_ = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateError)
+		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
 		return
 	}
 	var originalImage imgconv.ImageHandle
 	if originalImage, err = imgconv.NewImage(wand); err != nil {
 		println("failed to load file: " + sourceFile.Name())
 		println(err.Error())
-		_ = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateError)
+		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
 		return err
 	}
 
@@ -102,11 +102,11 @@ func (processor *ImageProcessor) ProcessTask(ctx context.Context, task *asynq.Ta
 	if err != nil {
 		println("failed to convert image file")
 		println(err.Error())
-		_ = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateError)
+		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
 		return
 	}
 
-	if err = processor.env.Repositories.ImageStates.Update(payload.ImageId, repo.StateDone); err != nil {
+	if err = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateDone); err != nil {
 		return
 	}
 
diff --git a/ui/src/ImageList.tsx b/ui/src/ImageList.tsx
index adc89b9..ca22322 100644
--- a/ui/src/ImageList.tsx
+++ b/ui/src/ImageList.tsx
@@ -10,16 +10,18 @@ export default function ImageList() {
             <p>{error as string}</p>
             <ul>
                 {data?.map(info => (
-                    <li>
-                        <p>{info.image?.id}</p>
-                        <p>{info.image?.title}</p>
-                        <p>{info.image?.description}</p>
-                        <p>{info.image?.original_name}</p>
-                        <p>{info.image?.mime_type}</p>
-                        <p>{info.image?.created_at}</p>
-                        <p>{info.image?.updated_at}</p>
+                    <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>
-                        <img src={info.url} alt=""/>
+                        <p>{info.url}</p>
+                        <img src={info.url+"t"} alt=""/>
                     </li>
                 ))}
             </ul>
diff --git a/ui/src/api/model/Image.ts b/ui/src/api/model/Image.ts
index 2af55c3..e944e64 100644
--- a/ui/src/api/model/Image.ts
+++ b/ui/src/api/model/Image.ts
@@ -7,4 +7,6 @@ export interface Image {
     updated_at: string,
     original_name: string,
     mime_type: string,
+    state: string,
+    url: string,
 }
diff --git a/ui/src/api/model/ImageInfo.ts b/ui/src/api/model/ImageInfo.ts
deleted file mode 100644
index bbfa239..0000000
--- a/ui/src/api/model/ImageInfo.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import {Image} from "./Image";
-
-export interface ImageInfo {
-    image: Image,
-    state: string,
-    url: string,
-}
diff --git a/ui/src/api/useListImages.ts b/ui/src/api/useListImages.ts
index a6424b5..d0e7ccf 100644
--- a/ui/src/api/useListImages.ts
+++ b/ui/src/api/useListImages.ts
@@ -1,13 +1,13 @@
 import {useQuery} from "react-query";
 import axios from "axios";
 import {useBaseUrl} from "./baseUrlContext";
-import {ImageInfo} from "./model/ImageInfo";
+import {Image} from "./model/Image";
 
 export const useListImages = () => {
     const baseUrl = useBaseUrl();
     return useQuery(
         "connector-deployments",
-        () => axios.get<ImageInfo[]>(
+        () => axios.get<Image[]>(
             "api/v1/images",
             {
                 baseURL: baseUrl
-- 
GitLab