Skip to content
Snippets Groups Projects
Verified Commit cceb6083 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Rewrite in progress

parent bdd31013
No related branches found
No related tags found
1 merge request!1Replace entire project structure
Pipeline #2568 failed
@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Regular.eot");src:url("/assets/fonts/Lato-Regular.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Regular.woff2") format("woff2"),url("/assets/fonts/Lato-Regular.woff") format("woff"),url("/assets/fonts/Lato-Regular.ttf") format("truetype");font-style:normal;font-weight:normal;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Bold.eot");src:url("/assets/fonts/Lato-Bold.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Bold.woff2") format("woff2"),url("/assets/fonts/Lato-Bold.woff") format("woff"),url("/assets/fonts/Lato-Bold.ttf") format("truetype");font-style:normal;font-weight:bold;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-BoldItalic.eot");src:url("/assets/fonts/Lato-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-BoldItalic.woff2") format("woff2"),url("/assets/fonts/Lato-BoldItalic.woff") format("woff"),url("/assets/fonts/Lato-BoldItalic.ttf") format("truetype");font-style:italic;font-weight:bold;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Italic.eot");src:url("/assets/fonts/Lato-Italic.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Italic.woff2") format("woff2"),url("/assets/fonts/Lato-Italic.woff") format("woff"),url("/assets/fonts/Lato-Italic.ttf") format("truetype");font-style:italic;font-weight:normal;text-rendering:optimizeLegibility}
*{margin:0;padding:0}*:focus{outline:none}*::-moz-focus-inner{border:0}html{height:100%}body{background:#282828;font-family:'Lato', sans-serif;font-size:81.25%;min-height:100%;display:flex;flex-direction:column}.container.centered{flex-grow:1;display:flex;margin:0 auto 64px auto;padding:0 16px;flex-direction:column;justify-content:center;width:100%;max-width:640px}nav{position:sticky;top:0;background:#333333;box-shadow:0 2px 4px rgba(0,0,0,0.2);padding:0 16px;z-index:100}nav ul{display:flex;max-width:1024px;margin:0 auto;height:56px;align-items:center}nav ul li,nav ul li>*{color:#282828;text-decoration:none}nav ul li{display:block;line-height:24px}@media (max-width: 640px){nav ul li.images,nav ul li.albums{display:none}}nav ul li.title a{display:inline-block;line-height:56px}nav ul li.title a img{width:32px;height:32px;vertical-align:middle}nav ul li.spacer{flex-grow:1}nav ul li:not(.spacer){margin:0 8px}nav ul li:not(.spacer):first-child{margin-left:0}nav ul li:not(.spacer):last-child{margin-right:0}nav ul li:not(.spacer):not(.title) a{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1)}nav ul li:not(.spacer):not(.title) a:hover,nav ul li:not(.spacer):not(.title) a:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.page.upload{flex-grow:1;display:flex;flex-direction:column}.page.upload .alert{padding:16px;margin:16px 0;box-shadow:0 2px 4px rgba(33,33,33,0.2);text-decoration:none;border-radius:2px}.page.upload .alert.success{background:#DCEDC8;color:#689F38;border-color:#689F38}.page.upload .alert.success a{color:#33691E}.page.upload .alert.error{background:#FFEBEE;color:#F44336;border-color:#F44336}.page.upload .alert.error a{color:#D32F2F}.page.upload form.upload{padding:96px 0;box-shadow:0 2px 4px rgba(0,0,0,0.2);text-decoration:none;border-radius:2px;text-align:center;background:#333333}.page.upload form.upload .upload-label{font-size:18pt;color:#fff}.page.upload form.upload label{position:relative;display:inline-block;overflow:hidden}.page.upload form.upload label span.text{position:relative;display:inline-block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);line-height:24px;color:#282828;cursor:pointer;z-index:1}.page.upload form.upload label span.text:hover,.page.upload form.upload label span.text:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.page.upload form.upload label input[type=file]{position:absolute;left:0;right:0;top:0;bottom:0;opacity:0}.page.upload .uploading-images{display:flex;flex-direction:row}.page.upload .uploading-images .images{display:flex;flex-direction:column;align-items:stretch;flex-grow:1}.page.upload .uploading-images .images .detail{margin-bottom:32px}.page.upload .uploading-images .images .detail .image{position:relative}.page.upload .uploading-images .images .detail .image .progress{position:absolute;top:-4px;left:0;right:0;height:4px;display:block;background-color:rgba(255,193,7,0.2);overflow:hidden;transition:opacity 400ms}.page.upload .uploading-images .images .detail .image .progress .indeterminate{background-color:rgba(255,193,7,0.8)}.page.upload .uploading-images .images .detail .image .progress .indeterminate::before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.page.upload .uploading-images .images .detail .image .progress .indeterminate::after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation-delay:1.125s}.page.upload .uploading-images .images .detail:not(.uploading) .progress{opacity:0}.page.upload.submitted .container.centered{display:none}.page.upload:not(.submitted) .uploading-images{display:none}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.page.image.detail,.page.upload .uploading-images{display:flex;flex-direction:row;align-items:start;align-self:center;padding:32px 32px 128px;justify-content:center}@media (max-width: 1024px){.page.image.detail,.page.upload .uploading-images{flex-direction:column;padding:32px 0;align-items:stretch;justify-content:start}}.page.image.detail .sidebar,.page.upload .uploading-images .sidebar{width:250px;max-width:640px;margin-left:32px}.page.image.detail .sidebar .info,.page.upload .uploading-images .sidebar .info{background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4);padding:8px;margin-bottom:8px;color:#ffffff}@media (max-width: 1024px){.page.image.detail .sidebar .info,.page.upload .uploading-images .sidebar .info{padding:16px;margin-bottom:32px}}.page.image.detail .sidebar .url div,.page.upload .uploading-images .sidebar .url div{display:flex;flex-direction:row}.page.image.detail .sidebar .url div input,.page.upload .uploading-images .sidebar .url div input{background:none;color:#fff;opacity:0.4;border:none;flex-shrink:1;display:block;flex-grow:1;width:0;text-overflow:ellipsis}.page.image.detail .sidebar .url div button.copy,.page.upload .uploading-images .sidebar .url div button.copy{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);border:none;font-family:'Lato', sans-serif;font-size:10pt;line-height:24px;margin-left:8px;cursor:pointer}.page.image.detail .sidebar .url div button.copy:hover,.page.image.detail .sidebar .url div button.copy:focus,.page.upload .uploading-images .sidebar .url div button.copy:hover,.page.upload .uploading-images .sidebar .url div button.copy:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.page.image.detail .sidebar .actions,.page.upload .uploading-images .sidebar .actions{background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4);display:flex;flex-direction:row;margin-bottom:8px}@media (max-width: 1024px){.page.image.detail .sidebar .actions,.page.upload .uploading-images .sidebar .actions{margin-bottom:32px}}.page.image.detail .sidebar .actions .delete-form,.page.image.detail .sidebar .actions .update-form,.page.upload .uploading-images .sidebar .actions .delete-form,.page.upload .uploading-images .sidebar .actions .update-form{display:flex;flex-grow:1;flex-basis:0}.page.image.detail .sidebar .actions .delete-form input[type=submit],.page.image.detail .sidebar .actions .update-form input[type=submit],.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit],.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);border:none;font-family:'Lato', sans-serif;font-size:10pt;line-height:24px;margin:8px;flex-grow:1;cursor:pointer}@media (max-width: 1024px){.page.image.detail .sidebar .actions .delete-form input[type=submit],.page.image.detail .sidebar .actions .update-form input[type=submit],.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit],.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]{margin:16px}}.page.image.detail .sidebar .actions .delete-form input[type=submit]:hover,.page.image.detail .sidebar .actions .delete-form input[type=submit]:focus,.page.image.detail .sidebar .actions .update-form input[type=submit]:hover,.page.image.detail .sidebar .actions .update-form input[type=submit]:focus,.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit]:hover,.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit]:focus,.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]:hover,.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}@media (max-width: 1024px){.page.image.detail .sidebar,.page.upload .uploading-images .sidebar{width:auto;margin-left:0;margin-top:32px}}.page.image.detail .detail,.page.upload .uploading-images .detail{max-width:640px;width:100%;background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4)}.page.image.detail .detail .title,.page.upload .uploading-images .detail .title{line-height:1.25;padding:16px;vertical-align:middle;font-size:14pt;font-weight:normal;color:#eee;background:none;border:none;width:100%;box-sizing:border-box}.page.image.detail .detail .title:not(.fake-input):empty,.page.upload .uploading-images .detail .title:not(.fake-input):empty{display:none}.page.image.detail .detail .title:not(.fake-input):empty+.image,.page.upload .uploading-images .detail .title:not(.fake-input):empty+.image{border-top-left-radius:4px;border-top-right-radius:4px}.page.image.detail .detail .image,.page.upload .uploading-images .detail .image{background:#000;display:flex;flex-direction:row;justify-content:center;align-items:start}.page.image.detail .detail .image img,.page.upload .uploading-images .detail .image img{max-width:100%}.page.image.detail .detail .description,.page.upload .uploading-images .detail .description{line-height:1.25;padding:16px;vertical-align:middle;font-size:11pt;font-weight:normal;color:#eee;background:none;border:none;width:100%;box-sizing:border-box;font-family:'Lato', sans-serif;resize:vertical;white-space:pre-line}.page.image.detail .detail .description:not(.fake-input):empty,.page.upload .uploading-images .detail .description:not(.fake-input):empty{display:none}.page.image.detail .fake-input[contenteditable]:empty:before,.page.upload .uploading-images .fake-input[contenteditable]:empty:before{opacity:0.4;content:attr(placeholder)}.page.image.list{display:flex;max-width:1024px;margin:0 auto;align-items:start;flex-wrap:wrap}.page.image.list .image{padding:8px;margin:8px;position:relative;box-shadow:0 2px 4px rgba(0,0,0,0.2);transition:all 200ms;text-decoration:none;width:160px;background:#333333;border-radius:2px}.page.image.list .image:hover,.page.image.list .image:focus{margin-top:4px;box-shadow:0 4px 6px rgba(0,0,0,0.4)}.page.image.list .image .image-container{display:flex;justify-content:center;align-content:center;justify-items:center;align-items:center;flex-direction:column;height:160px;width:160px;background:#000000}.page.image.list .image .info{display:block;z-index:1;color:#eeeeee;line-height:1.25;font-size:10pt;padding-top:12px}.page.image.list .image .info p{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
......@@ -14,18 +14,17 @@
width: 250px
max-width: 640px
margin-left: 32px
.url
.info
background: #333333
border-radius: 4px
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4)
padding: 8px
margin-bottom: 8px
color: #ffffff
@media (max-width: 1024px)
padding: 16px
margin-bottom: 32px
p
color: #ffffff
div
.url div
display: flex
flex-direction: row
input
......
......@@ -4,6 +4,8 @@ go 1.13
require (
github.com/go-redis/redis v6.15.6+incompatible
github.com/gorilla/mux v1.8.0 // indirect
github.com/justjanne/imgconv v1.0.3
github.com/lib/pq v1.10.2
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
......
......@@ -4,8 +4,12 @@ github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be
github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/justjanne/imgconv v1.0.3 h1:XXgqeLJ1ibV0XCdosMqVV65CdpmDADTnC3yBR2w52vE=
github.com/justjanne/imgconv v1.0.3/go.mod h1:8VxkQjMdEOziT9LJ8Mrqtz7SbmqlpExl69K8/CZWeAc=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
......@@ -25,6 +29,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gographics/imagick.v2 v2.6.0 h1:ewRsUQk3QkjGumERlndbFn/kTYRjyMaPY5gxwpuAhik=
gopkg.in/gographics/imagick.v2 v2.6.0/go.mod h1:/QVPLV/iKdNttRKthmDkeeGg+vdHurVEPc8zkU0XgBk=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
......
......@@ -2,7 +2,10 @@ package main
import (
"database/sql"
"fmt"
"git.kuschku.de/justjanne/imghost-frontend/repo"
"github.com/go-redis/redis"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
"net/http"
)
......@@ -26,20 +29,102 @@ func main() {
http.FileServer(http.Dir("assets")),
}
http.Handle("/upload/", pageUpload(pageContext))
imageRepo := repo.NewImageRepository(pageContext.Database)
albumRepo := repo.NewAlbumRepository(pageContext.Database)
http.Handle("/i/", http.StripPrefix("/i/", pageImageDetail(pageContext)))
http.Handle("/a/", http.StripPrefix("/a/", pageAlbumDetail(pageContext)))
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Home Page"))
})
router.HandleFunc("/i/{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) }
err = formatTemplate(w, "image_detail.html", ImageDetailData{
user,
image,
})
if err != nil { panic(err) }
}).Methods("GET")
router.HandleFunc("/i/{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) }
http.Handle("/me/images/", pageImageList(pageContext))
http.Handle("/assets/", http.StripPrefix("/assets/", pageContext.AssetServer))
http.Handle("/", pageIndex(pageContext))
err = image.VerifyOwner(user)
if err != nil { panic(err) }
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
image.Title = r.FormValue("title")
image.Description = r.FormValue("description")
err = imageRepo.Update(image)
if err != nil { panic(err) }
http.Redirect(w, r, "/i/" + imageId, http.StatusFound)
}).Methods("POST")
router.HandleFunc("/a/{albumId}", func(w http.ResponseWriter, r *http.Request) {
var err error
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{
user,
album,
images,
})
if err != nil { panic(err) }
}).Methods("GET")
router.HandleFunc("/a/{albumId}", func(w http.ResponseWriter, r *http.Request) {
var err error
user := parseUser(r)
albumId := mux.Vars(r)["albumId"]
album, err := albumRepo.Get(albumId)
if err != nil { panic(err) }
err = album.VerifyOwner(user)
if err != nil { panic(err) }
album.Title = r.FormValue("title")
album.Description = r.FormValue("description")
err = albumRepo.Update(album)
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())))
_, _ = w.Write([]byte(fmt.Sprintf("redis: %v\n", pageContext.Redis.Ping().Err())))
})
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) }
err = formatTemplate(w, "image_detail.html", ImageDetailData{
user,
image,
})
if err != nil { panic(err) }
})
err = http.ListenAndServe(":8080", nil)
err = http.ListenAndServe(":8080", router)
if err != nil {
panic(err)
}
......
package model
import (
"errors"
"time"
)
type Album struct {
Id string `json:"id"`
Owner string `json:"owner"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
func (album Album) VerifyOwner(user User) error {
if album.Owner != user.Id {
return errors.New("user does not have ownership over this item")
}
return nil
}
package model
type AlbumImage struct {
Id string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Position int `json:"position"`
}
package model
import (
"errors"
"time"
)
type Image struct {
Id string `json:"id"`
Owner string `json:"owner"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OriginalName string `json:"original_name"`
MimeType string `json:"mime_type"`
}
func (image Image) VerifyOwner(user User) error {
if image.Owner != user.Id {
return errors.New("user does not have ownership over this item")
}
return nil
}
package model
type User struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Roles []string `json:"roles"`
}
func (info User) HasRole(role string) bool {
for _, r := range info.Roles {
if r == role {
return true
}
}
return false
}
package main
import (
"net/http"
"fmt"
"path"
)
type AlbumDetailData struct {
User UserInfo
Album Album
IsMine bool
}
func pageAlbumDetail(ctx PageContext) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := parseUser(r)
_, albumId := path.Split(r.URL.Path)
result, err := ctx.Database.Query(`
SELECT
id,
owner,
coalesce(title, ''),
coalesce(description, ''),
coalesce(created_at, to_timestamp(0))
FROM albums
WHERE id = $1
`, albumId)
if err != nil {
panic(err)
}
var info Album
if result.Next() {
var owner string
err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt)
if err != nil {
panic(err)
}
result, err := ctx.Database.Query(`
SELECT
image,
title,
description,
position
FROM album_images
WHERE album = $1
ORDER BY position ASC
`, albumId)
if err != nil {
panic(err)
}
for result.Next() {
var image AlbumImage
err := result.Scan(&image.Id, &owner, &image.Title, &image.Description, &image.Position)
if err != nil {
panic(err)
}
info.Images = append(info.Images, image)
}
if err = formatTemplate(w, "album_detail.html", AlbumDetailData{
user,
info,
owner == user.Id,
}); err != nil {
panic(err)
}
return
}
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Album not found")
})
}
package main
import (
"net/http"
)
func pageAlbumList(ctx PageContext) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})
}
......@@ -2,6 +2,7 @@ package main
import (
"fmt"
"git.kuschku.de/justjanne/imghost-frontend/model"
_ "github.com/lib/pq"
"net/http"
"os"
......@@ -9,9 +10,8 @@ import (
)
type ImageDetailData struct {
User UserInfo
Image Image
IsMine bool
User model.User
Image model.Image
}
func pageImageDetail(ctx PageContext) http.Handler {
......@@ -37,7 +37,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
return
}
var info Image
var info model.Image
if result.Next() {
var owner string
......@@ -90,7 +90,6 @@ func pageImageDetail(ctx PageContext) http.Handler {
if err = formatTemplate(w, "image_detail.html", ImageDetailData{
user,
info,
owner == user.Id,
}); err != nil {
panic(err)
}
......
package main
import (
"net/http"
)
type ImageListData struct {
User UserInfo
Images []Image
}
func pageImageList(ctx PageContext) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := parseUser(r)
result, err := ctx.Database.Query(`
SELECT
id,
coalesce(title, ''),
coalesce(description, ''),
coalesce(created_at, to_timestamp(0)),
coalesce(original_name, ''),
coalesce(type, '')
FROM images
WHERE owner = $1
ORDER BY created_at DESC
`, user.Id)
if err != nil {
panic(err)
}
var images []Image
for result.Next() {
var info Image
err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType)
if err != nil {
panic(err)
}
images = append(images, info)
}
if err = formatTemplate(w, "image_list.html", ImageListData{
user,
images,
}); err != nil {
panic(err)
}
})
}
package main
import (
"git.kuschku.de/justjanne/imghost-frontend/model"
"net/http"
"strings"
)
type IndexData struct {
User UserInfo
User model.User
}
func removeFileExtensions(path string) string {
......
package main
import (
"net/http"
"fmt"
"time"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"git.kuschku.de/justjanne/imghost-frontend/model"
"io"
"mime/multipart"
"path/filepath"
"net/http"
"os"
"encoding/base64"
"crypto/rand"
"path/filepath"
"time"
)
type UploadData struct {
User UserInfo
User model.User
Results []Result
}
......@@ -54,21 +55,21 @@ func writeBody(reader io.ReadCloser, path string) error {
return out.Close()
}
func createImage(config *Config, body io.ReadCloser, fileHeader *multipart.FileHeader) (Image, error) {
func createImage(config *Config, body io.ReadCloser, fileHeader *multipart.FileHeader) (model.Image, error) {
id := generateId()
path := filepath.Join(config.SourceFolder, id)
err := writeBody(body, path)
if err != nil {
return Image{}, err
return model.Image{}, err
}
mimeType, err := detectMimeType(path)
if err != nil {
return Image{}, err
return model.Image{}, err
}
image := Image{
image := model.Image{
Id: id,
OriginalName: filepath.Base(fileHeader.Filename),
CreatedAt: time.Now(),
......@@ -77,7 +78,7 @@ func createImage(config *Config, body io.ReadCloser, fileHeader *multipart.FileH
return image, nil
}
func pageUpload(ctx PageContext) http.Handler {
func pageUpload(ctx PageContext) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
user := parseUser(r)
......
package repo
import (
"database/sql"
"git.kuschku.de/justjanne/imghost-frontend/model"
"time"
)
type AlbumRepository struct {
db *sql.DB
}
func NewAlbumRepository(db *sql.DB) AlbumRepository {
return AlbumRepository{
db: db,
}
}
func (repo AlbumRepository) List(user model.User) ([]model.Album, error) {
var albums []model.Album
result, err := repo.db.Query(`
SELECT
id,
owner,
coalesce(title, ''),
coalesce(description, ''),
coalesce(created_at, to_timestamp(0)),
coalesce(original_name, ''),
coalesce(type, '')
FROM images
WHERE owner = $1
ORDER BY created_at DESC
`, user.Id)
if err != nil {
return albums, err
}
for result.Next() {
var album model.Album
if err := result.Scan(
&album.Id, &album.Owner, &album.Title, &album.Description,
&album.CreatedAt,
); err != nil {
return albums, err
}
albums = append(albums, album)
}
return albums, nil
}
func (repo AlbumRepository) Get(albumId string) (model.Album, error) {
var album model.Album
result, err := repo.db.Query(`
SELECT
id,
owner,
coalesce(title, ''),
coalesce(description, ''),
coalesce(created_at, to_timestamp(0))
FROM albums
WHERE id = $1`,
albumId)
if err != nil {
return album, err
}
if result.Next() {
if err := result.Scan(
&album.Id, &album.Owner, &album.Title, &album.Description,
&album.CreatedAt,
); err != nil {
return album, err
}
}
return album, nil
}
func (repo AlbumRepository) Create(album model.Album) error {
if _, err := repo.db.Exec(`
INSERT INTO albums (id, owner, title, description, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5)`,
album.Id,
album.Owner,
album.Title,
album.Description,
time.Now().UTC(),
time.Now().UTC(),
); err != nil {
return err
}
return nil
}
func (repo AlbumRepository) Update(album model.Album) error {
if _, err := repo.db.Exec(
"UPDATE albums SET title = $1, description = $2, updated_at = $3 WHERE id = $4",
album.Title,
album.Description,
time.Now().UTC(),
album.Id,
); err != nil {
return err
}
return nil
}
func (repo AlbumRepository) Delete(album model.Album) error {
if _, err := repo.db.Exec(
"DELETE FROM albums WHERE id = $1",
album.Id,
); err != nil {
return err
}
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
}
package repo
import (
"database/sql"
"git.kuschku.de/justjanne/imghost-frontend/model"
"time"
)
type ImageRepository struct {
db *sql.DB
}
func NewImageRepository(db *sql.DB) ImageRepository {
return ImageRepository{
db: db,
}
}
func (repo ImageRepository) List(user model.User) ([]model.Image, error) {
var images []model.Image
result, err := repo.db.Query(`
SELECT
id,
owner,
coalesce(title, ''),
coalesce(description, ''),
coalesce(created_at, to_timestamp(0)),
coalesce(original_name, ''),
coalesce(type, '')
FROM images
WHERE owner = $1
ORDER BY created_at DESC
`, user.Id)
if err != nil {
return images, err
}
for result.Next() {
var image model.Image
if err := result.Scan(
&image.Id, &image.Owner, &image.Title, &image.Description,
&image.CreatedAt, &image.OriginalName, &image.MimeType,
); err != nil {
return images, err
}
images = append(images, image)
}
return images, nil
}
func (repo ImageRepository) Get(imageId string) (model.Image, error) {
var image model.Image
result, err := repo.db.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 {
return image, err
}
if result.Next() {
if err := result.Scan(
&image.Id, &image.Owner, &image.Title, &image.Description,
&image.CreatedAt, &image.OriginalName, &image.MimeType,
); err != nil {
return image, err
}
}
return image, nil
}
func (repo ImageRepository) Create(image model.Image) error {
if _, err := repo.db.Exec(`
INSERT INTO images (id, owner, title, description, created_at, updated_at, original_name, type)
VALUES ($1, $2, $3, $4, $5)`,
image.Id,
image.Owner,
image.Title,
image.Description,
time.Now().UTC(),
time.Now().UTC(),
image.OriginalName,
image.MimeType,
); err != nil {
return err
}
return nil
}
func (repo ImageRepository) Update(image model.Image) error {
if _, err := repo.db.Exec(
"UPDATE images SET title = $1, description = $2, updated_at = $3 WHERE id = $4",
image.Title,
image.Description,
time.Now().UTC(),
image.Id,
); err != nil {
return err
}
return nil
}
func (repo ImageRepository) Delete(image model.Image) error {
if _, err := repo.db.Exec(
"DELETE FROM images WHERE id = $1",
image.Id,
); err != nil {
return err
}
return nil
}
{{template "header" .}}
{{template "navigation" .User}}
{{template "content" .}}
{{template "footer" .}}
\ No newline at end of file
{{define "footer"}}
<script src="/assets/js/component/fake-input.js"></script>
<script src="/assets/js/component/copy.js"></script>
{{end}}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment