From cceb60839aabbe03ffb6b5d0f30394dd384e3949 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <janne@kuschku.de> Date: Sun, 25 Jul 2021 16:14:55 +0200 Subject: [PATCH] Rewrite in progress Signed-off-by: Janne Mareike Koschinski <janne@kuschku.de> --- assets/css/fonts.css | 1 + assets/css/style.css | 1 + assets/sass/_page_image_detail.sass | 63 +++++---- go.mod | 2 + go.sum | 6 + main.go | 103 +++++++++++++-- model/album.go | 22 ++++ model/album_image.go | 8 ++ model/image.go | 25 ++++ model/user.go | 17 +++ page_album_detail.go | 80 ----------- page_album_list.go | 10 -- page_image_detail.go | 9 +- page_image_list.go | 49 ------- page_index.go | 3 +- page_upload.go | 25 ++-- repo/album_repository.go | 198 ++++++++++++++++++++++++++++ repo/image_repository.go | 127 ++++++++++++++++++ templates/_base.html | 4 - templates/_footer.html | 4 - templates/_header.html | 25 ---- templates/_navigation.html | 20 --- templates/image_detail.html | 138 +++++++++++-------- templates/image_list.html | 19 --- templates/index.html | 2 - templates/upload.html | 26 ---- types.go | 36 +---- util.go | 98 +++++++------- 28 files changed, 693 insertions(+), 428 deletions(-) create mode 100644 assets/css/fonts.css create mode 100644 assets/css/style.css create mode 100644 model/album.go create mode 100644 model/album_image.go create mode 100644 model/image.go create mode 100644 model/user.go delete mode 100644 page_album_detail.go delete mode 100644 page_album_list.go delete mode 100644 page_image_list.go create mode 100644 repo/album_repository.go create mode 100644 repo/image_repository.go delete mode 100644 templates/_base.html delete mode 100644 templates/_footer.html delete mode 100644 templates/_header.html delete mode 100644 templates/_navigation.html delete mode 100644 templates/image_list.html delete mode 100644 templates/index.html delete mode 100644 templates/upload.html diff --git a/assets/css/fonts.css b/assets/css/fonts.css new file mode 100644 index 0000000..b2c0f8a --- /dev/null +++ b/assets/css/fonts.css @@ -0,0 +1 @@ +@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} diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..df56c40 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1 @@ +*{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} diff --git a/assets/sass/_page_image_detail.sass b/assets/sass/_page_image_detail.sass index e1bccc7..3a2f06f 100644 --- a/assets/sass/_page_image_detail.sass +++ b/assets/sass/_page_image_detail.sass @@ -14,45 +14,44 @@ 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 - display: flex - flex-direction: row - input - background: none - color: #fff - opacity: 0.4 - border: none - flex-shrink: 1 - display: block - flex-grow: 1 - width: 0 - text-overflow: ellipsis - 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 - &:hover, &:focus - background: #FFD54F - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) + .url div + display: flex + flex-direction: row + input + background: none + color: #fff + opacity: 0.4 + border: none + flex-shrink: 1 + display: block + flex-grow: 1 + width: 0 + text-overflow: ellipsis + 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 + &:hover, &:focus + background: #FFD54F + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) .actions background: #333333 border-radius: 4px @@ -136,4 +135,4 @@ display: none .fake-input[contenteditable]:empty:before opacity: 0.4 - content: attr(placeholder) \ No newline at end of file + content: attr(placeholder) diff --git a/go.mod b/go.mod index 302c4aa..9780623 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index cc0e436..be2231f 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/main.go b/main.go index 4976acf..f3d0649 100644 --- a/main.go +++ b/main.go @@ -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) } + + err = image.VerifyOwner(user) + if err != nil { panic(err) } + + 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 - http.Handle("/me/images/", pageImageList(pageContext)) - http.Handle("/assets/", http.StripPrefix("/assets/", pageContext.AssetServer)) - http.Handle("/", pageIndex(pageContext)) + user := parseUser(r) + imageId := mux.Vars(r)["imageId"] + image, err := imageRepo.Get(imageId) + if err != nil { panic(err) } - http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("OK")) + 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) } diff --git a/model/album.go b/model/album.go new file mode 100644 index 0000000..c385d91 --- /dev/null +++ b/model/album.go @@ -0,0 +1,22 @@ +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 +} diff --git a/model/album_image.go b/model/album_image.go new file mode 100644 index 0000000..3379dfe --- /dev/null +++ b/model/album_image.go @@ -0,0 +1,8 @@ +package model + +type AlbumImage struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Position int `json:"position"` +} diff --git a/model/image.go b/model/image.go new file mode 100644 index 0000000..695ad83 --- /dev/null +++ b/model/image.go @@ -0,0 +1,25 @@ +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 +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..84c1bfd --- /dev/null +++ b/model/user.go @@ -0,0 +1,17 @@ +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 +} diff --git a/page_album_detail.go b/page_album_detail.go deleted file mode 100644 index 440423f..0000000 --- a/page_album_detail.go +++ /dev/null @@ -1,80 +0,0 @@ -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") - }) -} diff --git a/page_album_list.go b/page_album_list.go deleted file mode 100644 index 62d66dd..0000000 --- a/page_album_list.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "net/http" -) - -func pageAlbumList(ctx PageContext) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - }) -} diff --git a/page_image_detail.go b/page_image_detail.go index 2cc76cf..8759da5 100644 --- a/page_image_detail.go +++ b/page_image_detail.go @@ -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) } diff --git a/page_image_list.go b/page_image_list.go deleted file mode 100644 index fb1950d..0000000 --- a/page_image_list.go +++ /dev/null @@ -1,49 +0,0 @@ -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) - } - }) -} diff --git a/page_index.go b/page_index.go index a2052fd..f1decb4 100644 --- a/page_index.go +++ b/page_index.go @@ -1,12 +1,13 @@ 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 { diff --git a/page_upload.go b/page_upload.go index 23154e6..7a5cf16 100644 --- a/page_upload.go +++ b/page_upload.go @@ -1,20 +1,21 @@ 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) diff --git a/repo/album_repository.go b/repo/album_repository.go new file mode 100644 index 0000000..4ced2fb --- /dev/null +++ b/repo/album_repository.go @@ -0,0 +1,198 @@ +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 +} + diff --git a/repo/image_repository.go b/repo/image_repository.go new file mode 100644 index 0000000..83b8821 --- /dev/null +++ b/repo/image_repository.go @@ -0,0 +1,127 @@ +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 +} diff --git a/templates/_base.html b/templates/_base.html deleted file mode 100644 index 9bab238..0000000 --- a/templates/_base.html +++ /dev/null @@ -1,4 +0,0 @@ -{{template "header" .}} -{{template "navigation" .User}} -{{template "content" .}} -{{template "footer" .}} \ No newline at end of file diff --git a/templates/_footer.html b/templates/_footer.html deleted file mode 100644 index 18fdea3..0000000 --- a/templates/_footer.html +++ /dev/null @@ -1,4 +0,0 @@ -{{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 diff --git a/templates/_header.html b/templates/_header.html deleted file mode 100644 index 90a099f..0000000 --- a/templates/_header.html +++ /dev/null @@ -1,25 +0,0 @@ -{{define "header"}} -<!DOCTYPE html> -<meta charset="utf-8"> -<title>{{template "title" .}}</title> - -<link rel="shortcut icon" href="/favicon.png"> -<link rel="shortcut icon" href="/favicon.svg"> - -<meta name="generator" content="Human v1.0"> - -<meta name="referrer" content="origin"> - -<meta http-equiv="X-UA-Compatible" content="IE=edge"> -<meta name="HandheldFriendly" content="True"> -<meta name="apple-mobile-web-app-capable" content="yes"> - -<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width"> - -<meta name="theme-color" content="#FFC107"> -<meta name="msapplication-navbutton-color" content="#FFC107"> -<meta name="apple-mobile-web-app-status-bar-style" content="#FFC107"> - -<link href="/assets/css/style.css" rel="stylesheet"> -<link href="/assets/css/fonts.css" rel="stylesheet"> -{{end}} \ No newline at end of file diff --git a/templates/_navigation.html b/templates/_navigation.html deleted file mode 100644 index bca2fc8..0000000 --- a/templates/_navigation.html +++ /dev/null @@ -1,20 +0,0 @@ -{{define "navigation"}} -<nav> - <ul> - <li class="title"><a href="/"><img src="/assets/images/logo.svg"></a></li> - {{if .HasRole "imghost:user" }} - <li><a href="/upload">Upload</a></li> - {{end}} - <li class="spacer"></li> - {{if .HasRole "imghost:user" }} - <li class="images"><a href="/me/images">My Images</a></li> - <li class="albums"><a href="/me/albums">My Albums</a></li> - <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> - {{else if .Id }} - <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> - {{else}} - <li><a href="/me/images">Login</a></li> - {{end}} - </ul> -</nav> -{{end}} \ No newline at end of file diff --git a/templates/image_detail.html b/templates/image_detail.html index abd530b..c8e455a 100644 --- a/templates/image_detail.html +++ b/templates/image_detail.html @@ -1,56 +1,82 @@ -{{define "title"}}{{.Image.Title}} | ik8r{{end}} -{{define "content"}} -<div class="page image detail"> - <div class="detail"> - {{if .IsMine}} - <h2 class="title fake-input" contenteditable="true" placeholder="Title">{{.Image.Title}}</h2> - {{else}} - <h2 class="title">{{.Image.Title}}</h2> - {{end}} - <a class="image" href="/{{.Image.Id}}.png"> - <img src="/{{.Image.Id}}.png"> - </a> - {{if .IsMine}} - <p class="description fake-input" contenteditable="true" placeholder="Description" - data-multiline>{{.Image.Description}}</p> - {{else}} - <div class="description">{{.Image.Description}}</div> - {{end}} - </div> - <div class="sidebar"> - {{if .IsMine}} - <div class="actions"> - <form name="delete" class="delete-form" method="post"> - <input type="hidden" name="action" value="delete"> - <input type="hidden" name="id" value="{{.Image.Id}}"> - <input type="submit" value="Delete"> - </form> - <form name="upload" class="update-form" method="post"> - <input type="hidden" name="action" value="update"> - <input type="hidden" name="id" value="{{.Image.Id}}"> - <input type="hidden" name="title" value="{{.Image.Title}}"> - <input type="hidden" name="description" value="{{.Image.Description}}"> - <input type="submit" id="save" value="Save"> - </form> - </div> - {{end}} - <div class="url"> - <p>Detail Page</p> - <div> - <input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}"> - <button class="copy" data-target="#url_full">Copy</button> - </div> - </div> - <div class="url"> - <p>Direct Link</p> - <div> - <input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png"> - <button class="copy" data-target="#url_direct">Copy</button> - </div> - </div> - </div> -</div> -{{if .IsMine}} -<script src="/assets/js/page_image_detail.js"></script> -{{end}} -{{end}} +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageDetailData*/ -}} +<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>Image</h1> + <p> + <label> + Id:<br> + <input type="text" disabled value="{{.Image.Id}}"> + </label> + </p> + <p> + <label> + Owner:<br> + <input type="text" disabled value="{{.Image.Owner}}"> + </label> + </p> + <p> + <label> + Title:<br> + <input type="text" name="title" value="{{.Image.Title}}"> + </label> + </p> + <p> + <label> + Description:<br> + <input type="text" name="description" value="{{.Image.Description}}"> + </label> + </p> + <p> + <label> + OriginalName:<br> + <input type="text" disabled value="{{.Image.OriginalName}}"> + </label> + </p> + <p> + <label> + MimeType:<br> + <input type="text" disabled value="{{.Image.MimeType}}"> + </label> + </p> + <p> + <label> + CreatedAt:<br> + <input type="text" disabled value="{{.Image.CreatedAt}}"> + </label> + </p> + <p> + <label> + UpdatedAt:<br> + <input type="text" disabled value="{{.Image.UpdatedAt}}"> + </label> + </p> + <p> + <input type="submit"> + </p> +</form> diff --git a/templates/image_list.html b/templates/image_list.html deleted file mode 100644 index 4cc941a..0000000 --- a/templates/image_list.html +++ /dev/null @@ -1,19 +0,0 @@ -{{define "title"}}My Images | ik8r{{end}} -{{define "content"}} -<div class="page image list"> -{{range .Images}} - <a class="image" href="/i/{{.Id}}"> - <div class="image-container"> - <img src="/{{.Id}}t.png"> - </div> - <div class="info"> - <p>{{.OriginalName}}</p> - <p> - <time>{{.CreatedAt.Format "2006-01-02 15:04"}}</time> - </p> - <p>{{.MimeType}}</p> - </div> - </a> -{{end}} -</div> -{{end}} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 3590730..0000000 --- a/templates/index.html +++ /dev/null @@ -1,2 +0,0 @@ -{{define "title"}}ik8r{{end}} -{{define "content"}}{{end}} \ No newline at end of file diff --git a/templates/upload.html b/templates/upload.html deleted file mode 100644 index 6508d46..0000000 --- a/templates/upload.html +++ /dev/null @@ -1,26 +0,0 @@ -{{define "title"}}Upload | ik8r{{end}} -{{define "content"}} -<div class="page upload"> - <div class="container centered"> - <form class="upload" action="/upload/" method="POST" enctype="multipart/form-data"> - <label> - <span class="text">Select Files</span> - <input type="file" name="file" accept=".jpg,.jpeg,.png,.gif,.apng,.tiff,.tif,.bmp,.webp,.mp4,.mov" multiple/> - </label> - </form> - </div> - <div class="uploading-images"> - <div class="images"></div> - <div class="sidebar"> - <div class="url"> - <p>Album</p> - <div> - <input id="url_full" type="text" value="https://i.k8r.eu/a/ERROR"> - <button class="copy" data-target="#url_full">Copy</button> - </div> - </div> - </div> - </div> -</div> -<script src="/assets/js/page_upload.js"></script> -{{end}} \ No newline at end of file diff --git a/types.go b/types.go index 2ea713f..5cea91b 100644 --- a/types.go +++ b/types.go @@ -2,18 +2,10 @@ package main import ( "encoding/json" + "github.com/justjanne/imgconv" "os" - "time" ) -type Image struct { - Id string `json:"id"` - Title string - Description string - CreatedAt time.Time - OriginalName string - MimeType string `json:"mime_type"` -} type Result struct { Id string `json:"id"` @@ -21,25 +13,9 @@ type Result struct { Errors []string `json:"errors"` } -type Size struct { - Width uint `json:"width"` - Height uint `json:"height"` - Format string `json:"format"` -} - -const ( - sizeFormatCover = "cover" - sizeFormatContain = "contain" -) - -type Quality struct { - CompressionQuality uint `json:"compression_quality"` - SamplingFactors []float64 `json:"sampling_factors"` -} - type SizeDefinition struct { - Size Size `json:"size"` - Suffix string `json:"suffix"` + Size imgconv.Size `json:"size"` + Suffix string `json:"suffix"` } type RedisConfig struct { @@ -54,7 +30,7 @@ type DatabaseConfig struct { type Config struct { Sizes []SizeDefinition - Quality Quality + Quality imgconv.Quality SourceFolder string TargetFolder string Redis RedisConfig @@ -66,8 +42,8 @@ type Config struct { func NewConfigFromEnv() Config { config := Config{} - json.Unmarshal([]byte(os.Getenv("IK8R_SIZES")), &config.Sizes) - json.Unmarshal([]byte(os.Getenv("IK8R_QUALITY")), &config.Quality) + _ = json.Unmarshal([]byte(os.Getenv("IK8R_SIZES")), &config.Sizes) + _ = json.Unmarshal([]byte(os.Getenv("IK8R_QUALITY")), &config.Quality) config.SourceFolder = os.Getenv("IK8R_SOURCE_FOLDER") config.TargetFolder = os.Getenv("IK8R_TARGET_FOLDER") config.Redis.Address = os.Getenv("IK8R_REDIS_ADDRESS") diff --git a/util.go b/util.go index 0a69e89..d7de6d3 100644 --- a/util.go +++ b/util.go @@ -1,31 +1,16 @@ package main import ( - "fmt" - "net/http" - "html/template" - "time" "database/sql" - "github.com/go-redis/redis" - "strings" "encoding/json" + "fmt" + "git.kuschku.de/justjanne/imghost-frontend/model" + "github.com/go-redis/redis" + "html/template" + "net/http" ) -type UserInfo struct { - Id string - Name string - Email string - Roles []string -} -func (info UserInfo) HasRole(role string) bool { - for _, r := range info.Roles { - if r == role { - return true - } - } - return false -} type PageContext struct { Config *Config @@ -35,32 +20,26 @@ type PageContext struct { AssetServer http.Handler } -type AlbumImage struct { - Id string - Title string - Description string - Position int -} - -type Album struct { - Id string - Title string - Description string - CreatedAt time.Time - Images []AlbumImage -} - -func parseUser(r *http.Request) UserInfo { - return UserInfo{ - r.Header.Get("X-Auth-Subject"), - r.Header.Get("X-Auth-Username"), - r.Header.Get("X-Auth-Email"), - strings.Split(r.Header.Get("X-Auth-Roles"), ","), +func parseUser(r *http.Request) model.User { + return model.User{ + "ad45284c-be4d-4546-8171-41cf126ac091", + "justJanne", + "janne@kuschku.de", + []string{"imghost:user", "imghost:admin"}, } + + /* + return UserInfo{ + r.Header.Get("X-Auth-Subject"), + r.Header.Get("X-Auth-Username"), + r.Header.Get("X-Auth-Email"), + strings.Split(r.Header.Get("X-Auth-Roles"), ","), + } + */ } func returnJson(w http.ResponseWriter, data interface{}) error { - marshalled, err := json.Marshal(data) + marshalled, err := json.MarshalIndent(data, "", " ") if err != nil { return err } @@ -84,16 +63,17 @@ func returnError(w http.ResponseWriter, code int, message string) error { func formatTemplate(w http.ResponseWriter, templateName string, data interface{}) error { pageTemplate, err := template.ParseFiles( - "templates/_base.html", + /*"templates/_base.html", "templates/_header.html", "templates/_navigation.html", - "templates/_footer.html", + "templates/_footer.html",*/ fmt.Sprintf("templates/%s", templateName), ) if err != nil { return err } + w.Header().Set("Content-Type", "text/html") err = pageTemplate.Execute(w, data) if err != nil { return err @@ -101,3 +81,33 @@ func formatTemplate(w http.ResponseWriter, templateName string, data interface{} return nil } + +func (ctx *PageContext) getImageList(user model.User) ([]model.Image, error) { + 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 { + return nil, err + } + + var images []model.Image + for result.Next() { + var info model.Image + + if err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil { + return nil, err + } + images = append(images, info) + } + + return images, nil +} -- GitLab