forked from jason/cpolis
		
	Allow uploading a banner image
This commit is contained in:
		@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										166
									
								
								cmd/frontend/images.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								cmd/frontend/images.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -3,25 +3,19 @@
 | 
			
		||||
 | 
			
		||||
<form hx-encoding="multipart/form-data">
 | 
			
		||||
    <div class="flex flex-col gap-4">
 | 
			
		||||
        <div>
 | 
			
		||||
            <h3>Aktuelle Artikel</h3>
 | 
			
		||||
            <div class="flex flex-col gap-4">
 | 
			
		||||
                {{range .}}
 | 
			
		||||
                <div class="border border-slate-200 dark:border-slate-800 px-2 py-1 rounded-md">
 | 
			
		||||
                    <h1 class="font-bold text-2xl">{{.Title}}</h1>
 | 
			
		||||
                    <p>{{.Description}}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{end}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-full" id="issue-banner-container"></div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
            <h3>Titelseite</h3>
 | 
			
		||||
            <div class="grid grid-cols-2 gap-4 items-center">
 | 
			
		||||
                <input class="h-full" name="issue-title" placeholder="Titel" required type="text" />
 | 
			
		||||
                <label class="btn text-center" for="image-upload">Titelbild</label>
 | 
			
		||||
                <input class="hidden" id="image-upload" name="issue-image" type="file" required
 | 
			
		||||
                    hx-post="/issue/upload-image" />
 | 
			
		||||
        <h3>Titelseite</h3>
 | 
			
		||||
        <div class="grid grid-cols-2 gap-4 items-center">
 | 
			
		||||
            <div class="flex flex-col">
 | 
			
		||||
                <label for="issue-title">Titel</label>
 | 
			
		||||
                <input name="issue-title" required type="text" />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="grid grid-cols-1 items-center">
 | 
			
		||||
                <label class="btn cursor-pointer text-center" for="issue-banner-upload">Titelbild</label>
 | 
			
		||||
                <input class="hidden" id="issue-banner-upload" name="issue-banner" type="file" required
 | 
			
		||||
                    hx-post="/issue/upload-banner" hx-target="#issue-banner-container" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +26,18 @@
 | 
			
		||||
                <input id="issue-content" name="issue-content" type="hidden" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
            <h3>Aktuelle Artikel</h3>
 | 
			
		||||
            <div class="flex flex-col gap-4">
 | 
			
		||||
                {{range .}}
 | 
			
		||||
                <div class="border border-slate-200 dark:border-slate-800 px-2 py-1 rounded-md">
 | 
			
		||||
                    <h1 class="font-bold text-2xl">{{.Title}}</h1>
 | 
			
		||||
                    <p>{{.Summary}}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{end}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
@@ -71,3 +77,10 @@
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
{{define "issue-banner-template"}}
 | 
			
		||||
<div class="w-full" id="issue-banner-container">
 | 
			
		||||
    <img src="data:image/webp;base64,{{.BannerImage}}" alt="Banner Image">
 | 
			
		||||
    <input id="issue-banner-url" name="issue-banner-url" type="hidden" value="{{.URL}}" />
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
@@ -1,15 +1,24 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Editor</h2>
 | 
			
		||||
 | 
			
		||||
<form id="edit-area">
 | 
			
		||||
<form id="edit-area" hx-encoding="multipart/form-data">
 | 
			
		||||
    <div class="flex flex-col gap-y-1">
 | 
			
		||||
        <label for="article-title">Titel</label>
 | 
			
		||||
        <div class="w-full" id="article-banner-container">
 | 
			
		||||
            <img src="data:image/webp;base64,{{.BannerImage}}" alt="Banner Image">
 | 
			
		||||
            <input id="article-banner-url" name="article-banner-url" type="hidden" value="{{.Article.BannerLink}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="grid grid-cols-2 gap-4 items-center">
 | 
			
		||||
            <input name="article-title" type="text" value="{{.Article.Title}}" />
 | 
			
		||||
            <label class="btn text-center" for="image-upload">Titelbild</label>
 | 
			
		||||
            <input class="hidden" id="image-upload" name="issue-image" type="file" required
 | 
			
		||||
                hx-post="/issue/upload-image" />
 | 
			
		||||
            <!-- TODO: Route einfügen -->
 | 
			
		||||
            <div class="flex flex-col">
 | 
			
		||||
                <label for="article-title">Titel</label>
 | 
			
		||||
                <input name="article-title" type="text" value="{{.Article.Title}}" />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="grid grid-cols-1 items-center">
 | 
			
		||||
                <label class="btn cursor-pointer text-center" for="article-banner">Titelbild</label>
 | 
			
		||||
                <input class="hidden" id="article-banner" name="article-banner" type="file" required
 | 
			
		||||
                    hx-post="/article/upload-banner" hx-target="#article-banner-container" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@@ -81,3 +90,10 @@
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
{{define "article-banner-template"}}
 | 
			
		||||
<div class="w-full" id="article-banner-container">
 | 
			
		||||
    <img src="data:image/webp;base64,{{.BannerImage}}" alt="Banner Image">
 | 
			
		||||
    <input id="article-banner-url" name="article-banner-url" type="hidden" value="{{.URL}}" />
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
 | 
			
		||||
            <button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button>
 | 
			
		||||
            <form class="flex" hx-encoding="multipart/form-data">
 | 
			
		||||
                <label class="btn text-center" for="pdf-upload">PDF hochladen</label>
 | 
			
		||||
                <label class="btn cursor-pointer text-center" for="pdf-upload">PDF hochladen</label>
 | 
			
		||||
                <input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file"
 | 
			
		||||
                    hx-post="/pdf/upload" />
 | 
			
		||||
            </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,10 @@
 | 
			
		||||
<h2>{{.ActionTitle}}</h2>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
    <div class="w-full" id="article-banner-container">
 | 
			
		||||
        <img src="data:image/webp;base64,{{.BannerImage}}" alt="Banner Image">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <span>Titel</span>
 | 
			
		||||
    <div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
 | 
			
		||||
        {{.Article.Title}}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user