Compare commits

...

18 Commits

Author SHA1 Message Date
5dc5590da9 Merge branch 'devel' 2024-10-30 02:24:50 +01:00
81c046c1b0 Add summary checks back 2024-10-30 02:22:19 +01:00
364112a0a4 Merge branch 'devel' 2024-10-30 02:14:16 +01:00
1e9fdd2ab9 Update version number 2024-10-30 02:13:40 +01:00
a38523e933 Merge branch 'devel' 2024-10-30 02:13:03 +01:00
1fbc0ddcf6 Make banner link and summary optional 2024-10-30 02:12:53 +01:00
0dd2101a08 Cleanup 2024-10-29 17:00:51 +01:00
e95871ee70 Fix bug that allowed empty banner images 2024-10-29 16:43:42 +01:00
083718711d Use empty alt tag for banner image 2024-10-29 16:37:41 +01:00
20a12c6299 Implement banner images in a more standard way 2024-10-29 16:36:40 +01:00
200672dae2 Merge branch 'devel' 2024-10-28 18:28:31 +01:00
be829e662b Change version number 2024-10-28 18:28:22 +01:00
3d3aad88c8 Merge branch 'devel' 2024-10-28 18:27:33 +01:00
0036b42d82 Update htmx version 2024-10-28 18:27:24 +01:00
e4e43d1a83 Merge branch 'devel' 2024-10-28 18:02:56 +01:00
59deb69e2f Lift size constraints from image and pdf uploads 2024-10-28 17:59:53 +01:00
3aef27585a Rename AtomFeed to AtomFile everywhere else 2024-10-28 17:57:51 +01:00
3aa8796537 Rename AtomFeed to AtomFile in config 2024-10-27 15:50:45 +01:00
12 changed files with 68 additions and 110 deletions

View File

@ -8,8 +8,6 @@ import (
"git.streifling.com/jason/atom"
)
type Feed struct{ *atom.Feed }
func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
feed := atom.NewFeed(c.Title)
feed.ID = atom.NewID("urn:feed:1")
@ -32,12 +30,25 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
}
entry := atom.NewEntry(articleTitle)
entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID))
entry.Content = atom.NewContent(atom.OutOfLine, "text/hmtl", article.ContentLink)
entry.Published = atom.NewDate(article.Created)
if article.AutoGenerated {
entry.Content = atom.NewContent(atom.InlineText, "text", "")
} else {
entry.Content = atom.NewContent(atom.OutOfLine, "text/hmtl", article.ContentLink)
articleSummary, err := ConvertToPlain(article.Summary)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for Atom feed: %v", err)
}
entry.Summary = atom.NewText("text", articleSummary)
}
if len(article.BannerLink) > 0 {
linkID := entry.AddLink(atom.NewLink(article.BannerLink))
entry.Links[linkID].Rel = "enclosure"
entry.Links[linkID].Type = "image/webp"
}
user, err := db.GetUser(c, article.AuthorID)
if err != nil {
@ -45,15 +56,6 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
}
entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName))
articleSummary, err := ConvertToPlain(article.Summary)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for Atom feed: %v", err)
}
if article.AutoGenerated {
articleSummary = "auto generated"
}
entry.Summary = atom.NewText("text", articleSummary)
tags, err := db.GetArticleTags(article.ID)
if err != nil {
return nil, fmt.Errorf("error getting tags for articles for Atom feed: %v", err)

View File

@ -18,7 +18,7 @@ type Config struct {
DBName string
Description string
Domain string
AtomFeed string
AtomFile string
FirebaseKey string
GOBKeyFile string
Link string
@ -39,7 +39,7 @@ func newConfig() *Config {
return &Config{
AESKeyFile: "/var/www/cpolis/aes.key",
ArticleDir: "/var/www/cpolis/articles",
AtomFeed: "/var/www/cpolis/cpolis.atom",
AtomFile: "/var/www/cpolis/cpolis.atom",
ConfigFile: "/etc/cpolis/config.toml",
DBName: "cpolis",
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
@ -52,7 +52,7 @@ func newConfig() *Config {
PDFDir: "/var/www/cpolis/pdfs",
PicsDir: "/var/www/cpolis/pics",
Port: ":8080",
Version: "v0.13.1",
Version: "v0.13.3",
WebDir: "/var/www/cpolis/web",
}
}
@ -110,7 +110,7 @@ func (c *Config) handleCliArgs() error {
flag.StringVar(&c.AESKeyFile, "aes", c.AESKeyFile, "aes key file")
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file")
flag.StringVar(&c.AtomFile, "feed", c.AtomFile, "atom feed file")
flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
flag.StringVar(&c.Description, "desc", c.Description, "channel description")
@ -187,10 +187,10 @@ func (c *Config) setupConfig(cliConfig *Config) error {
c.Domain = "https://" + c.Domain
}
if cliConfig.AtomFeed != defaultConfig.AtomFeed {
c.AtomFeed = cliConfig.AtomFeed
if cliConfig.AtomFile != defaultConfig.AtomFile {
c.AtomFile = cliConfig.AtomFile
}
c.AtomFeed, err = mkFile(c.AtomFeed, 0644, 0744)
c.AtomFile, err = mkFile(c.AtomFile, 0644, 0744)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}

View File

@ -1,7 +1,6 @@
package backend
import (
"encoding/base64"
"fmt"
"image"
"io"
@ -43,20 +42,3 @@ func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, err
return filename, nil
}
func ServeBase64Image(c *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
}

View File

@ -14,7 +14,7 @@ func ServeAtomFeed(c *b.Config) http.HandlerFunc {
return
}
absFilepath, err := filepath.Abs(c.AtomFeed)
absFilepath, err := filepath.Abs(c.AtomFile)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -80,7 +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"),
BannerLink: c.Domain + "/image/serve/" + r.PostFormValue("article-banner-url"),
Summary: r.PostFormValue("article-summary"),
Published: false,
Rejected: false,
@ -93,10 +93,6 @@ 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
@ -181,6 +177,11 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return
}
bannerLink := r.PostFormValue("article-banner-url")
if len(bannerLink) != 0 {
bannerLink = c.Domain + "/image/serve/" + bannerLink
}
summary := r.PostFormValue("article-summary")
if len(summary) == 0 {
http.Error(w, "Bitte die Beschreibung eingeben.", http.StatusBadRequest)
@ -202,6 +203,7 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
if err = db.UpdateAttributes(
&b.Attribute{Table: "articles", ID: id, AttName: "title", Value: title},
&b.Attribute{Table: "articles", ID: id, AttName: "banner_link", Value: bannerLink},
&b.Attribute{Table: "articles", ID: id, AttName: "summary", Value: summary},
&b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false},
&b.Attribute{Table: "articles", ID: id, AttName: "is_in_issue", Value: r.PostFormValue("issue") == "on"},
@ -347,13 +349,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
}
imgURL := strings.Split(data.Article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.BannerImage = imgURL[len(imgURL)-1]
articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md")
content, err := os.ReadFile(articleAbsName)
@ -469,7 +465,7 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = b.SaveAtomFeed(c.AtomFeed, feed); err != nil {
if err = b.SaveAtomFeed(c.AtomFile, feed); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -624,13 +620,7 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto
}
imgURL := strings.Split(article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.BannerImage = imgURL[len(imgURL)-1]
data.Article.Summary, err = b.ConvertToPlain(article.Summary)
if err != nil {
@ -704,7 +694,7 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = b.SaveAtomFeed(c.AtomFeed, feed); err != nil {
if err = b.SaveAtomFeed(c.AtomFile, feed); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -807,13 +797,7 @@ func EditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
}
imgURL := strings.Split(data.Article.BannerLink, "/")
imgFileName := imgURL[len(imgURL)-1]
data.BannerImage, err = b.ServeBase64Image(c, imgFileName)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.BannerImage = imgURL[len(imgURL)-1]
content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md"))
if err != nil {

View File

@ -5,6 +5,7 @@ import (
"html/template"
"log"
"net/http"
"path/filepath"
b "streifling.com/jason/cpolis/cmd/backend"
)
@ -17,12 +18,6 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
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)
@ -75,20 +70,8 @@ func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate
return
}
base64Img, err := b.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
data := new(struct{ BannerImage string })
data.BannerImage = filename
tmpl, err := template.ParseFiles(c.WebDir + "/templates/" + htmlFile)
if err = template.Must(tmpl, err).ExecuteTemplate(w, htmlTemplate, data); err != nil {
@ -98,3 +81,22 @@ func UploadBanner(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate
}
}
}
func ServeImage(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
}
absFilepath, err := filepath.Abs(c.PicsDir)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeFile(w, r, absFilepath+"/"+r.PathValue("img"))
}
}

View File

@ -41,10 +41,6 @@ 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 {
@ -97,7 +93,7 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = b.SaveAtomFeed(c.AtomFeed, feed); err != nil {
if err = b.SaveAtomFeed(c.AtomFile, feed); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -18,12 +18,6 @@ func UploadPDF(c *b.Config, s *b.CookieStore) http.HandlerFunc {
return
}
if err := r.ParseMultipartForm(10 << 20); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, _, err := r.FormFile("pdf-upload")
if err != nil {
log.Println(err)

View File

@ -68,6 +68,7 @@ func main() {
mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store))
mux.HandleFunc("GET /atom/serve", c.ServeAtomFeed(config))
mux.HandleFunc("GET /hub", f.ShowHub(config, db, store))
mux.HandleFunc("GET /image/{img}", f.ServeImage(config, store))
mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config))
mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, store))
mux.HandleFunc("GET /logout", f.Logout(config, store))

View File

@ -3,10 +3,7 @@
<form id="edit-area" hx-encoding="multipart/form-data">
<div class="flex flex-col gap-y-1">
<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>
{{template "article-banner-template" .}}
<div class="grid grid-cols-2 gap-4 items-center">
<div class="flex flex-col">
@ -17,7 +14,7 @@
<div class="grid grid-cols-1 items-center">
<label class="btn 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" />
hx-post="/article/upload-banner" hx-swap="outerHTML" hx-target="#article-banner-container" />
</div>
</div>
</div>
@ -92,8 +89,8 @@
{{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 id="article-banner-container">
<img src="/image/{{.BannerImage}}" alt="">
<input id="article-banner-url" name="article-banner-url" type="hidden" value="{{.BannerImage}}" />
</div>
{{end}}

View File

@ -42,7 +42,7 @@
<p>{{.Version}} - <strong>Alpha: Drastische Änderungen und Fehler vorbehalten.</strong></p>
</footer>
<script src="https://unpkg.com/htmx.org@2.0.2"></script>
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {

View File

@ -3,7 +3,7 @@
<div>
<div class="w-full" id="article-banner-container">
<img src="data:image/webp;base64,{{.BannerImage}}" alt="Banner Image">
<img src="/image/{{.BannerImage}}" alt="Banner Image">
</div>
<span>Titel</span>