Compare commits

...

8 Commits

23 changed files with 497 additions and 169 deletions

46
cmd/control/cli.go Normal file
View File

@ -0,0 +1,46 @@
package control
import (
"flag"
"fmt"
"path/filepath"
)
type CliArgs struct {
DBName string
KeyFile string
LogFile string
PicsDir string
WebDir string
}
func HandleCliArgs() (*CliArgs, error) {
var err error
cliArgs := new(CliArgs)
keyFile := flag.String("key", "/var/www/cpolis.key", "key file")
logFile := flag.String("log", "/var/log/cpolis.log", "log file")
picsDir := flag.String("pics", "/var/www/cpolis/pics", "pictures directory")
webDir := flag.String("web", "/var/www/cpolis/web", "web directory")
flag.StringVar(&cliArgs.DBName, "db", "cpolis", "DB name")
flag.Parse()
cliArgs.KeyFile, err = filepath.Abs(*keyFile)
if err != nil {
return nil, fmt.Errorf("error finding absolute path for KeyFile: %v", err)
}
cliArgs.LogFile, err = filepath.Abs(*logFile)
if err != nil {
return nil, fmt.Errorf("error finding absolute path for LogFile: %v", err)
}
cliArgs.PicsDir, err = filepath.Abs(*picsDir)
if err != nil {
return nil, fmt.Errorf("error finding absolute path for PicsDir: %v", err)
}
cliArgs.WebDir, err = filepath.Abs(*webDir)
if err != nil {
return nil, fmt.Errorf("error finding absolute path for WebDir: %v", err)
}
return cliArgs, nil
}

View File

@ -2,6 +2,8 @@ package control
import (
"fmt"
"io"
"os"
"time"
"git.streifling.com/jason/rss"
@ -48,3 +50,82 @@ func GetChannel(db *model.DB, title, link, description string) (*rss.Channel, er
return channel, nil
}
func GenerateRSS(db *model.DB, title, link, desc string) (*string, error) {
channel := &rss.Channel{
Title: title,
Link: link,
Description: desc,
Items: make([]*rss.Item, 0),
}
articles, err := db.GetCertainArticles(true, false)
if err != nil {
return nil, fmt.Errorf("error getting published articles for RSS feed: %v", err)
}
for _, article := range articles {
tags, err := db.GetArticleTags(article.ID)
if err != nil {
return nil, fmt.Errorf("error getting tags for articles for RSS feed: %v", err)
}
tagNames := make([]string, 0)
for _, tag := range tags {
tagNames = append(tagNames, tag.Name)
}
tagNames = append(tagNames, fmt.Sprint("Orient Express ", article.IssueID))
user, err := db.GetUser(article.AuthorID)
if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err)
}
articleTitle, err := ConvertToPlain(article.Title)
if err != nil {
return nil, fmt.Errorf("error converting title to plain text for RSS feed: %v", err)
}
articleDescription, err := ConvertToPlain(article.Description)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
}
articleContent, err := ConvertToHTML(article.Content)
if err != nil {
return nil, fmt.Errorf("error converting content to HTML for RSS feed: %v", err)
}
channel.Items = append(channel.Items, &rss.Item{
Title: articleTitle,
Author: user.FirstName + user.LastName,
PubDate: article.Created.Format(time.RFC1123Z),
Description: articleDescription,
Content: &rss.Content{Value: articleContent},
Categories: tagNames,
})
}
feed := rss.NewFeed()
feed.Channels = append(feed.Channels, channel)
rss, err := feed.ToXML()
if err != nil {
return nil, fmt.Errorf("error converting RSS feed to XML: %v", err)
}
return &rss, nil
}
func SaveRSS(filename string, feed *string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating file for RSS feed: %v", err)
}
defer file.Close()
file.Chmod(0644)
if _, err = io.WriteString(file, *feed); err != nil {
return fmt.Errorf("error writing to RSS file: %v", err)
}
return nil
}

View File

@ -16,62 +16,67 @@ func init() {
}
func main() {
logFile, err := os.OpenFile("tmp/cpolis.log",
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
args, err := control.HandleCliArgs()
if err != nil {
log.Fatalln(err)
}
logFile, err := os.OpenFile(args.LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatalln(err)
}
defer logFile.Close()
// log.SetOutput(logFile)
log.SetOutput(logFile)
db, err := model.OpenDB("cpolis")
db, err := model.OpenDB(args.DBName)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
key, err := control.LoadKey("tmp/key.gob")
key, err := control.LoadKey(args.KeyFile)
if err != nil {
key, err = control.NewKey()
if err != nil {
log.Fatalln(err)
}
control.SaveKey(key, "tmp/key.gob")
control.SaveKey(key, args.KeyFile)
}
store := control.NewCookieStore(key)
mux := http.NewServeMux()
mux.Handle("/web/static/", http.StripPrefix("/web/static/",
http.FileServer(http.Dir("web/static/"))))
mux.HandleFunc("/", view.HomePage(db, store))
http.FileServer(http.Dir(args.WebDir+"/static/"))))
mux.HandleFunc("/", view.HomePage(args, db, store))
mux.HandleFunc("GET /create-tag/", view.CreateTag)
mux.HandleFunc("GET /create-user/", view.CreateUser)
mux.HandleFunc("GET /edit-user/", view.EditUser(db, store))
mux.HandleFunc("GET /hub/", view.ShowHub(db, store))
mux.HandleFunc("GET /logout/", view.Logout(store))
mux.HandleFunc("GET /publish-issue/", view.PublishLatestIssue(db))
mux.HandleFunc("GET /rejected-articles/", view.ShowRejectedArticles(db, store))
mux.HandleFunc("GET /rss/", view.ShowRSS(
mux.HandleFunc("GET /create-tag/", view.CreateTag(args))
mux.HandleFunc("GET /create-user/", view.CreateUser(args))
mux.HandleFunc("GET /edit-user/", view.EditUser(args, db, store))
mux.HandleFunc("GET /hub/", view.ShowHub(args, db, store))
mux.HandleFunc("GET /logout/", view.Logout(args, store))
mux.HandleFunc("GET /publish-issue/", view.PublishLatestIssue(args, db, store))
mux.HandleFunc("GET /rejected-articles/", view.ShowRejectedArticles(args, db, store))
mux.HandleFunc("GET /rss/", view.ShowRSS(args,
db,
"Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt",
"https://distrikt-ni-st.de",
"Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität",
))
mux.HandleFunc("GET /this-issue/", view.ShowCurrentArticles(db))
mux.HandleFunc("GET /unpublished-articles/", view.ShowUnpublishedArticles(db))
mux.HandleFunc("GET /write-article/", view.WriteArticle(db))
mux.HandleFunc("GET /this-issue/", view.ShowCurrentArticles(args, db))
mux.HandleFunc("GET /unpublished-articles/", view.ShowUnpublishedArticles(args, db))
mux.HandleFunc("GET /write-article/", view.WriteArticle(args, db))
mux.HandleFunc("POST /add-tag/", view.AddTag(db, store))
mux.HandleFunc("POST /add-user/", view.AddUser(db, store))
mux.HandleFunc("POST /login/", view.Login(db, store))
mux.HandleFunc("POST /publish-article/", view.PublishArticle(db, store))
mux.HandleFunc("POST /reject-article/", view.RejectArticle(db, store))
mux.HandleFunc("POST /resubmit-article/", view.ResubmitArticle(db, store))
mux.HandleFunc("POST /review-rejected-article/", view.ReviewRejectedArticle(db, store))
mux.HandleFunc("POST /review-unpublished-article/", view.ReviewUnpublishedArticle(db, store))
mux.HandleFunc("POST /submit-article/", view.SubmitArticle(db, store))
mux.HandleFunc("POST /update-user/", view.UpdateUser(db, store))
mux.HandleFunc("POST /add-tag/", view.AddTag(args, db, store))
mux.HandleFunc("POST /add-user/", view.AddUser(args, db, store))
mux.HandleFunc("POST /login/", view.Login(args, db, store))
mux.HandleFunc("POST /publish-article/", view.PublishArticle(args, db, store))
mux.HandleFunc("POST /reject-article/", view.RejectArticle(args, db, store))
mux.HandleFunc("POST /resubmit-article/", view.ResubmitArticle(args, db, store))
mux.HandleFunc("POST /review-rejected-article/", view.ReviewRejectedArticle(args, db, store))
mux.HandleFunc("POST /review-unpublished-article/", view.ReviewUnpublishedArticle(args, db, store))
mux.HandleFunc("POST /submit-article/", view.SubmitArticle(args, db, store))
mux.HandleFunc("POST /update-user/", view.UpdateUser(args, db, store))
mux.HandleFunc("POST /upload-image/", view.UploadImage(args))
log.Fatalln(http.ListenAndServe(":8080", mux))
}

View File

@ -105,7 +105,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
query := `
SELECT id, title, created, description, content, author_id
SELECT id, title, created, description, content, author_id, issue_id
FROM articles
WHERE published = ?
AND rejected = ?
@ -121,7 +121,8 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
var created []byte
if err = rows.Scan(&article.ID, &article.Title, &created,
&article.Description, &article.Content, &article.AuthorID); err != nil {
&article.Description, &article.Content, &article.AuthorID,
&article.IssueID); err != nil {
return nil, fmt.Errorf("error scanning article row: %v", err)
}

View File

@ -6,10 +6,7 @@ import (
)
func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
query := `
INSERT INTO articles_tags (article_id, tag_id)
VALUES (?, ?)
`
query := "INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)"
for i := 0; i < TxMaxRetries; i++ {
err := func() error {
@ -68,8 +65,8 @@ func (db *DB) GetArticleTags(articleID int64) ([]*Tag, error) {
}
func (db *DB) UpdateArticleTags(articleID int64, tagIDs []int64) error {
query := `
`
deleteQuery := "DELETE FROM articles_tags WHERE article_id = ?"
insertQuery := "INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)"
for i := 0; i < TxMaxRetries; i++ {
err := func() error {
@ -78,6 +75,22 @@ func (db *DB) UpdateArticleTags(articleID int64, tagIDs []int64) error {
return fmt.Errorf("error starting transaction: %v", err)
}
if _, err := tx.Exec(deleteQuery, articleID); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error deleting entries from articles_tags before inserting new ones: %v", err)
}
for _, tagID := range tagIDs {
if _, err := tx.Exec(insertQuery, articleID, tagID); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error inserting new entries into articles_tags: %v", err)
}
}
if err = tx.Commit(); err != nil {
return fmt.Errorf("error committing transaction: %v", err)
}

View File

@ -23,7 +23,6 @@ func (db *DB) AddIssue() (int64, error) {
}
func (db *DB) PublishLatestIssue() error {
var id int64
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
updateQuery := "UPDATE issues SET published = true WHERE published = false"
insertQuery := "INSERT INTO issues (published) VALUES (?)"
@ -35,7 +34,7 @@ func (db *DB) PublishLatestIssue() error {
return fmt.Errorf("error starting transaction: %v", err)
}
if _, err := tx.Exec(updateQuery, id); err != nil {
if _, err := tx.Exec(updateQuery); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}

View File

@ -1,9 +1,12 @@
package view
import (
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
@ -11,21 +14,21 @@ import (
"streifling.com/jason/cpolis/cmd/model"
)
func ShowHub(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func ShowHub(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int))
}
}
func WriteArticle(db *model.DB) http.HandlerFunc {
func WriteArticle(c *control.CliArgs, db *model.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tags, err := db.GetTagList()
if err != nil {
@ -34,16 +37,16 @@ func WriteArticle(db *model.DB) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/editor.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tags)
}
}
func SubmitArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func SubmitArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -81,13 +84,13 @@ func SubmitArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}
}
func ResubmitArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func ResubmitArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PostFormValue("article-id"), 10, 64)
if err != nil {
@ -100,7 +103,7 @@ func ResubmitArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
description := r.PostFormValue("article-description")
content := r.PostFormValue("article-content")
if err := db.UpdateAttributes(
if err = db.UpdateAttributes(
&model.Attribute{Table: "articles", ID: id, AttName: "title", Value: title},
&model.Attribute{Table: "articles", ID: id, AttName: "description", Value: description},
&model.Attribute{Table: "articles", ID: id, AttName: "content", Value: content},
@ -111,20 +114,37 @@ func ResubmitArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
r.ParseForm()
tags := make([]int64, 0)
for _, tag := range r.Form["tags"] {
tagID, err := strconv.ParseInt(tag, 10, 64)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tags = append(tags, tagID)
}
if err = db.UpdateArticleTags(id, tags); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}
}
func ShowUnpublishedArticles(db *model.DB) http.HandlerFunc {
func ShowUnpublishedArticles(c *control.CliArgs, db *model.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
unpublishedArticles, err := db.GetCertainArticles(false, false)
if err != nil {
@ -133,13 +153,13 @@ func ShowUnpublishedArticles(db *model.DB) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/unpublished-articles.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/unpublished-articles.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", unpublishedArticles)
}
}
func ShowRejectedArticles(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func ShowRejectedArticles(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type htmlData struct {
MyIDs map[int64]bool
@ -168,13 +188,13 @@ func ShowRejectedArticles(db *model.DB, s *control.CookieStore) http.HandlerFunc
}
}
tmpl, err := template.ParseFiles("web/templates/rejected-articles.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/rejected-articles.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", data)
}
}
func ReviewUnpublishedArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func ReviewUnpublishedArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type htmlData struct {
Article *model.Article
@ -203,13 +223,13 @@ func ReviewUnpublishedArticle(db *model.DB, s *control.CookieStore) http.Handler
return
}
tmpl, err := template.ParseFiles("web/templates/to-be-published.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/to-be-published.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", data)
}
}
func ReviewRejectedArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func ReviewRejectedArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type htmlData struct {
Selected map[int64]bool
@ -250,13 +270,13 @@ func ReviewRejectedArticle(db *model.DB, s *control.CookieStore) http.HandlerFun
data.Selected[tag.ID] = true
}
tmpl, err := template.ParseFiles("web/templates/rework-article.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/rework-article.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", data)
}
}
func PublishArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func PublishArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64)
if err != nil {
@ -267,7 +287,7 @@ func PublishArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -288,13 +308,30 @@ func PublishArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
feed, err := control.GenerateRSS(
db,
"Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt",
"https://distrikt-ni-st.de",
"Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität",
)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = control.SaveRSS("tmp/orientexpress_alle.rss", feed); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}
}
func RejectArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func RejectArticle(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64)
if err != nil {
@ -305,7 +342,7 @@ func RejectArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -318,13 +355,13 @@ func RejectArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}
}
func ShowCurrentArticles(db *model.DB) http.HandlerFunc {
func ShowCurrentArticles(c *control.CliArgs, db *model.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
articles, err := db.GetCurrentIssueArticles()
if err != nil {
@ -333,20 +370,37 @@ func ShowCurrentArticles(db *model.DB) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/current-articles.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/current-articles.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", articles)
}
}
func PublishLatestIssue(db *model.DB) http.HandlerFunc {
func UploadImage(c *control.CliArgs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := db.PublishLatestIssue(); err != nil {
file, header, err := r.FormFile("article-image")
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
filename := fmt.Sprint(c.PicsDir, time.Now().Format("2006-01-02_15:04:05"), "-", header.Filename)
img, err := os.Create(filename)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer img.Close()
if _, err = io.Copy(img, file); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html")
template.Must(tmpl, err).ExecuteTemplate(w, "editor-images", fmt.Sprint("![", header.Filename, "](", filename, ")"))
}
}

View File

@ -8,23 +8,25 @@ import (
"streifling.com/jason/cpolis/cmd/model"
)
func CreateTag(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("web/templates/add-tag.html")
func CreateTag(c *control.CliArgs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-tag.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
}
}
func AddTag(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func AddTag(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
db.AddTag(r.PostFormValue("tag"))
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}

31
cmd/view/issues.go Normal file
View File

@ -0,0 +1,31 @@
package view
import (
"html/template"
"log"
"net/http"
"streifling.com/jason/cpolis/cmd/control"
"streifling.com/jason/cpolis/cmd/model"
)
func PublishLatestIssue(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := db.PublishLatestIssue(); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
}
}

View File

@ -12,7 +12,7 @@ import (
"streifling.com/jason/cpolis/cmd/model"
)
func ShowRSS(db *model.DB, title, link, desc string) http.HandlerFunc {
func ShowRSS(c *control.CliArgs, db *model.DB, title, link, desc string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
channel := &rss.Channel{
Title: title,
@ -88,7 +88,7 @@ func ShowRSS(db *model.DB, title, link, desc string) http.HandlerFunc {
return
}
files := []string{"web/templates/index.html", "web/templates/feed.rss"}
files := []string{c.WebDir + "/templates/index.html", c.WebDir + "/templates/feed.rss"}
tmpl, err := template.ParseFiles(files...)
template.Must(tmpl, err).Execute(w, rss)
}

View File

@ -27,26 +27,26 @@ func saveSession(w http.ResponseWriter, r *http.Request, s *control.CookieStore,
return nil
}
func HomePage(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func HomePage(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
numRows, err := db.CountEntries("users")
if err != nil {
log.Fatalln(err)
}
files := []string{"web/templates/index.html"}
files := []string{c.WebDir + "/templates/index.html"}
if numRows == 0 {
files = append(files, "web/templates/add-user.html")
files = append(files, c.WebDir+"/templates/add-user.html")
tmpl, err := template.ParseFiles(files...)
template.Must(tmpl, err).Execute(w, nil)
} else {
session, _ := s.Get(r, "cookie")
if auth, ok := session.Values["authenticated"].(bool); auth && ok {
files = append(files, "web/templates/hub.html")
files = append(files, c.WebDir+"/templates/hub.html")
tmpl, err := template.ParseFiles(files...)
template.Must(tmpl, err).Execute(w, session.Values["role"])
} else {
files = append(files, "web/templates/login.html")
files = append(files, c.WebDir+"/templates/login.html")
tmpl, err := template.ParseFiles(files...)
template.Must(tmpl, err).Execute(w, nil)
}
@ -54,7 +54,7 @@ func HomePage(db *model.DB, s *control.CookieStore) http.HandlerFunc {
}
}
func Login(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func Login(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userName := r.PostFormValue("username")
password := r.PostFormValue("password")
@ -84,16 +84,16 @@ func Login(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user.Role)
}
}
func Logout(s *control.CookieStore) http.HandlerFunc {
func Logout(c *control.CliArgs, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -106,7 +106,7 @@ func Logout(s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
}
}

View File

@ -31,12 +31,14 @@ func checkUserStrings(user *model.User) (string, int, bool) {
}
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("web/templates/add-user.html")
func CreateUser(c *control.CliArgs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
}
}
func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func AddUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
role, err := strconv.Atoi(r.PostFormValue("role"))
if err != nil {
@ -59,7 +61,7 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if len(htmlData.UserName) == 0 || len(htmlData.FirstName) == 0 ||
len(htmlData.LastName) == 0 || len(pass) == 0 || len(pass2) == 0 {
htmlData.Msg = "Alle Felder müssen ausgefüllt werden."
tmpl, err := template.ParseFiles("web/templates/add-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", htmlData)
return
}
@ -67,7 +69,7 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if !ok {
htmlData.Msg = fmt.Sprint(userString, " ist zu lang. Maximal ",
stringLen, " Zeichen erlaubt.")
tmpl, err := template.ParseFiles("web/templates/add-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", htmlData)
return
}
@ -75,13 +77,13 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if id != 0 {
htmlData.Msg = fmt.Sprint(htmlData.UserName,
" ist bereits vergeben. Bitte anderen Benutzernamen wählen.")
tmpl, err := template.ParseFiles("web/templates/add-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", htmlData)
return
}
if pass != pass2 {
htmlData.Msg = "Die Passwörter stimmen nicht überein."
tmpl, err := template.ParseFiles("web/templates/add-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", htmlData)
return
}
@ -117,16 +119,16 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
}
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0)
}
}
func EditUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func EditUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -138,16 +140,16 @@ func EditUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
return
}
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user)
}
}
func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
func UpdateUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
@ -167,7 +169,7 @@ func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if len(userData.UserName) == 0 || len(userData.FirstName) == 0 ||
len(userData.LastName) == 0 {
userData.Msg = "Alle Felder mit * müssen ausgefüllt sein."
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", userData.Msg)
return
@ -177,7 +179,7 @@ func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if !ok {
userData.Msg = fmt.Sprint(userString, " ist zu lang. Maximal ",
stringLen, " Zeichen erlaubt.")
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", userData)
return
@ -187,7 +189,7 @@ func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
if id != userData.ID {
userData.Msg = "Benutzername bereits vergeben."
userData.UserName = ""
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", userData)
return
@ -203,11 +205,11 @@ func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
newPass,
newPass2); err != nil {
userData.Msg = "Aktualisierung der Benutzerdaten fehlgeschlagen."
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", userData)
}
tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int))
}

View File

@ -1,8 +1,10 @@
{{define "page-content"}}
<h2>Neuer Benutzer</h2>
<form>
<input required name="tag" placeholder="Tag" type="text" />
<input type="submit" value="Anlegen" hx-post="/add-tag/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
{{end}}

View File

@ -1,13 +1,18 @@
{{define "page-content"}}
<h2>Neuer Benutzer</h2>
<form>
<div>
<input required name="username" placeholder="Benutzername" type="text" value="{{.UserName}}" />
<input required name="password" placeholder="Passwort" type="password" />
<input required name="password2" placeholder="Passwort wiederholen" type="password" />
</div>
<div>
<input required name="first-name" placeholder="Vorname" type="text" value="{{.FirstName}}" />
<input required name="last-name" placeholder="Nachname" type="text" value="{{.LastName}}" />
</div>
<div>
<input required id="author" name="role" type="radio" value="3" {{if eq .Role 3 }}checked{{end}} />
<label for="author">Autor</label>
<input required id="editor" name="role" type="radio" value="2" {{if eq .Role 2 }}checked{{end}} />
@ -16,9 +21,11 @@
<label for="publisher">Herausgeber</label>
<input required id="admin" name="role" type="radio" value="0" {{if eq .Role 0 }}checked{{end}} />
<label for="admin">Admin</label>
</div>
<input type="submit" value="Anlegen" hx-post="/add-user/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
<script>

View File

@ -1,10 +1,13 @@
{{define "page-content"}}
{{range .}}
<div>
{{range .}}
<div>
<h1>{{.Title}}</h1>
<p>{{.Description}}</p>
</div>
{{end}}
</div>
{{end}}
<button hx-get="/publish-issue/" hx-target="#page-content">Ausgabe publizieren</button>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
{{end}}

View File

@ -1,13 +1,19 @@
{{define "page-content"}}
<form>
<div>
<input name="username" type="text" value="{{.UserName}}" />
<input name="first-name" type="text" value="{{.FirstName}}" />
<input name="last-name" type="text" value="{{.LastName}}" />
</div>
<div>
<input name="old-password" placeholder="Altes Passwort" type="password" />
<input name="password" placeholder="Neues Passwort" type="password" />
<input name="password2" placeholder="Wiederholen" type="password" />
</div>
<input type="submit" value="Aktualisieren" hx-post="/update-user/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
{{end}}

View File

@ -1,13 +1,57 @@
{{define "page-content"}}
<h2>Editor</h2>
<form>
<div>
<input name="article-title" placeholder="Titel" type="text" />
<textarea name="article-description" placeholder="Beschreibung"></textarea>
<textarea name="article-content" placeholder="Artikel"></textarea>
</div>
<div>
{{range .}}
<div>
<input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" />
<label for="{{.Name}}">{{.Name}}</label>
</div>
{{end}}
</div>
<div id="editor-images">
<input name="article-image" type="file" hx-encoding="multipart/form-data" hx-post="/upload-image/"
hx-swap="beforeend" hx-target="#editor-images" />
</div>
<input type="submit" value="Senden" hx-post="/submit-article/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
<script>
function copyToClipboard(text) {
event.preventDefault(); // Get-Request verhindern
var textarea = document.createElement("textarea");
textarea.textContent = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (err) {
console.warn('Fehler beim Kopieren', err);
}
document.body.removeChild(textarea);
}
</script>
{{end}}
{{define "editor-images"}}
{{if gt (len .) 0}}
<div>
{{.}}
<button onclick="copyToClipboard('{{.}}')">Kopieren</button>
</div>
{{end}}
{{end}}

View File

@ -1,17 +1,25 @@
{{define "page-content"}}
<h2>Hub</h2>
<button hx-get="/write-article/" hx-target="#page-content">Artikel schreiben</button>
<button hx-get="/rejected-articles/" hx-target="#page-content">Abgelehnte Artikel</button>
<button hx-get="/rss/" hx-target="#page-content">RSS Feed</button>
<button hx-get="/edit-user/" hx-target="#page-content">Benutzer bearbeiten</button>
<div>
<button hx-get="/write-article/" hx-target="#page-content">Artikel schreiben</button>
<button hx-get="/rejected-articles/" hx-target="#page-content">Abgelehnte Artikel</button>
<button hx-get="/rss/" hx-target="#page-content">RSS Feed</button>
<button hx-get="/edit-user/" hx-target="#page-content">Benutzer bearbeiten</button>
</div>
{{if lt . 3}}
<button hx-get="/unpublished-articles/" hx-target="#page-content">Unveröffentlichte Artikel</button>
<button hx-get="/create-tag/" hx-target="#page-content">Neuer Tag</button>
<div>
<button hx-get="/unpublished-articles/" hx-target="#page-content">Unveröffentlichte Artikel</button>
<button hx-get="/create-tag/" hx-target="#page-content">Neuer Tag</button>
</div>
{{end}}
{{if lt . 2}}
<button hx-get="/this-issue/" hx-target="#page-content">Diese Ausgabe</button>
<div>
<button hx-get="/this-issue/" hx-target="#page-content">Diese Ausgabe</button>
</div>
{{end}}
{{if eq . 0}}
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
<div>
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
</div>
{{end}}
{{end}}

View File

@ -1,8 +1,11 @@
{{define "page-content"}}
<h2>Anmeldung</h2>
<form>
<div>
<input name="username" placeholder="Benutzername" type="text" />
<input name="password" placeholder="Passwort" type="password" />
</div>
<input type="submit" value="Anmelden" hx-post="/login/" hx-target="#page-content" />
</form>
{{end}}

View File

@ -1,12 +1,18 @@
{{define "page-content"}}
<form>
<div>
{{range .RejectedArticles}}
<div>
{{if index $.MyIDs .ID}}
<input required id="{{.ID}}" name="id" type="radio" value="{{.ID}}" />
<label for="{{.ID}}">{{.Title}}</label>
{{end}}
</div>
{{end}}
</div>
<input type="submit" value="Auswählen" hx-post="/review-rejected-article/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Zurück</button>
{{end}}

View File

@ -1,16 +1,25 @@
{{define "page-content"}}
<h2>Editor</h2>
<form>
<div>
<input name="article-title" placeholder="Titel" type="text" value="{{.Article.Title}}" />
<textarea name="article-description" placeholder="Beschreibung">{{.Article.Description}}</textarea>
<textarea name="article-content" placeholder="Artikel">{{.Article.Content}}</textarea>
<input name="article-id" type="hidden" value="{{.Article.ID}}" />
</div>
<div>
{{range .Tags}}
<input id="tag-{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" {{if index $.Selected .ID}}checked{{end}} />
<div>
<input id="tag-{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" {{if index $.Selected
.ID}}checked{{end}} />
<label for="tag-{{.Name}}">{{.Name}}</label>
</div>
{{end}}
</div>
<input type="submit" value="Senden" hx-post="/resubmit-article/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Zurück</button>
{{end}}

View File

@ -1,10 +1,16 @@
{{define "page-content"}}
<form>
<div>
{{range .}}
<div>
<input required id="{{.ID}}" name="id" type="radio" value="{{.ID}}" />
<label for="{{.ID}}">{{.Title}}</label>
</div>
{{end}}
</div>
<input type="submit" value="Auswählen" hx-post="/review-unpublished-article/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Zurück</button>
{{end}}