Compare commits

...

13 Commits

22 changed files with 723 additions and 529 deletions

View File

@ -12,7 +12,6 @@ args_bin = [
"-domain localhost", "-domain localhost",
"-feed tmp/cpolis.atom", "-feed tmp/cpolis.atom",
"-firebase tmp/firebase.json", "-firebase tmp/firebase.json",
"-gob tmp/cpolis.gob",
"-img-width 256", "-img-width 256",
"-link https://distrikt-ni-st.de", "-link https://distrikt-ni-st.de",
"-log tmp/cpolis.log", "-log tmp/cpolis.log",

View File

@ -7,6 +7,8 @@ import (
"log" "log"
"os" "os"
"time" "time"
"github.com/google/uuid"
) )
type Article struct { type Article struct {
@ -14,6 +16,7 @@ type Article struct {
Title string Title string
BannerLink string BannerLink string
Summary string Summary string
UUID uuid.UUID
ID int64 ID int64
CreatorID int64 CreatorID int64
IssueID int64 IssueID int64
@ -31,8 +34,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
selectQuery := "SELECT id FROM issues WHERE published = false" selectQuery := "SELECT id FROM issues WHERE published = false"
insertQuery := ` insertQuery := `
INSERT INTO articles INSERT INTO articles
(title, banner_link, summary, published, rejected, creator_id, issue_id, edited_id, clicks, is_in_issue, auto_generated) (title, banner_link, summary, published, rejected, creator_id, issue_id, edited_id, clicks, is_in_issue, auto_generated, uuid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
` `
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
@ -49,7 +52,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err) return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err)
} }
result, err := tx.Exec(insertQuery, a.Title, a.BannerLink, a.Summary, a.Published, a.Rejected, a.CreatorID, id, a.EditedID, 0, a.IsInIssue, a.AutoGenerated) result, err := tx.Exec(insertQuery, a.Title, a.BannerLink, a.Summary, a.Published, a.Rejected, a.CreatorID, id, a.EditedID, 0, a.IsInIssue, a.AutoGenerated, a.UUID.String())
if err != nil { if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
@ -83,7 +86,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
func (db *DB) GetArticle(id int64) (*Article, error) { func (db *DB) GetArticle(id int64) (*Article, error) {
query := ` query := `
SELECT title, created, banner_link, summary, published, creator_id, issue_id, edited_id, clicks, is_in_issue, auto_generated SELECT title, created, banner_link, summary, published, creator_id, issue_id, edited_id, clicks, is_in_issue, auto_generated, uuid
FROM articles FROM articles
WHERE id = ? WHERE id = ?
` `
@ -91,9 +94,10 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
article := new(Article) article := new(Article)
var created []byte var created []byte
var uuidString string
var err error var err error
if err := row.Scan(&article.Title, &created, &article.BannerLink, &article.Summary, &article.Published, &article.CreatorID, &article.IssueID, &article.EditedID, &article.Clicks, &article.IsInIssue, &article.AutoGenerated); err != nil { if err := row.Scan(&article.Title, &created, &article.BannerLink, &article.Summary, &article.Published, &article.CreatorID, &article.IssueID, &article.EditedID, &article.Clicks, &article.IsInIssue, &article.AutoGenerated, &uuidString); err != nil {
return nil, fmt.Errorf("error scanning article row: %v", err) return nil, fmt.Errorf("error scanning article row: %v", err)
} }
@ -103,12 +107,17 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
return nil, fmt.Errorf("error parsing created: %v", err) return nil, fmt.Errorf("error parsing created: %v", err)
} }
article.UUID, err = uuid.Parse(uuidString)
if err != nil {
return nil, fmt.Errorf("error parsing uuid: %v", err)
}
return article, nil return article, nil
} }
func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, error) { func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, error) {
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT id, title, created, banner_link, summary, creator_id, issue_id, clicks, published, rejected, is_in_issue, auto_generated SELECT id, title, created, banner_link, summary, creator_id, issue_id, clicks, published, rejected, is_in_issue, auto_generated, uuid
FROM articles FROM articles
WHERE %s = ? WHERE %s = ?
`, attribute) `, attribute)
@ -121,8 +130,9 @@ func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, erro
for rows.Next() { for rows.Next() {
article := new(Article) article := new(Article)
var created []byte var created []byte
var uuidString string
if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.CreatorID, &article.IssueID, &article.Clicks, &article.Published, &article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil { if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.CreatorID, &article.IssueID, &article.Clicks, &article.Published, &article.Rejected, &article.IsInIssue, &article.AutoGenerated, &uuidString); err != nil {
return nil, fmt.Errorf("error scanning article row: %v", err) return nil, fmt.Errorf("error scanning article row: %v", err)
} }
@ -131,6 +141,11 @@ func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, erro
return nil, fmt.Errorf("error parsing created: %v", err) return nil, fmt.Errorf("error parsing created: %v", err)
} }
article.UUID, err = uuid.Parse(uuidString)
if err != nil {
return nil, fmt.Errorf("error parsing uuid: %v", err)
}
articleList = append(articleList, article) articleList = append(articleList, article)
} }
@ -142,7 +157,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
issueQuery := "SELECT id FROM issues WHERE published = false" issueQuery := "SELECT id FROM issues WHERE published = false"
articlesQuery := ` articlesQuery := `
SELECT id, title, created, banner_link, summary, clicks, auto_generated SELECT id, title, created, banner_link, summary, clicks, auto_generated, uuid
FROM articles FROM articles
WHERE issue_id = ? AND published = true AND is_in_issue = true WHERE issue_id = ? AND published = true AND is_in_issue = true
` `
@ -174,8 +189,9 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
for rows.Next() { for rows.Next() {
article := new(Article) article := new(Article)
var created []byte var created []byte
var uuidString string
if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.Clicks, &article.AutoGenerated); err != nil { if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.Clicks, &article.AutoGenerated, &uuidString); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
} }
@ -190,6 +206,14 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
return nil, fmt.Errorf("error parsing created: %v", err) return nil, fmt.Errorf("error parsing created: %v", err)
} }
article.UUID, err = uuid.Parse(uuidString)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return nil, fmt.Errorf("error parsing uuid: %v", err)
}
articleList = append(articleList, article) articleList = append(articleList, article)
} }
@ -284,11 +308,11 @@ func (db *DB) DeleteArticle(id int64) error {
return nil return nil
} }
func WriteArticleToFile(c *Config, articleID int64, content []byte) error { func WriteArticleToFile(c *Config, articleUUID uuid.UUID, content []byte) error {
articleAbsName := fmt.Sprint(c.ArticleDir, "/", articleID, ".md") articleAbsName := fmt.Sprint(c.ArticleDir, "/", articleUUID, ".md")
if err := os.WriteFile(articleAbsName, content, 0644); err != nil { if err := os.WriteFile(articleAbsName, content, 0644); err != nil {
return fmt.Errorf("error writing article %v to file: %v", articleID, err) return fmt.Errorf("error writing article %v to file: %v", articleUUID, err)
} }
return nil return nil

View File

@ -33,7 +33,7 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
entry := atom.NewEntry(articleTitle) entry := atom.NewEntry(articleTitle)
entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID)) entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID))
entry.Published = atom.NewDate(article.Created) entry.Published = atom.NewDate(article.Created)
entry.Content = atom.NewContent(atom.OutOfLine, "text/hmtl", fmt.Sprint(c.Domain, "/article/serve/", article.ID)) entry.Content = atom.NewContent(atom.OutOfLine, "text/html", fmt.Sprint(c.Domain, "/article/serve/", article.UUID))
if article.AutoGenerated { if article.AutoGenerated {
entry.Summary = atom.NewText("text", "automatically generated") entry.Summary = atom.NewText("text", "automatically generated")

View File

@ -12,48 +12,48 @@ import (
) )
type Config struct { type Config struct {
AESKeyFile string AESKeyFile string
ArticleDir string ArticleDir string
ConfigFile string AtomFile string
DBName string ConfigFile string
Description string DBName string
Domain string Description string
AtomFile string Domain string
FirebaseKey string FirebaseKey string
GOBKeyFile string Link string
Link string LogFile string
LogFile string PDFDir string
PDFDir string PicsDir string
PicsDir string Port string
Port string Title string
Title string Version string
Version string WebDir string
WebDir string CookieExpiryHours int
MaxBannerHeight int MaxBannerHeight int
MaxBannerWidth int MaxBannerWidth int
MaxImgHeight int MaxImgHeight int
MaxImgWidth int MaxImgWidth int
} }
func newConfig() *Config { func newConfig() *Config {
return &Config{ return &Config{
AESKeyFile: "/var/www/cpolis/aes.key", AESKeyFile: "/var/www/cpolis/aes.key",
ArticleDir: "/var/www/cpolis/articles", ArticleDir: "/var/www/cpolis/articles",
AtomFile: "/var/www/cpolis/cpolis.atom", AtomFile: "/var/www/cpolis/cpolis.atom",
ConfigFile: "/etc/cpolis/config.toml", ConfigFile: "/etc/cpolis/config.toml",
DBName: "cpolis", CookieExpiryHours: 24 * 30,
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json", DBName: "cpolis",
GOBKeyFile: "/var/www/cpolis/gob.key", FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
LogFile: "/var/log/cpolis.log", LogFile: "/var/log/cpolis.log",
MaxBannerHeight: 1080, MaxBannerHeight: 1080,
MaxBannerWidth: 1920, MaxBannerWidth: 1920,
MaxImgHeight: 1080, MaxImgHeight: 1080,
MaxImgWidth: 1920, MaxImgWidth: 1920,
PDFDir: "/var/www/cpolis/pdfs", PDFDir: "/var/www/cpolis/pdfs",
PicsDir: "/var/www/cpolis/pics", PicsDir: "/var/www/cpolis/pics",
Port: ":8080", Port: ":8080",
Version: "v0.14.1", Version: "v0.15.0",
WebDir: "/var/www/cpolis/web", WebDir: "/var/www/cpolis/web",
} }
} }
@ -116,13 +116,13 @@ func (c *Config) handleCliArgs() error {
flag.StringVar(&c.Description, "desc", c.Description, "channel description") flag.StringVar(&c.Description, "desc", c.Description, "channel description")
flag.StringVar(&c.Domain, "domain", c.Domain, "domain name") flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file") flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file")
flag.StringVar(&c.GOBKeyFile, "gob", c.GOBKeyFile, "gob key file")
flag.StringVar(&c.Link, "link", c.Link, "channel Link") flag.StringVar(&c.Link, "link", c.Link, "channel Link")
flag.StringVar(&c.LogFile, "log", c.LogFile, "log file") flag.StringVar(&c.LogFile, "log", c.LogFile, "log file")
flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory") flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory")
flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory") flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory")
flag.StringVar(&c.Title, "title", c.Title, "channel title") flag.StringVar(&c.Title, "title", c.Title, "channel title")
flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory") flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory")
flag.IntVar(&c.CookieExpiryHours, "cookie-expiry-hours", c.CookieExpiryHours, "cookies expire after this amount of hours")
flag.IntVar(&c.MaxBannerHeight, "banner-height", c.MaxBannerHeight, "maximum banner height") flag.IntVar(&c.MaxBannerHeight, "banner-height", c.MaxBannerHeight, "maximum banner height")
flag.IntVar(&c.MaxBannerWidth, "banner-width", c.MaxBannerWidth, "maximum banner width") flag.IntVar(&c.MaxBannerWidth, "banner-width", c.MaxBannerWidth, "maximum banner width")
flag.IntVar(&c.MaxImgHeight, "img-height", c.MaxImgHeight, "maximum image height") flag.IntVar(&c.MaxImgHeight, "img-height", c.MaxImgHeight, "maximum image height")
@ -171,6 +171,18 @@ func (c *Config) setupConfig(cliConfig *Config) error {
return fmt.Errorf("error setting up directory: %v", err) return fmt.Errorf("error setting up directory: %v", err)
} }
if cliConfig.AtomFile != defaultConfig.AtomFile {
c.AtomFile = cliConfig.AtomFile
}
c.AtomFile, err = mkFile(c.AtomFile, 0644, 0744)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.CookieExpiryHours != defaultConfig.CookieExpiryHours {
c.CookieExpiryHours = cliConfig.CookieExpiryHours
}
if cliConfig.DBName != defaultConfig.DBName { if cliConfig.DBName != defaultConfig.DBName {
c.DBName = cliConfig.DBName c.DBName = cliConfig.DBName
} }
@ -182,19 +194,11 @@ func (c *Config) setupConfig(cliConfig *Config) error {
if cliConfig.Domain != defaultConfig.Domain { if cliConfig.Domain != defaultConfig.Domain {
c.Domain = cliConfig.Domain c.Domain = cliConfig.Domain
} }
domainStrings := strings.Split(c.Domain, "/") domainStrings := strings.Split(c.Domain, ":")
if domainStrings[0] != "http:" && domainStrings[0] != "https:" { if domainStrings[0] != "http" && domainStrings[0] != "https" {
c.Domain = "https://" + c.Domain c.Domain = "https://" + c.Domain
} }
if cliConfig.AtomFile != defaultConfig.AtomFile {
c.AtomFile = cliConfig.AtomFile
}
c.AtomFile, err = mkFile(c.AtomFile, 0644, 0744)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.FirebaseKey != defaultConfig.FirebaseKey { if cliConfig.FirebaseKey != defaultConfig.FirebaseKey {
c.FirebaseKey = cliConfig.FirebaseKey c.FirebaseKey = cliConfig.FirebaseKey
} }
@ -203,14 +207,6 @@ func (c *Config) setupConfig(cliConfig *Config) error {
return fmt.Errorf("error setting up file: %v", err) return fmt.Errorf("error setting up file: %v", err)
} }
if cliConfig.GOBKeyFile != defaultConfig.GOBKeyFile {
c.GOBKeyFile = cliConfig.GOBKeyFile
}
c.GOBKeyFile, err = mkFile(c.GOBKeyFile, 0600, 0700)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.Link != defaultConfig.Link { if cliConfig.Link != defaultConfig.Link {
c.Link = cliConfig.Link c.Link = cliConfig.Link
} }

