From 8a99526c35e3e524cb1c78983221fcca3486bd87 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <janne@kuschku.de> Date: Sun, 25 Jul 2021 18:09:59 +0200 Subject: [PATCH] Rewrite in progress2 Signed-off-by: Janne Mareike Koschinski <janne@kuschku.de> --- main.go | 114 ++++++++-- model/album.go | 1 + model/album_image.go | 1 - page_image_detail.go | 101 --------- repo/album_repository.go | 80 +------ repo/albumimage_repository.go | 199 ++++++++++++++++++ templates/album_detail.gohtml | 101 +++++++++ ...{image_detail.html => image_detail.gohtml} | 4 +- 8 files changed, 401 insertions(+), 200 deletions(-) delete mode 100644 page_image_detail.go create mode 100644 repo/albumimage_repository.go create mode 100644 templates/album_detail.gohtml rename templates/{image_detail.html => image_detail.gohtml} (89%) diff --git a/main.go b/main.go index f3d0649..f85b689 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "database/sql" "fmt" + "git.kuschku.de/justjanne/imghost-frontend/model" "git.kuschku.de/justjanne/imghost-frontend/repo" "github.com/go-redis/redis" "github.com/gorilla/mux" @@ -10,6 +11,35 @@ import ( "net/http" ) +type ImageViewModel struct { + User model.User + Image model.Image +} + +type AlbumViewModel struct { + User model.User + Album model.Album + Images []model.AlbumImage +} + +func MethodOverride(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + method := r.PostFormValue("_method") + if method == "" { + method = r.Header.Get("X-HTTP-Method-Override") + } + + if method == http.MethodPut || + method == http.MethodPatch || + method == http.MethodDelete { + r.Method = method + } + } + next.ServeHTTP(w, r) + }) +} + func main() { config := NewConfigFromEnv() @@ -31,6 +61,7 @@ func main() { imageRepo := repo.NewImageRepository(pageContext.Database) albumRepo := repo.NewAlbumRepository(pageContext.Database) + albumImageRepo := repo.NewAlbumImageRepository(pageContext.Database) router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -42,13 +73,17 @@ func main() { user := parseUser(r) imageId := mux.Vars(r)["imageId"] image, err := imageRepo.Get(imageId) - if err != nil { panic(err) } + if err != nil { + panic(err) + } - err = formatTemplate(w, "image_detail.html", ImageDetailData{ + err = formatTemplate(w, "image_detail.gohtml", ImageViewModel{ user, image, }) - if err != nil { panic(err) } + if err != nil { + panic(err) + } }).Methods("GET") router.HandleFunc("/i/{imageId}", func(w http.ResponseWriter, r *http.Request) { var err error @@ -75,16 +110,22 @@ func main() { user := parseUser(r) albumId := mux.Vars(r)["albumId"] album, err := albumRepo.Get(albumId) - if err != nil { panic(err) } - images, err := albumRepo.GetImages(album) - if err != nil { panic(err) } - - err = formatTemplate(w, "album_detail.html", AlbumDetailData{ + if err != nil { + panic(err) + } + images, err := albumImageRepo.List(album) + if err != nil { + panic(err) + } + + err = formatTemplate(w, "album_detail.gohtml", AlbumViewModel{ user, album, images, }) - if err != nil { panic(err) } + if err != nil { + panic(err) + } }).Methods("GET") router.HandleFunc("/a/{albumId}", func(w http.ResponseWriter, r *http.Request) { var err error @@ -95,15 +136,51 @@ func main() { if err != nil { panic(err) } err = album.VerifyOwner(user) - if err != nil { panic(err) } + if err != nil { + panic(err) + } album.Title = r.FormValue("title") album.Description = r.FormValue("description") err = albumRepo.Update(album) - if err != nil { panic(err) } + if err != nil { + panic(err) + } + + http.Redirect(w, r, "/a/"+albumId, http.StatusFound) + }).Methods("POST") + router.HandleFunc("/a/{albumId}/{imageId}", func(w http.ResponseWriter, r *http.Request) { + var err error + + user := parseUser(r) + vars := mux.Vars(r) + albumId := vars["albumId"] + imageId := vars["imageId"] + album, err := albumRepo.Get(albumId) + if err != nil { + panic(err) + } + + err = album.VerifyOwner(user) + if err != nil { + panic(err) + } + + albumImage, err := albumImageRepo.Get(album, imageId) + if err != nil { + panic(err) + } - http.Redirect(w, r, "/a/" + albumId, http.StatusFound) + albumImage.Title = r.FormValue("title") + albumImage.Description = r.FormValue("description") + + err = albumImageRepo.Update(album, albumImage) + if err != nil { + panic(err) + } + + http.Redirect(w, r, "/a/"+albumId, http.StatusFound) }).Methods("POST") router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(fmt.Sprintf("postgres: %v\n", pageContext.Database.Ping()))) @@ -112,19 +189,16 @@ func main() { router.HandleFunc("/{imageId}", func(w http.ResponseWriter, r *http.Request) { var err error - user := parseUser(r) imageId := mux.Vars(r)["imageId"] image, err := imageRepo.Get(imageId) - if err != nil { panic(err) } + if err != nil { + panic(err) + } - err = formatTemplate(w, "image_detail.html", ImageDetailData{ - user, - image, - }) - if err != nil { panic(err) } + http.Redirect(w, r, fmt.Sprintf("/i/%s", image.Id), http.StatusFound) }) - err = http.ListenAndServe(":8080", router) + err = http.ListenAndServe(":8080", MethodOverride(router)) if err != nil { panic(err) } diff --git a/model/album.go b/model/album.go index c385d91..59f060d 100644 --- a/model/album.go +++ b/model/album.go @@ -11,6 +11,7 @@ type Album struct { Title string `json:"title"` Description string `json:"description"` CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func (album Album) VerifyOwner(user User) error { diff --git a/model/album_image.go b/model/album_image.go index 3379dfe..3bb099c 100644 --- a/model/album_image.go +++ b/model/album_image.go @@ -4,5 +4,4 @@ type AlbumImage struct { Id string `json:"id"` Title string `json:"title"` Description string `json:"description"` - Position int `json:"position"` } diff --git a/page_image_detail.go b/page_image_detail.go deleted file mode 100644 index 8759da5..0000000 --- a/page_image_detail.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "fmt" - "git.kuschku.de/justjanne/imghost-frontend/model" - _ "github.com/lib/pq" - "net/http" - "os" - "path" -) - -type ImageDetailData struct { - User model.User - Image model.Image -} - -func pageImageDetail(ctx PageContext) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user := parseUser(r) - _, imageId := path.Split(r.URL.Path) - - result, err := ctx.Database.Query(` - SELECT - id, - owner, - coalesce(title, ''), - coalesce(description, ''), - coalesce(created_at, to_timestamp(0)), - coalesce(original_name, ''), - coalesce(type, '') - FROM images - WHERE id = $1 - `, imageId) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") - return - } - - var info model.Image - - if result.Next() { - var owner string - err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") - return - } - - switch r.PostFormValue("action") { - case "update": - _, err = ctx.Database.Exec( - "UPDATE images SET title = $1, description = $2 WHERE id = $3 AND owner = $4", - r.PostFormValue("title"), - r.PostFormValue("description"), - info.Id, - user.Id, - ) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") - return - } - if r.PostFormValue("from_js") == "true" { - _ = returnJson(w, true) - } else { - http.Redirect(w, r, r.URL.Path, http.StatusFound) - } - return - case "delete": - _, err = ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") - return - } - for _, definition := range ctx.Config.Sizes { - err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix))) - if err != nil && !os.IsNotExist(err) { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") - return - } - } - http.Redirect(w, r, "/me/images", http.StatusFound) - return - } - - if err = formatTemplate(w, "image_detail.html", ImageDetailData{ - user, - info, - }); err != nil { - panic(err) - } - return - } - - _ = returnError(w, http.StatusNotFound, "Image Not Found") - }) -} diff --git a/repo/album_repository.go b/repo/album_repository.go index 4ced2fb..f55f3af 100644 --- a/repo/album_repository.go +++ b/repo/album_repository.go @@ -60,7 +60,8 @@ func (repo AlbumRepository) Get(albumId string) (model.Album, error) { owner, coalesce(title, ''), coalesce(description, ''), - coalesce(created_at, to_timestamp(0)) + coalesce(created_at, to_timestamp(0)), + coalesce(updated_at, to_timestamp(0)) FROM albums WHERE id = $1`, albumId) @@ -71,7 +72,7 @@ func (repo AlbumRepository) Get(albumId string) (model.Album, error) { if result.Next() { if err := result.Scan( &album.Id, &album.Owner, &album.Title, &album.Description, - &album.CreatedAt, + &album.CreatedAt, &album.UpdatedAt, ); err != nil { return album, err } @@ -121,78 +122,3 @@ func (repo AlbumRepository) Delete(album model.Album) error { return nil } - -func (repo AlbumRepository) AddImage(album model.Album, image model.AlbumImage) error { - if _, err := repo.db.Exec(` - INSERT INTO album_images (album, image, title, description, position) - VALUES ($1, $2, $3, $4)`, - album.Id, - image.Id, - image.Title, - image.Description, - image.Position, - ); err != nil { - return err - } - - return nil -} - -func (repo AlbumRepository) GetImages(album model.Album) ([]model.AlbumImage, error) { - var albumImages []model.AlbumImage - - result, err := repo.db.Query(` - SELECT - image, - coalesce(title, ''), - coalesce(description, ''), - position - FROM album_images`) - if err != nil { - return albumImages, err - } - - for result.Next() { - var albumImage model.AlbumImage - - if err := result.Scan( - &albumImage.Id, &albumImage.Title, &albumImage.Description, - &albumImage.Position, - ); err != nil { - return albumImages, err - } - albumImages = append(albumImages, albumImage) - } - - return albumImages, nil -} - -func (repo AlbumRepository) RemoveImage(album model.Album, imageId string) error { - if _, err := repo.db.Exec( - "DELETE FROM album_images WHERE album = $1 AND image = $2", - album.Id, - imageId, - ); err != nil { - return err - } - - return nil -} - -func (repo AlbumRepository) ReorderImages(album model.Album, images []model.AlbumImage) error { - if _, err := repo.db.Exec( - "DELETE FROM album_images WHERE album = $1", - album.Id, - ); err != nil { - return err - } - - for _, image := range images { - if err := repo.AddImage(album, image); err != nil { - return err - } - } - - return nil -} - diff --git a/repo/albumimage_repository.go b/repo/albumimage_repository.go new file mode 100644 index 0000000..60ccfb4 --- /dev/null +++ b/repo/albumimage_repository.go @@ -0,0 +1,199 @@ +package repo + +import ( + "database/sql" + "git.kuschku.de/justjanne/imghost-frontend/model" + "time" +) + +type AlbumImageRepository struct { + db *sql.DB +} + +func NewAlbumImageRepository(db *sql.DB) AlbumImageRepository { + return AlbumImageRepository{ + db: db, + } +} + +func (repo AlbumImageRepository) List(album model.Album) ([]model.AlbumImage, error) { + var albums []model.AlbumImage + + result, err := repo.db.Query(` + SELECT + image, + coalesce(title, ''), + coalesce(description, '') + FROM album_images + WHERE album = $1 + ORDER BY position + `, album.Id) + if err != nil { + return albums, err + } + + for result.Next() { + var album model.AlbumImage + + if err := result.Scan( + &album.Id, &album.Title, &album.Description, + ); err != nil { + return albums, err + } + albums = append(albums, album) + } + + return albums, nil +} + +func (repo AlbumImageRepository) Get(album model.Album, imageId string) (model.AlbumImage, error) { + var albumImage model.AlbumImage + + result, err := repo.db.Query(` + SELECT + image, + coalesce(title, ''), + coalesce(description, '') + FROM album_images + WHERE album = $1 + AND image = $2 + ORDER BY position`, + album.Id, + imageId) + if err != nil { + return albumImage, err + } + + if result.Next() { + if err := result.Scan( + &albumImage.Id, &albumImage.Title, &albumImage.Description, + ); err != nil { + return albumImage, err + } + } + + return albumImage, nil +} + +func (repo AlbumImageRepository) Append(album model.Album, image model.AlbumImage) error { + if _, err := repo.db.Exec(` + INSERT INTO album_images (album, image, title, description, position) + VALUES ($1, $2, $3, $4, ( + SELECT COUNT(image) + FROM album_images + WHERE album = $1 + ))`, + album.Id, + image.Id, + image.Title, + image.Description, + ); err != nil { + return err + } + if _, err := repo.db.Exec(` + UPDATE albums SET updated_at = $2 WHERE id = $1`, + album.Id, + time.Now().UTC(), + ); err != nil { + return err + } + + return nil +} + +func (repo AlbumImageRepository) Update(album model.Album, image model.AlbumImage) error { + if _, err := repo.db.Exec(` + UPDATE album_images + SET title = $3, description = $4 + WHERE album = $1 + AND image = $2`, + album.Id, + image.Id, + image.Title, + image.Description, + ); err != nil { + return err + } + if _, err := repo.db.Exec(` + UPDATE albums SET updated_at = $2 WHERE id = $1`, + album.Id, + time.Now().UTC(), + ); err != nil { + return err + } + + return nil +} + +func (repo AlbumImageRepository) DeleteAll(album model.Album) error { + if _, err := repo.db.Exec( + "DELETE FROM album_images WHERE album = $1", + album.Id, + ); err != nil { + return err + } + if _, err := repo.db.Exec(` + UPDATE albums SET updated_at = $2 WHERE id = $1`, + album.Id, + time.Now().UTC(), + ); err != nil { + return err + } + + return nil +} + +func (repo AlbumImageRepository) Delete(album model.Album, image model.AlbumImage) error { + if _, err := repo.db.Exec( + "DELETE FROM album_images WHERE album = $1 AND image = $2", + album.Id, + image.Id, + ); err != nil { + return err + } + if _, err := repo.db.Exec(` + UPDATE albums SET updated_at = $2 WHERE id = $1`, + album.Id, + time.Now().UTC(), + ); err != nil { + return err + } + + return nil +} + +func (repo AlbumImageRepository) Reorder(album model.Album, images []model.AlbumImage) error { + tx, err := repo.db.Begin() + if err != nil { + return err + } + + for index, image := range images { + if _, err := tx.Exec(` + UPDATE album_images + SET position = $3 + WHERE album = $1 + AND image = $2`, + album.Id, + image.Id, + index, + ); err != nil { + return err + } + } + + if _, err := tx.Exec(` + UPDATE albums SET updated_at = $2 WHERE id = $1`, + album.Id, + time.Now().UTC(), + ); err != nil { + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil +} diff --git a/templates/album_detail.gohtml b/templates/album_detail.gohtml new file mode 100644 index 0000000..6cce2d6 --- /dev/null +++ b/templates/album_detail.gohtml @@ -0,0 +1,101 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.AlbumViewModel*/ -}} +<form> + <h1>User</h1> + <p> + <label> + Id:<br> + <input type="text" disabled value="{{.User.Id}}"> + </label> + </p> + <p> + <label> + Name:<br> + <input type="text" disabled value="{{.User.Name}}"> + </label> + </p> + <p> + <label> + Email:<br> + <input type="text" disabled value="{{.User.Email}}"> + </label> + </p> + <p><b>Roles: </b></p> + <ul> + {{range .User.Roles}} + <li>{{.}}</li> + {{end}} + </ul> +</form> +<form method="post"> + <h1>Album</h1> + <p> + <label> + Id:<br> + <input type="text" disabled value="{{.Album.Id}}"> + </label> + </p> + <p> + <label> + Owner:<br> + <input type="text" disabled value="{{.Album.Owner}}"> + </label> + </p> + <p> + <label> + Title:<br> + <input type="text" name="title" value="{{.Album.Title}}"> + </label> + </p> + <p> + <label> + Description:<br> + <input type="text" name="description" value="{{.Album.Description}}"> + </label> + </p> + <p> + <label> + CreatedAt:<br> + <input type="text" disabled value="{{.Album.CreatedAt}}"> + </label> + </p> + <p> + <label> + UpdatedAt:<br> + <input type="text" disabled value="{{.Album.UpdatedAt}}"> + </label> + </p> + <p> + <input type="submit"> + </p> +</form> +<ul> + {{range .Images}} + <li> + <form method="post" action="/a/{{$.Album.Id}}/{{.Id}}"> + <h1>Image</h1> + <p> + <label> + Id:<br> + <input type="text" disabled value="{{.Id}}"> + </label> + </p> + <p> + <label> + Title:<br> + <input type="text" name="title" value="{{.Title}}"> + </label> + </p> + <p> + <label> + Description:<br> + <input type="text" name="description" value="{{.Description}}"> + </label> + </p> + <p> + <input type="submit"> + </p> + </form> + <img src="https://i.k8r.eu/{{.Image.Id}}l"/> + </li> + {{end}} +</ul> diff --git a/templates/image_detail.html b/templates/image_detail.gohtml similarity index 89% rename from templates/image_detail.html rename to templates/image_detail.gohtml index c8e455a..1da70f2 100644 --- a/templates/image_detail.html +++ b/templates/image_detail.gohtml @@ -1,4 +1,4 @@ -{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageDetailData*/ -}} +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageViewModel*/ -}} <form> <h1>User</h1> <p> @@ -27,6 +27,7 @@ </ul> </form> <form method="post"> + <input type="hidden" name="_method" value="post"> <h1>Image</h1> <p> <label> @@ -80,3 +81,4 @@ <input type="submit"> </p> </form> +<img src="https://i.k8r.eu/{{.Image.Id}}l"/> -- GitLab