From 343022273c8c1ca8b5ca013dce0a5facbf492314 Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Sun, 27 Oct 2024 07:21:36 +0100 Subject: [PATCH] Allow uploading a banner image --- .air.toml | 1 + cmd/backend/images.go | 2 +- cmd/frontend/articles.go | 72 ++++---- cmd/frontend/images.go | 166 ++++++++++++++++++ cmd/frontend/issues.go | 80 +-------- cmd/main.go | 7 +- create_db.sql | 1 + ...rrent-articles.html => current-issue.html} | 49 ++++-- web/templates/editor.html | 30 +++- web/templates/hub.html | 2 +- web/templates/review-article.html | 4 + 11 files changed, 274 insertions(+), 140 deletions(-) create mode 100644 cmd/frontend/images.go rename web/templates/{current-articles.html => current-issue.html} (66%) diff --git a/.air.toml b/.air.toml index 22c379d..566c4f9 100644 --- a/.air.toml +++ b/.air.toml @@ -19,6 +19,7 @@ args_bin = [ "-rss tmp/orientexpress_alle.rss", "-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'", "-web web", + "-width 1024", ] bin = "./tmp/main" cmd = "go build -o ./tmp/main ./cmd/main.go" diff --git a/cmd/backend/images.go b/cmd/backend/images.go index a809b0f..3ee087a 100644 --- a/cmd/backend/images.go +++ b/cmd/backend/images.go @@ -36,7 +36,7 @@ func SaveImage(c *Config, src io.Reader) (string, error) { } defer file.Close() - if err = webp.Encode(file, img, &webp.Options{Lossless: true}); err != nil { + if err = webp.Encode(file, img, &webp.Options{Quality: 80}); err != nil { return "", fmt.Errorf("error encoding image as webp: %v", err) } diff --git a/cmd/frontend/articles.go b/cmd/frontend/articles.go index e116c59..5cdaa89 100644 --- a/cmd/frontend/articles.go +++ b/cmd/frontend/articles.go @@ -1,13 +1,13 @@ package frontend import ( - "encoding/json" "fmt" "html/template" "log" "net/http" "os" "strconv" + "strings" "time" b "streifling.com/jason/cpolis/cmd/backend" @@ -24,6 +24,7 @@ type EditorHTMLData struct { Action string ActionTitle string ActionButton string + BannerImage string HTMLContent template.HTML Article *b.Article Tags []*b.Tag @@ -79,6 +80,7 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { article := &b.Article{ Title: r.PostFormValue("article-title"), + BannerLink: r.PostFormValue("article-banner-url"), Summary: r.PostFormValue("article-summary"), Published: false, Rejected: false, @@ -91,6 +93,10 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { http.Error(w, "Bitte den Titel eingeben.", http.StatusBadRequest) return } + if len(article.BannerLink) == 0 { + http.Error(w, "Bitte ein Titelbild einfügen.", http.StatusBadRequest) + return + } if len(article.Summary) == 0 { http.Error(w, "Bitte die Beschreibung eingeben.", http.StatusBadRequest) return @@ -334,6 +340,15 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler return } + imgURL := strings.Split(data.Article.BannerLink, "/") + imgFileName := imgURL[len(imgURL)-1] + data.BannerImage, err = serveBase64Image(c, imgFileName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md") content, err := os.ReadFile(articleAbsName) if err != nil { @@ -496,7 +511,7 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } } -func ShowCurrentArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { +func ShowCurrentIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := getSession(w, r, c, s); err != nil { log.Println(err) @@ -511,7 +526,7 @@ func ShowCurrentArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFu return } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/current-articles.html") + tmpl, err := template.ParseFiles(c.WebDir + "/templates/current-issue.html") if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", articles); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -520,39 +535,6 @@ func ShowCurrentArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFu } } -func UploadArticleImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if _, err := getSession(w, r, c, s); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - file, _, err := r.FormFile("article-image") - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - defer file.Close() - - filename, err := b.SaveImage(c, file) - if err != nil { - if err == b.ErrUnsupportedFormat { - http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) - return - } - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - url := c.Domain + "/image/serve/" + filename - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(url) - } -} - func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := getSession(w, r, c, s); err != nil { @@ -629,6 +611,15 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto return } + imgURL := strings.Split(article.BannerLink, "/") + imgFileName := imgURL[len(imgURL)-1] + data.BannerImage, err = serveBase64Image(c, imgFileName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Article.Summary, err = b.ConvertToPlain(article.Summary) if err != nil { log.Println(err) @@ -797,6 +788,15 @@ func EditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + imgURL := strings.Split(data.Article.BannerLink, "/") + imgFileName := imgURL[len(imgURL)-1] + data.BannerImage, err = serveBase64Image(c, imgFileName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md")) if err != nil { log.Println(err) diff --git a/cmd/frontend/images.go b/cmd/frontend/images.go new file mode 100644 index 0000000..478eb6f --- /dev/null +++ b/cmd/frontend/images.go @@ -0,0 +1,166 @@ +package frontend + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "html/template" + "io" + "log" + "net/http" + "os" + + b "streifling.com/jason/cpolis/cmd/backend" +) + +func serveBase64Image(c *b.Config, filename string) (string, error) { + file := c.PicsDir + "/" + filename + + img, err := os.Open(file) + if err != nil { + return "", fmt.Errorf("error opening file %v: %v", file, err) + } + defer img.Close() + + imgBytes, err := io.ReadAll(img) + if err != nil { + return "", fmt.Errorf("error turning %v into bytes: %v", file, err) + } + + return base64.StdEncoding.EncodeToString(imgBytes), nil +} + +func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := r.ParseMultipartForm(10 << 20); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, _, err := r.FormFile("article-image") + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + filename, err := b.SaveImage(c, file) + if err != nil { + if err == b.ErrUnsupportedFormat { + http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) + return + } + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + url := c.Domain + "/image/serve/" + filename + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(url) + } +} + +func UploadIssueImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := r.ParseMultipartForm(10 << 20); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, _, err := r.FormFile("issue-image") + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() + + filename, err := b.SaveImage(c, file) + if err != nil { + if err == b.ErrUnsupportedFormat { + http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) + return + } + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + session.Values["issue-image"] = filename + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } +} + +func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + file, _, err := r.FormFile(fileKey) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + filename, err := b.SaveImage(c, file) + if err != nil { + if err == b.ErrUnsupportedFormat { + http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) + return + } + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + base64Img, err := serveBase64Image(c, filename) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data := new(struct { + BannerImage string + URL string + }) + + data.BannerImage = base64Img + data.URL = c.Domain + "/image/serve/" + filename + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/" + htmlFile) + if err = template.Must(tmpl, err).ExecuteTemplate(w, htmlTemplate, data); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} diff --git a/cmd/frontend/issues.go b/cmd/frontend/issues.go index 6f651e8..ec0c6d6 100644 --- a/cmd/frontend/issues.go +++ b/cmd/frontend/issues.go @@ -4,10 +4,8 @@ import ( "fmt" "html/template" "log" - "mime" "net/http" "os" - "path/filepath" "time" b "streifling.com/jason/cpolis/cmd/backend" @@ -29,35 +27,9 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun return } - if session.Values["issue-image"] == nil { - http.Error(w, "Bitte ein Bild einfügen.", http.StatusBadRequest) - return - } - - imgFileName := session.Values["issue-image"].(string) - fmt.Println(imgFileName) - imgAbsName := c.PicsDir + "/" + imgFileName - - imgFile, err := os.Open(imgAbsName) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer imgFile.Close() - - imgInfo, err := imgFile.Stat() - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - article := &b.Article{ - EncURL: c.Domain + "/image/serve/" + imgFileName, - EncLength: int(imgInfo.Size()), - EncType: mime.TypeByExtension(filepath.Ext(imgAbsName)), Title: r.PostFormValue("issue-title"), + BannerLink: r.PostFormValue("issue-banner-url"), Published: true, Rejected: false, Created: time.Now(), @@ -69,6 +41,11 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun http.Error(w, "Bitte den Titel eingeben.", http.StatusBadRequest) return } + if len(article.BannerLink) == 0 { + http.Error(w, "Bitte ein Titelbild einfügen.", http.StatusBadRequest) + return + } + article.ID, err = db.AddArticle(article) if err != nil { log.Println(err) @@ -142,48 +119,3 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun } } } - -func UploadIssueImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - session, err := getSession(w, r, c, s) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := r.ParseMultipartForm(10 << 20); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - file, _, err := r.FormFile("issue-image") - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer file.Close() - - filename, err := b.SaveImage(c, file) - if err != nil { - if err == b.ErrUnsupportedFormat { - http.Error(w, "Das Dateiformat wird nicht unterstützt.", http.StatusBadRequest) - return - } - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - session.Values["issue-image"] = filename - if err = session.Save(r, w); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - } -} diff --git a/cmd/main.go b/cmd/main.go index 63eb879..8c4220f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -68,7 +68,7 @@ func main() { mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config, store)) - mux.HandleFunc("GET /issue/this", f.ShowCurrentArticles(config, db, store)) + mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, store)) mux.HandleFunc("GET /logout", f.Logout(config, store)) mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config)) @@ -83,9 +83,10 @@ func main() { mux.HandleFunc("POST /article/resubmit/{id}", f.ResubmitArticle(config, db, store)) mux.HandleFunc("POST /article/submit", f.SubmitArticle(config, db, store)) - mux.HandleFunc("POST /article/upload-image", f.UploadArticleImage(config, store)) + mux.HandleFunc("POST /article/upload-banner", f.UploadBanner(config, store, "article-banner", "editor.html", "article-banner-template")) + mux.HandleFunc("POST /article/upload-image", f.UploadImage(config, store)) mux.HandleFunc("POST /issue/publish", f.PublishLatestIssue(config, db, store)) - mux.HandleFunc("POST /issue/upload-image", f.UploadIssueImage(config, store)) + mux.HandleFunc("POST /issue/upload-banner", f.UploadBanner(config, store, "issue-banner", "current-issue.html", "issue-banner-template")) mux.HandleFunc("POST /login", f.Login(config, db, store)) mux.HandleFunc("POST /pdf/upload", f.UploadPDF(config, store)) mux.HandleFunc("POST /tag/add", f.AddTag(config, db, store)) diff --git a/create_db.sql b/create_db.sql index 937c592..cbf4e4a 100644 --- a/create_db.sql +++ b/create_db.sql @@ -24,6 +24,7 @@ CREATE TABLE articles ( id INT AUTO_INCREMENT, title VARCHAR(255) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + banner_link VARCHAR(255), summary TEXT NOT NULL, content_link VARCHAR(255), enc_url VARCHAR(255), diff --git a/web/templates/current-articles.html b/web/templates/current-issue.html similarity index 66% rename from web/templates/current-articles.html rename to web/templates/current-issue.html index f9039a4..1f4f80c 100644 --- a/web/templates/current-articles.html +++ b/web/templates/current-issue.html @@ -3,25 +3,19 @@
-
-

Aktuelle Artikel

-
- {{range .}} -
-

{{.Title}}

-

{{.Description}}

-
- {{end}} -
-
+
-
-

Titelseite

-
- - - +

Titelseite

+
+
+ + +
+ +
+ +
@@ -32,6 +26,18 @@
+ +
+

Aktuelle Artikel

+
+ {{range .}} +
+

{{.Title}}

+

{{.Summary}}

+
+ {{end}} +
+
@@ -71,3 +77,10 @@ }); {{end}} + +{{define "issue-banner-template"}} +
+ Banner Image + +
+{{end}} diff --git a/web/templates/editor.html b/web/templates/editor.html index 932f749..b85dd41 100644 --- a/web/templates/editor.html +++ b/web/templates/editor.html @@ -1,15 +1,24 @@ {{define "page-content"}}

Editor

- +
- +
+ Banner Image + +
+
- - - - +
+ + +
+ +
+ + +
@@ -81,3 +90,10 @@ }); {{end}} + +{{define "article-banner-template"}} +
+ Banner Image + +
+{{end}} diff --git a/web/templates/hub.html b/web/templates/hub.html index eb8999d..0c49698 100644 --- a/web/templates/hub.html +++ b/web/templates/hub.html @@ -25,7 +25,7 @@
- + diff --git a/web/templates/review-article.html b/web/templates/review-article.html index d43ab69..ee57a5a 100644 --- a/web/templates/review-article.html +++ b/web/templates/review-article.html @@ -2,6 +2,10 @@

{{.ActionTitle}}

+
+ Banner Image +
+ Titel
{{.Article.Title}}