59
cmd/backend/docx.go Normal file
View File

@ -0,0 +1,59 @@
package backend
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"github.com/google/uuid"
)
func ConvertToMarkdown(c *Config, filename string) ([]byte, error) {
var stderr bytes.Buffer
articleID := uuid.New()
articleFileName := fmt.Sprint("/tmp/", articleID, ".md")
tmpDir, err := os.MkdirTemp("/tmp", "cpolis_images")
if err != nil {
return nil, fmt.Errorf("error creating temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
cmd := exec.Command("pandoc", "-s", "-f", "docx", "-t", "commonmark_x", "-o", articleFileName, "--extract-media", tmpDir, filename) // TODO: Is writing to a file necessary?
cmd.Stderr = &stderr
if err = cmd.Run(); err != nil {
return nil, fmt.Errorf("error converting docx to markdown: %v: %v", err, stderr.String())
}
defer os.Remove(articleFileName)
articleContent, err := os.ReadFile(articleFileName)
if err != nil {
return nil, fmt.Errorf("error reading markdown file: %v", err)
}
imageNames, err := filepath.Glob(filepath.Join(tmpDir, "/media/*"))
if err != nil {
return nil, fmt.Errorf("error getting docx images from temporary directory: %v", err)
}
for _, name := range imageNames {
image, err := os.Open(name)
if err != nil {
return nil, fmt.Errorf("error opening image file %v: %v", name, err)
}
defer image.Close()
newImageName, err := SaveImage(image, c.MaxImgHeight, c.MaxImgWidth, c.PicsDir)
if err != nil {
return nil, fmt.Errorf("error saving image %v: %v", name, err)
}
articleContent = regexp.MustCompile(name).ReplaceAll(articleContent, []byte(c.PicsDir+"/"+newImageName))
}
return articleContent, nil
}

View File

@ -1,10 +1,16 @@
package backend package backend
import ( import (
"bufio"
"fmt" "fmt"
"image" "image"
"io" "io"
"io/fs"
"log"
"os" "os"
"path/filepath"
"strings"
"time"
"github.com/chai2010/webp" "github.com/chai2010/webp"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
@ -30,7 +36,7 @@ func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, err
} }
filename := fmt.Sprint(uuid.New(), ".webp") filename := fmt.Sprint(uuid.New(), ".webp")
file, err := os.Create(path + filename) file, err := os.Create(filepath.Join(path, filename))
if err != nil { if err != nil {
return "", fmt.Errorf("error creating new image file: %v", err) return "", fmt.Errorf("error creating new image file: %v", err)
} }
@ -42,3 +48,59 @@ func SaveImage(src io.Reader, maxHeight, maxWidth int, path string) (string, err
return filename, nil return filename, nil
} }
func CleanUpImages(c *Config) {
for {
if err := filepath.Walk(c.PicsDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
imageName := info.Name()
absImageName := path
if err = filepath.Walk(c.ArticleDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
mdFile, err := os.Open(path)
if err != nil {
return err
}
defer mdFile.Close()
scanner := bufio.NewScanner(mdFile)
imageWasFound := false
for scanner.Scan() {
if strings.Contains(scanner.Text(), imageName) {
imageWasFound = true
}
}
if !imageWasFound {
if err = os.Remove(absImageName); err != nil {
return err
}
}
return scanner.Err()
}
return nil
}); err != nil {
return err
}
}
return nil
}); err != nil {
log.Println(err)
}
time.Sleep(time.Hour)
}
}

View File

@ -1,63 +0,0 @@
package backend
import (
"crypto/rand"
"encoding/gob"
"fmt"
"io"
"os"
"github.com/gorilla/sessions"
)
type (
CookieStore struct{ sessions.CookieStore }
Session struct{ sessions.Session }
)
func NewKey() ([]byte, error) {
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, fmt.Errorf("error generating key: %v", err)
}
return key, nil
}
func SaveKey(key []byte, filename string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating key file: %v", err)
}
defer file.Close()
file.Chmod(0600)
if err = gob.NewEncoder(file).Encode(key); err != nil {
return fmt.Errorf("error ecoding key: %v", err)
}
return nil
}
func LoadKey(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("error opening key file: %v", err)
}
defer file.Close()
key := make([]byte, 32)
if err = gob.NewDecoder(file).Decode(&key); err != nil {
return nil, fmt.Errorf("error decoding key: %v", err)
}
return key, nil
}
func NewCookieStore(key []byte) *CookieStore {
store := sessions.NewCookieStore(key)
store.Options.Secure = true
store.Options.HttpOnly = true
return &CookieStore{*store}
}

View File

@ -9,9 +9,9 @@ import (
f "streifling.com/jason/cpolis/cmd/frontend" f "streifling.com/jason/cpolis/cmd/frontend"
) )
func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { func ServeImage(c *b.Config, s map[string]*f.Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := f.GetSession(w, r, c, s); err != nil { if _, err := f.ManageSession(w, r, c, s); err != nil {
if !tokenIsVerified(w, r, c) { if !tokenIsVerified(w, r, c) {
return return
} }

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
@ -45,33 +46,27 @@ type EditorHTMLData struct {
Contributors []*b.User Contributors []*b.User
} }
func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func WriteArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
var data *EditorHTMLData
if session.Values["article"] == nil {
data = &EditorHTMLData{Action: "submit", Article: new(b.Article), ArticleUsers: make(map[string]*ArticleUser)}
} else {
data = session.Values["article"].(*EditorHTMLData)
}
users, err := db.GetAllUsers(c) users, err := db.GetAllUsers(c)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
data := &EditorHTMLData{Action: "submit", Article: new(b.Article), ArticleUsers: make(map[string]*ArticleUser)}
for _, user := range users { for _, user := range users {
data.ArticleUsers[fmt.Sprint(user.LastName, user.FirstName, user.ID)] = &ArticleUser{User: user, ArticleRole: None} data.ArticleUsers[fmt.Sprint(user.LastName, user.FirstName, user.ID)] = &ArticleUser{User: user, ArticleRole: None}
} }
creator, err := db.GetUser(c, session.Values["id"].(int64)) creator, err := db.GetUser(c, session.User.ID)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -97,19 +92,11 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func SubmitArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Values["article"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -117,12 +104,13 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
Title: r.PostFormValue("article-title"), Title: r.PostFormValue("article-title"),
BannerLink: r.PostFormValue("article-banner-url"), BannerLink: r.PostFormValue("article-banner-url"),
Summary: r.PostFormValue("article-summary"), Summary: r.PostFormValue("article-summary"),
CreatorID: session.Values["id"].(int64), CreatorID: session.User.ID,
Published: false, Published: false,
Rejected: false, Rejected: false,
IsInIssue: r.PostFormValue("issue") == "on", IsInIssue: r.PostFormValue("issue") == "on",
AutoGenerated: false, AutoGenerated: false,
EditedID: 0, EditedID: 0,
UUID: uuid.New(),
} }
if len(article.Title) == 0 { if len(article.Title) == 0 {
@ -178,7 +166,7 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest) http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest)
return return
} }
if err := b.WriteArticleToFile(c, article.ID, content); err != nil { if err := b.WriteArticleToFile(c, article.UUID, content); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -215,7 +203,7 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -227,23 +215,34 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ResubmitArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil {
http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
return
}
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
article := &b.Article{ article, err := db.GetArticle(id)
Title: r.PostFormValue("article-title"), if err != nil {
BannerLink: r.PostFormValue("article-banner-url"), log.Println(err)
Summary: r.PostFormValue("article-summary"), http.Error(w, err.Error(), http.StatusInternalServerError)
CreatorID: session.Values["id"].(int64), return
IsInIssue: r.PostFormValue("issue") == "on",
} }
article.Title = r.PostFormValue("article-title")
article.BannerLink = r.PostFormValue("article-banner-url")
article.Summary = r.PostFormValue("article-summary")
article.CreatorID = session.User.ID
article.IsInIssue = r.PostFormValue("issue") == "on"
if len(article.Title) == 0 { if len(article.Title) == 0 {
http.Error(w, "Bitte den Titel eingeben.", http.StatusBadRequest) http.Error(w, "Bitte den Titel eingeben.", http.StatusBadRequest)
return return
@ -285,20 +284,13 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
article.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
content := r.PostFormValue("article-content") content := r.PostFormValue("article-content")
if len(content) == 0 { if len(content) == 0 {
http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest) http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest)
return return
} }
contentLink := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md")
if err = os.WriteFile(contentLink, []byte(content), 0644); err != nil { if err = b.WriteArticleToFile(c, article.UUID, []byte(content)); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -346,7 +338,7 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -358,11 +350,10 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func ShowUnpublishedUnrejectedAndPublishedRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ShowUnpublishedUnrejectedAndPublishedRejectedArticles(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -402,12 +393,11 @@ func ShowUnpublishedUnrejectedAndPublishedRejectedArticles(c *b.Config, db *b.DB
} }
} }
func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ShowRejectedArticles(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -425,7 +415,7 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF
data.MyIDs = make(map[int64]bool) data.MyIDs = make(map[int64]bool)
for _, article := range data.RejectedArticles { for _, article := range data.RejectedArticles {
if article.CreatorID == session.Values["id"].(int64) { if article.CreatorID == session.User.ID {
data.MyIDs[article.ID] = true data.MyIDs[article.ID] = true
} }
} }
@ -440,12 +430,11 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF
} }
} }
func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ReviewRejectedArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -466,7 +455,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
data.Image = data.Article.BannerLink data.Image = data.Article.BannerLink
articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md") articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.UUID, ".md")
content, err := os.ReadFile(articleAbsName) content, err := os.ReadFile(articleAbsName)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -513,7 +502,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
data.ArticleUsers[fmt.Sprint(contributor.LastName, contributor.FirstName, contributor.ID)].ArticleRole = Contributor data.ArticleUsers[fmt.Sprint(contributor.LastName, contributor.FirstName, contributor.ID)].ArticleRole = Contributor
} }
creator, err := db.GetUser(c, session.Values["id"].(int64)) creator, err := db.GetUser(c, session.User.ID)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -545,12 +534,11 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
} }
} }
func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func PublishArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -598,7 +586,7 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", oldArticle.ID, ".md")); err != nil { if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", oldArticle.UUID, ".md")); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -624,7 +612,7 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -636,12 +624,11 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func RejectArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -659,7 +646,7 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -671,11 +658,10 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func ShowCurrentIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ShowCurrentIssue(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -695,11 +681,10 @@ func ShowCurrentIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
} }
} }
func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.HandlerFunc { func ShowPublishedArticles(c *b.Config, db *b.DB, s map[string]*Session, action string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -732,11 +717,10 @@ func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore, action strin
} }
} }
func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, button string) http.HandlerFunc { func ReviewArticle(c *b.Config, db *b.DB, s map[string]*Session, action, title, button string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -780,7 +764,7 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto
return return
} }
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md")
content, err := os.ReadFile(articleAbsName) content, err := os.ReadFile(articleAbsName)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -827,16 +811,22 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto
} }
} }
func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func DeleteArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil {
http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
return
}
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) article, err := db.GetArticle(id)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -849,7 +839,7 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", id, ".md")); err != nil { if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md")); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -868,7 +858,7 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -880,12 +870,11 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AllowEditArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -921,8 +910,8 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
return return
} }
src := fmt.Sprint(c.ArticleDir, "/", oldArticle.ID, ".md") src := fmt.Sprint(c.ArticleDir, "/", oldArticle.UUID, ".md")
dst := fmt.Sprint(c.ArticleDir, "/", newArticle.ID, ".md") dst := fmt.Sprint(c.ArticleDir, "/", newArticle.UUID, ".md")
if err = b.CopyFile(src, dst); err != nil { if err = b.CopyFile(src, dst); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -962,7 +951,7 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
@ -973,11 +962,10 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
} }
} }
func EditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func EditArticle(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -999,7 +987,7 @@ func EditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
data.Image = data.Article.BannerLink data.Image = data.Article.BannerLink
content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md")) content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.UUID, ".md"))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

107
cmd/frontend/docx.go Normal file
View File

@ -0,0 +1,107 @@
package frontend
import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/gabriel-vasile/mimetype"
"github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend"
)
func UploadDocx(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := ManageSession(w, r, c, s)
if err != nil {
http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
return
}
file, fileHeader, err := r.FormFile("docx-upload")
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
var buf bytes.Buffer
if _, err = io.Copy(&buf, file); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
mime := mimetype.Detect(buf.Bytes())
if !mime.Is("application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
http.Error(w, "Die Datei ist kein DOCX Worddokument.", http.StatusBadRequest)
return
}
docxFilename := fmt.Sprint(uuid.New(), ".docx")
absDocxFilepath, err := filepath.Abs("/tmp/" + docxFilename)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = os.WriteFile(absDocxFilepath, buf.Bytes(), 0644); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer os.Remove(absDocxFilepath)
mdString, err := b.ConvertToMarkdown(c, absDocxFilepath)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
uuidName := uuid.New()
mdFilename := fmt.Sprint(uuidName, ".md")
absMdFilepath, err := filepath.Abs(c.ArticleDir + "/" + mdFilename)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = os.WriteFile(absMdFilepath, mdString, 0644); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
article := &b.Article{
Created: time.Now(),
UUID: uuidName,
CreatorID: session.User.ID,
Rejected: true,
}
article.Title = fmt.Sprint(fileHeader.Filename, "-", article.UUID)
id, err := db.AddArticle(article)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = db.WriteArticleAuthors(id, []int64{session.User.ID}); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
}

99
cmd/frontend/homepage.go Normal file
View File

@ -0,0 +1,99 @@
package frontend
import (
"html/template"
"log"
"net/http"
"time"
b "streifling.com/jason/cpolis/cmd/backend"
)
func HomePage(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
numRows, err := db.CountEntries("users")
if err != nil {
log.Fatalln(err)
}
data := new(struct {
*UserHTMLData
Version string
})
data.UserHTMLData = &UserHTMLData{User: new(b.User)}
data.Version = c.Version
files := make([]string, 2)
files[0] = c.WebDir + "/templates/index.html"
if numRows == 0 {
data.Role = b.NonExistent
data.Title = "Erster Benutzer (Administrator)"
data.ButtonText = "Anlegen"
data.URL = "/user/add-first"
files[1] = c.WebDir + "/templates/edit-user.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} else {
cookie, err := r.Cookie("cpolis_session")
if err != nil {
files[1] = c.WebDir + "/templates/login.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
session, ok := s[cookie.Value]
if !ok {
cookie.Expires = time.Now()
http.SetCookie(w, cookie)
files[1] = c.WebDir + "/templates/login.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
data.Role = session.User.Role
files[1] = c.WebDir + "/templates/hub.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
}
func ShowHub(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := ManageSession(w, r, c, s)
if err != nil {
http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
return
}
data := new(struct{ Role int })
data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}

View File

@ -9,11 +9,10 @@ import (
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func UploadEasyMDEImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { func UploadEasyMDEImage(c *b.Config, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -42,11 +41,10 @@ func UploadEasyMDEImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
} }
} }
func UploadImage(c *b.Config, s *b.CookieStore, fileKey, htmlFile, htmlTemplate string) http.HandlerFunc { func UploadImage(c *b.Config, s map[string]*Session, fileKey, htmlFile, htmlTemplate string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }

View File

@ -8,22 +8,15 @@ import (
"os" "os"
"time" "time"
"github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func PublishLatestIssue(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Values["article"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -34,6 +27,7 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
Rejected: false, Rejected: false,
Created: time.Now(), Created: time.Now(),
AutoGenerated: true, AutoGenerated: true,
UUID: uuid.New(),
} }
if len(article.Title) == 0 { if len(article.Title) == 0 {
@ -49,14 +43,7 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
} }
authorIDs := make([]int64, 1) authorIDs := make([]int64, 1)
var ok bool authorIDs[0] = session.User.ID
if authorIDs[0], ok = session.Values["id"].(int64); !ok {
msg := "fälschlicherweise session.Values[\"id\"].(int64) für authorIDs[0] angenommen"
log.Println(msg)
http.Error(w, msg, http.StatusInternalServerError)
return
}
if err = db.WriteArticleAuthors(article.ID, authorIDs); err != nil { if err = db.WriteArticleAuthors(article.ID, authorIDs); err != nil {
log.Println(err) log.Println(err)
@ -70,7 +57,7 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
return return
} }
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.UUID, ".md")
if err = os.WriteFile(articleAbsName, content, 0644); err != nil { if err = os.WriteFile(articleAbsName, content, 0644); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -101,15 +88,8 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
return return
} }
session.Values["issue-image"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)

View File

@ -5,20 +5,20 @@ import (
"log" "log"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings"
"github.com/google/uuid" "github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func UploadPDF(c *b.Config, s *b.CookieStore) http.HandlerFunc { func UploadPDF(c *b.Config, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
file, _, err := r.FormFile("pdf-upload") file, header, err := r.FormFile("pdf-upload")
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -44,7 +44,9 @@ func UploadPDF(c *b.Config, s *b.CookieStore) http.HandlerFunc {
return return
} }
filename := fmt.Sprint(uuid.New(), ".pdf") oldFilename := header.Filename
oldFilename = strings.Join(strings.Split(oldFilename, ".")[:len(oldFilename)-1], ".")
filename := fmt.Sprint(oldFilename, ".", uuid.New(), ".pdf")
absFilepath, err := filepath.Abs(c.PDFDir + "/" + filename) absFilepath, err := filepath.Abs(c.PDFDir + "/" + filename)
if err != nil { if err != nil {
log.Println(err) log.Println(err)

View File

@ -1,115 +1,102 @@
package frontend package frontend
import ( import (
"context"
"errors"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"time"
"github.com/google/uuid"
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func saveSession(w http.ResponseWriter, r *http.Request, s *b.CookieStore, u *b.User) error { type Session struct {
session, err := s.Get(r, "cookie") ctx context.Context
if err != nil { cancel context.CancelFunc
return fmt.Errorf("error getting session: %v", err) cookie *http.Cookie
} User *b.User
session.Values["authenticated"] = true
session.Values["id"] = u.ID
session.Values["name"] = u.FirstName + u.LastName
session.Values["role"] = u.Role
if err := session.Save(r, w); err != nil {
return fmt.Errorf("error saving session: %v", err)
}
return nil
} }
// GetSession is used for verifying that the user is logged in and returns their session and an error. func newSession(w http.ResponseWriter, c *b.Config, sessionExpiryChan chan<- string, user *b.User) *Session {
func GetSession(w http.ResponseWriter, r *http.Request, c *b.Config, s *b.CookieStore) (*b.Session, error) { sessionID := uuid.New().String()
msg := "Keine gültige Session. Bitte erneut anmelden." expires := time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours))
ctx, cancel := context.WithDeadline(context.Background(), expires)
session := &Session{
ctx: ctx,
cancel: cancel,
cookie: &http.Cookie{
Name: "cpolis_session",
Value: sessionID,
Expires: expires,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
},
User: user,
}
go func() {
<-session.ctx.Done()
sessionExpiryChan <- session.cookie.Value
session.cookie.Expires = time.Now()
http.SetCookie(w, session.cookie)
}()
return session
}
func StartSessions() (map[string]*Session, chan string) {
sessions := make(map[string]*Session)
sessionExpiryChan := make(chan string)
go func() {
for sessionID := range sessionExpiryChan {
delete(sessions, sessionID)
}
}()
return sessions, sessionExpiryChan
}
// ManageSession is used for verifying that the user is logged in and returns
// their session and an error. It also handles cases where the user is not
// logged in.
func ManageSession(w http.ResponseWriter, r *http.Request, c *b.Config, s map[string]*Session) (*Session, error) {
tmpl, tmplErr := template.ParseFiles(c.WebDir+"/templates/index.html", c.WebDir+"/templates/login.html") tmpl, tmplErr := template.ParseFiles(c.WebDir+"/templates/index.html", c.WebDir+"/templates/login.html")
tmpSession, err := s.Get(r, "cookie") cookie, err := r.Cookie("cpolis_session")
if err != nil { if err != nil {
if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", msg); err != nil { if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
return nil, fmt.Errorf("error executing template: %v", err) return nil, fmt.Errorf("error executing template: %v", err)
} }
return nil, fmt.Errorf("error getting session: %v", err)
return nil, errors.New("no cookie set")
} }
session := &b.Session{Session: *tmpSession} session, ok := s[cookie.Value]
if session.IsNew { if !ok {
if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", msg); err != nil { cookie.Expires = time.Now()
http.SetCookie(w, cookie)
if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
return nil, fmt.Errorf("error executing template: %v", err) return nil, fmt.Errorf("error executing template: %v", err)
} }
return nil, errors.New("session does not exist")
} }
session.cookie.Expires = time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours))
http.SetCookie(w, session.cookie)
return session, nil return session, nil
} }
func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func Login(c *b.Config, db *b.DB, s map[string]*Session, sessionExpiryChan chan string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
numRows, err := db.CountEntries("users")
if err != nil {
log.Fatalln(err)
}
data := new(struct {
*UserHTMLData
Version string
})
data.UserHTMLData = &UserHTMLData{User: new(b.User)}
data.Version = c.Version
files := make([]string, 2)
files[0] = c.WebDir + "/templates/index.html"
if numRows == 0 {
data.Role = b.NonExistent
data.Title = "Erster Benutzer (Administrator)"
data.ButtonText = "Anlegen"
data.URL = "/user/add-first"
files[1] = c.WebDir + "/templates/edit-user.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} else {
session, err := s.Get(r, "cookie")
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if auth, ok := session.Values["authenticated"].(bool); auth && ok {
data.Role = session.Values["role"].(int)
files[1] = c.WebDir + "/templates/hub.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} else {
data.Role = b.Author
files[1] = c.WebDir + "/templates/login.html"
tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
}
}
func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userName := r.PostFormValue("username") userName := r.PostFormValue("username")
password := r.PostFormValue("password") password := r.PostFormValue("password")
@ -133,11 +120,9 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err := saveSession(w, r, s, user); err != nil { session := newSession(w, c, sessionExpiryChan, user)
log.Println(err) s[session.cookie.Value] = session
http.Error(w, err.Error(), http.StatusInternalServerError) http.SetCookie(w, session.cookie)
return
}
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil {
@ -148,52 +133,32 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func Logout(c *b.Config, s *b.CookieStore) http.HandlerFunc { func Logout(c *b.Config, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) tmpl, tmplErr := template.ParseFiles(c.WebDir + "/templates/login.html")
cookie, err := r.Cookie("cpolis_session")
if err != nil { if err != nil {
log.Println(err) if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) log.Println(err)
return http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} }
cookie.Expires = time.Now()
http.SetCookie(w, cookie)
session.Options.MaxAge = -1 session, ok := s[cookie.Value]
if err = session.Save(r, w); err != nil { if !ok {
log.Println(err) if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) log.Println(err)
return http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} }
session.cancel()
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
func ShowHub(c *b.Config, db *b.DB, 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
}
session.Values["article"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -8,11 +8,10 @@ import (
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
func CreateTag(c *b.Config, s *b.CookieStore) http.HandlerFunc { func CreateTag(c *b.Config, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -25,12 +24,11 @@ func CreateTag(c *b.Config, s *b.CookieStore) http.HandlerFunc {
} }
} }
func AddTag(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AddTag(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -42,7 +40,7 @@ func AddTag(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
db.AddTag(tag) db.AddTag(tag)
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)

View File

@ -43,11 +43,10 @@ func sortUsersByName(users []*b.User) {
}) })
} }
func CreateUser(c *b.Config, s *b.CookieStore) http.HandlerFunc { func CreateUser(c *b.Config, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -67,12 +66,11 @@ func CreateUser(c *b.Config, s *b.CookieStore) http.HandlerFunc {
} }
} }
func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AddUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -134,7 +132,7 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -146,16 +144,15 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func EditSelf(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
user, err := db.GetUser(c, session.Values["id"].(int64)) user, err := db.GetUser(c, session.User.ID)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -179,17 +176,16 @@ func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func UpdateSelf(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
user := &b.User{ user := &b.User{
ID: session.Values["id"].(int64), ID: session.User.ID,
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
@ -244,7 +240,7 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -256,7 +252,7 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AddFirstUser(c *b.Config, db *b.DB, s map[string]*Session, sessionExpiryChan chan string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
user := &b.User{ user := &b.User{
@ -303,20 +299,18 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err := saveSession(w, r, s, user); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := db.AddIssue(); err != nil { if _, err := db.AddIssue(); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
session := newSession(w, c, sessionExpiryChan, user)
s[session.cookie.Value] = session
http.SetCookie(w, session.cookie)
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = 0 data.Role = user.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
@ -327,12 +321,11 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.HandlerFunc { func ShowAllUsers(c *b.Config, db *b.DB, s map[string]*Session, action string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -348,7 +341,7 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
delete(data.Users, session.Values["id"].(int64)) delete(data.Users, session.User.ID)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/show-all-users.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/show-all-users.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
@ -359,11 +352,10 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H
} }
} }
func EditUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func EditUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if _, err := GetSession(w, r, c, s); err != nil { if _, err := ManageSession(w, r, c, s); err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -398,12 +390,11 @@ func EditUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func UpdateUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -473,7 +464,7 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
@ -484,12 +475,11 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
} }
func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func DeleteUser(c *b.Config, db *b.DB, s map[string]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r, c, s) session, err := ManageSession(w, r, c, s)
if err != nil { if err != nil {
log.Println(err) http.Error(w, "Die Session ist abgelaufen. Bitte erneut anmelden.", http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -507,7 +497,7 @@ func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
data := new(struct{ Role int }) data := new(struct{ Role int })
data.Role = session.Values["role"].(int) data.Role = session.User.Role
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"encoding/gob"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -11,10 +10,6 @@ import (
f "streifling.com/jason/cpolis/cmd/frontend" f "streifling.com/jason/cpolis/cmd/frontend"
) )
func init() {
gob.Register(b.User{})
}
func main() { func main() {
config, err := b.HandleConfig() config, err := b.HandleConfig()
if err != nil { if err != nil {
@ -34,68 +29,62 @@ func main() {
} }
defer db.Close() defer db.Close()
key, err := b.LoadKey(config.GOBKeyFile) sessions, sessionExpiryChan := f.StartSessions()
if err != nil { defer close(sessionExpiryChan)
key, err = b.NewKey()
if err != nil { // go b.CleanUpImages(config)
log.Fatalln(err)
}
if err = b.SaveKey(key, config.GOBKeyFile); err != nil {
log.Fatalln(err)
}
}
store := b.NewCookieStore(key)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/web/static/", http.StripPrefix("/web/static/", mux.Handle("/web/static/", http.StripPrefix("/web/static/",
http.FileServer(http.Dir(config.WebDir+"/static/")))) http.FileServer(http.Dir(config.WebDir+"/static/"))))
mux.HandleFunc("/", f.HomePage(config, db, store)) mux.HandleFunc("/", f.HomePage(config, db, sessions))
mux.HandleFunc("GET /article/allow-edit/{id}", f.AllowEditArticle(config, db, store)) mux.HandleFunc("GET /article/allow-edit/{id}", f.AllowEditArticle(config, db, sessions))
mux.HandleFunc("GET /article/all-published/review-edit", f.ShowPublishedArticles(config, db, store, "review-edit")) mux.HandleFunc("GET /article/all-published/review-edit", f.ShowPublishedArticles(config, db, sessions, "review-edit"))
mux.HandleFunc("GET /article/all-published/delete", f.ShowPublishedArticles(config, db, store, "review-delete")) mux.HandleFunc("GET /article/all-published/delete", f.ShowPublishedArticles(config, db, sessions, "review-delete"))
mux.HandleFunc("GET /article/all-rejected", f.ShowRejectedArticles(config, db, store)) mux.HandleFunc("GET /article/all-rejected", f.ShowRejectedArticles(config, db, sessions))
mux.HandleFunc("GET /article/all-unpublished-unrejected-and-published-rejected", f.ShowUnpublishedUnrejectedAndPublishedRejectedArticles(config, db, store)) mux.HandleFunc("GET /article/all-unpublished-unrejected-and-published-rejected", f.ShowUnpublishedUnrejectedAndPublishedRejectedArticles(config, db, sessions))
mux.HandleFunc("GET /article/delete/{id}", f.DeleteArticle(config, db, store)) mux.HandleFunc("GET /article/delete/{id}", f.DeleteArticle(config, db, sessions))
mux.HandleFunc("GET /article/edit/{id}", f.EditArticle(config, db, store)) mux.HandleFunc("GET /article/edit/{id}", f.EditArticle(config, db, sessions))
mux.HandleFunc("GET /article/publish/{id}", f.PublishArticle(config, db, store)) mux.HandleFunc("GET /article/publish/{id}", f.PublishArticle(config, db, sessions))
mux.HandleFunc("GET /article/reject/{id}", f.RejectArticle(config, db, store)) mux.HandleFunc("GET /article/reject/{id}", f.RejectArticle(config, db, sessions))
mux.HandleFunc("GET /article/review-delete/{id}", f.ReviewArticle(config, db, store, "delete", "Artikel löschen", "Löschen")) mux.HandleFunc("GET /article/review-delete/{id}", f.ReviewArticle(config, db, sessions, "delete", "Artikel löschen", "Löschen"))
mux.HandleFunc("GET /article/review-edit/{id}", f.ReviewArticle(config, db, store, "allow-edit", "Artikel bearbeiten", "Bearbeiten erlauben")) mux.HandleFunc("GET /article/review-edit/{id}", f.ReviewArticle(config, db, sessions, "allow-edit", "Artikel bearbeiten", "Bearbeiten erlauben"))
mux.HandleFunc("GET /article/review-rejected/{id}", f.ReviewRejectedArticle(config, db, store)) mux.HandleFunc("GET /article/review-rejected/{id}", f.ReviewRejectedArticle(config, db, sessions))
mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewArticle(config, db, store, "publish", "Artikel veröffentlichen", "Veröffentlichen")) mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewArticle(config, db, sessions, "publish", "Artikel veröffentlichen", "Veröffentlichen"))
mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db)) mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db))
mux.HandleFunc("GET /article/serve/{id}/clicks", c.ServeClicks(db)) mux.HandleFunc("GET /article/serve/{id}/clicks", c.ServeClicks(db))
mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store)) mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, sessions))
mux.HandleFunc("GET /atom/serve", c.ServeAtomFeed(config)) mux.HandleFunc("GET /atom/serve", c.ServeAtomFeed(config))
mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, sessions))
mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config, store)) mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config, sessions))
mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, store)) mux.HandleFunc("GET /issue/this", f.ShowCurrentIssue(config, db, sessions))
mux.HandleFunc("GET /logout", f.Logout(config, store)) mux.HandleFunc("GET /logout", f.Logout(config, sessions))
mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config))
mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config)) mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config))
mux.HandleFunc("GET /tag/create", f.CreateTag(config, store)) mux.HandleFunc("GET /tag/create", f.CreateTag(config, sessions))
mux.HandleFunc("GET /user/create", f.CreateUser(config, store)) mux.HandleFunc("GET /user/create", f.CreateUser(config, sessions))
mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store)) mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, sessions))
mux.HandleFunc("GET /user/edit/{id}", f.EditUser(config, db, store)) mux.HandleFunc("GET /user/edit/{id}", f.EditUser(config, db, sessions))
mux.HandleFunc("GET /user/edit/self", f.EditSelf(config, db, store)) mux.HandleFunc("GET /user/edit/self", f.EditSelf(config, db, sessions))
mux.HandleFunc("GET /user/show-all/delete", f.ShowAllUsers(config, db, store, "delete")) mux.HandleFunc("GET /user/show-all/delete", f.ShowAllUsers(config, db, sessions, "delete"))
mux.HandleFunc("GET /user/show-all/edit", f.ShowAllUsers(config, db, store, "edit")) mux.HandleFunc("GET /user/show-all/edit", f.ShowAllUsers(config, db, sessions, "edit"))
mux.HandleFunc("POST /article/resubmit/{id}", f.ResubmitArticle(config, db, store)) mux.HandleFunc("POST /article/resubmit/{id}", f.ResubmitArticle(config, db, sessions))
mux.HandleFunc("POST /article/submit", f.SubmitArticle(config, db, store)) mux.HandleFunc("POST /article/submit", f.SubmitArticle(config, db, sessions))
mux.HandleFunc("POST /article/upload-banner", f.UploadImage(config, store, "article-banner", "editor.html", "article-banner-template")) mux.HandleFunc("POST /article/upload-banner", f.UploadImage(config, sessions, "article-banner", "editor.html", "article-banner-template"))
mux.HandleFunc("POST /article/upload-image", f.UploadEasyMDEImage(config, store)) mux.HandleFunc("POST /article/upload-image", f.UploadEasyMDEImage(config, sessions))
mux.HandleFunc("POST /issue/publish", f.PublishLatestIssue(config, db, store)) mux.HandleFunc("POST /docx/upload", f.UploadDocx(config, db, sessions))
mux.HandleFunc("POST /issue/upload-banner", f.UploadImage(config, store, "issue-banner", "current-issue.html", "issue-banner-template")) mux.HandleFunc("POST /issue/publish", f.PublishLatestIssue(config, db, sessions))
mux.HandleFunc("POST /login", f.Login(config, db, store)) mux.HandleFunc("POST /issue/upload-banner", f.UploadImage(config, sessions, "issue-banner", "current-issue.html", "issue-banner-template"))
mux.HandleFunc("POST /pdf/upload", f.UploadPDF(config, store)) mux.HandleFunc("POST /login", f.Login(config, db, sessions, sessionExpiryChan))
mux.HandleFunc("POST /tag/add", f.AddTag(config, db, store)) mux.HandleFunc("POST /pdf/upload", f.UploadPDF(config, sessions))
mux.HandleFunc("POST /user/add", f.AddUser(config, db, store)) mux.HandleFunc("POST /tag/add", f.AddTag(config, db, sessions))
mux.HandleFunc("POST /user/add-first", f.AddFirstUser(config, db, store)) mux.HandleFunc("POST /user/add", f.AddUser(config, db, sessions))
mux.HandleFunc("POST /user/update/{id}", f.UpdateUser(config, db, store)) mux.HandleFunc("POST /user/add-first", f.AddFirstUser(config, db, sessions, sessionExpiryChan))
mux.HandleFunc("POST /user/update/self", f.UpdateSelf(config, db, store)) mux.HandleFunc("POST /user/update/{id}", f.UpdateUser(config, db, sessions))
mux.HandleFunc("POST /user/upload-profile-pic", f.UploadImage(config, store, "upload-profile-pic", "edit-user.html", "profile-pic-template")) mux.HandleFunc("POST /user/update/self", f.UpdateSelf(config, db, sessions))
mux.HandleFunc("POST /user/upload-profile-pic", f.UploadImage(config, sessions, "upload-profile-pic", "edit-user.html", "profile-pic-template"))
log.Fatalln(http.ListenAndServe(config.Port, mux)) log.Fatalln(http.ListenAndServe(config.Port, mux))
} }

View File

@ -38,6 +38,7 @@ CREATE TABLE articles (
clicks INT NOT NULL, clicks INT NOT NULL,
is_in_issue BOOL NOT NULL, is_in_issue BOOL NOT NULL,
auto_generated BOOL NOT NULL, auto_generated BOOL NOT NULL,
uuid VARCHAR(36) NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY (creator_id) REFERENCES users (id), FOREIGN KEY (creator_id) REFERENCES users (id),
FOREIGN KEY (issue_id) REFERENCES issues (id) FOREIGN KEY (issue_id) REFERENCES issues (id)

3
go.mod
View File

@ -8,9 +8,9 @@ require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/chai2010/webp v1.1.1 github.com/chai2010/webp v1.1.1
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/gabriel-vasile/mimetype v1.4.8
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.4.0
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.32.0
@ -49,7 +49,6 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect

8
go.sum
View File

@ -62,6 +62,8 @@ github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -80,8 +82,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
@ -94,10 +94,6 @@ github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrk
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=

View File

@ -7,6 +7,11 @@
<h2>Artikel</h2> <h2>Artikel</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button> <button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button>
<form class="flex" hx-encoding="multipart/form-data">
<label class="btn text-center" for="docx-upload">Word-Dokument hochladen</label>
<input accept=".docx" class="hidden" id="docx-upload" name="docx-upload" type="file"
hx-post="/docx/upload" />
</form>
<button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button> <button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button>
{{if lt .Role 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected" {{if lt .Role 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected"
hx-target="#page-content">Artikel veröffentlichen</button>{{end}} hx-target="#page-content">Artikel veröffentlichen</button>{{end}}