forked from jason/cpolis
		
	Compare commits
	
		
			17 Commits
		
	
	
		
			publish_is
			...
			tailwind
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7e7de28b14 | |||
| 0139f7ab9a | |||
| 7fc115bcc3 | |||
| ae90f693f6 | |||
| a730e11b4a | |||
| 959e1e96b3 | |||
| 68b052625f | |||
| a0fe0024f2 | |||
| 6e3c4bf647 | |||
| 26988ecf6a | |||
| 9408ce99e3 | |||
| af036b4909 | |||
| e60e6114bd | |||
| 600044c621 | |||
| 77a90cb4f1 | |||
| 34e9e9edd5 | |||
| 4d1faf3d4a | 
							
								
								
									
										44
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
root = "."
 | 
			
		||||
testdata_dir = "testdata"
 | 
			
		||||
tmp_dir = "tmp"
 | 
			
		||||
 | 
			
		||||
[build]
 | 
			
		||||
  args_bin = []
 | 
			
		||||
  bin = "./tmp/main -key tmp/key.gob -log tmp/cpolis.log -pics tmp/pics -rss tmp/orientexpress_alle.rss -web web"
 | 
			
		||||
  cmd = "go build -o ./tmp/main ./cmd/main.go"
 | 
			
		||||
  delay = 0
 | 
			
		||||
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
 | 
			
		||||
  exclude_file = []
 | 
			
		||||
  exclude_regex = ["_test.go"]
 | 
			
		||||
  exclude_unchanged = false
 | 
			
		||||
  follow_symlink = false
 | 
			
		||||
  full_bin = ""
 | 
			
		||||
  include_dir = []
 | 
			
		||||
  include_ext = ["go", "tpl", "tmpl", "html", "css"]
 | 
			
		||||
  include_file = []
 | 
			
		||||
  kill_delay = "0s"
 | 
			
		||||
  log = "build-errors.log"
 | 
			
		||||
  poll = false
 | 
			
		||||
  poll_interval = 0
 | 
			
		||||
  rerun = false
 | 
			
		||||
  rerun_delay = 500
 | 
			
		||||
  send_interrupt = false
 | 
			
		||||
  stop_on_error = false
 | 
			
		||||
 | 
			
		||||
[color]
 | 
			
		||||
  app = ""
 | 
			
		||||
  build = "yellow"
 | 
			
		||||
  main = "magenta"
 | 
			
		||||
  runner = "green"
 | 
			
		||||
  watcher = "cyan"
 | 
			
		||||
 | 
			
		||||
[log]
 | 
			
		||||
  main_only = false
 | 
			
		||||
  time = false
 | 
			
		||||
 | 
			
		||||
[misc]
 | 
			
		||||
  clean_on_exit = false
 | 
			
		||||
 | 
			
		||||
[screen]
 | 
			
		||||
  clear_on_rebuild = false
 | 
			
		||||
  keep_scroll = true
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -23,3 +23,4 @@ go.work
 | 
			
		||||
 | 
			
		||||
# Custom stuff
 | 
			
		||||
tmp/
 | 
			
		||||
style.css
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								cmd/control/cli.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								cmd/control/cli.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
package control
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CliArgs struct {
 | 
			
		||||
	DBName  string
 | 
			
		||||
	KeyFile string
 | 
			
		||||
	LogFile string
 | 
			
		||||
	Port    string
 | 
			
		||||
	PicsDir string
 | 
			
		||||
	RSSFile string
 | 
			
		||||
	WebDir  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HandleCliArgs() (*CliArgs, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	cliArgs := new(CliArgs)
 | 
			
		||||
 | 
			
		||||
	keyFile := flag.String("key", "/var/www/cpolis/cpolis.key", "key file")
 | 
			
		||||
	logFile := flag.String("log", "/var/log/cpolis.log", "log file")
 | 
			
		||||
	picsDir := flag.String("pics", "/var/www/cpolis/pics", "pictures directory")
 | 
			
		||||
	port := flag.Int("port", 8080, "port")
 | 
			
		||||
	rssFile := flag.String("rss", "/var/www/cpolis/cpolis.rss", "RSS file")
 | 
			
		||||
	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.Port = fmt.Sprint(":", *port)
 | 
			
		||||
 | 
			
		||||
	cliArgs.RSSFile, err = filepath.Abs(*rssFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("error finding absolute path for RSSFile: %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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										73
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								cmd/main.go
									
									
									
									
									
								
							@@ -16,62 +16,75 @@ 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 /review-rejected-article/{id}/",
 | 
			
		||||
		view.ReviewRejectedArticle(args, db, store))
 | 
			
		||||
	mux.HandleFunc("GET /review-unpublished-article/{id}/",
 | 
			
		||||
		view.ReviewUnpublishedArticle(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/{id}/",
 | 
			
		||||
		view.PublishArticle(args, db, store))
 | 
			
		||||
	mux.HandleFunc("POST /reject-article/{id}/",
 | 
			
		||||
		view.RejectArticle(args, db, store))
 | 
			
		||||
	mux.HandleFunc("POST /resubmit-article/{id}/",
 | 
			
		||||
		view.ResubmitArticle(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))
 | 
			
		||||
	log.Fatalln(http.ListenAndServe(args.Port, mux))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,15 +84,15 @@ 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)
 | 
			
		||||
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -182,7 +202,7 @@ func ReviewUnpublishedArticle(db *model.DB, s *control.CookieStore) http.Handler
 | 
			
		||||
		}
 | 
			
		||||
		data := new(htmlData)
 | 
			
		||||
 | 
			
		||||
		id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64)
 | 
			
		||||
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -218,7 +238,7 @@ func ReviewRejectedArticle(db *model.DB, s *control.CookieStore) http.HandlerFun
 | 
			
		||||
		}
 | 
			
		||||
		data := new(htmlData)
 | 
			
		||||
 | 
			
		||||
		id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64)
 | 
			
		||||
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
@@ -250,15 +270,15 @@ 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)
 | 
			
		||||
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
@@ -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,15 +308,32 @@ 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(c.RSSFile, 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)
 | 
			
		||||
		id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
@@ -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(""))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
	template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
 | 
			
		||||
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
									
								
							
							
						
						
									
										31
									
								
								cmd/view/issues.go
									
									
									
									
									
										Normal 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"])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
	template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
 | 
			
		||||
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))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/** @type {import('tailwindcss').Config} */
 | 
			
		||||
module.exports = {
 | 
			
		||||
    content: ["./web/templates/*.html"],
 | 
			
		||||
    theme: {
 | 
			
		||||
        extend: {}
 | 
			
		||||
    },
 | 
			
		||||
    plugins: [],
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								web/static/css/input.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								web/static/css/input.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    width: 800px;
 | 
			
		||||
    @apply mx-auto text-slate-900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
    @apply font-bold mb-2 text-2xl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form {
 | 
			
		||||
    @apply flex flex-col gap-y-3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="file"] {
 | 
			
		||||
    @apply border rounded-md w-full;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="password"],
 | 
			
		||||
input[type="text"] {
 | 
			
		||||
    @apply border h-8 rounded-md;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
    @apply border h-32 rounded-md;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-area {
 | 
			
		||||
    @apply flex gap-4 mt-4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn {
 | 
			
		||||
    @apply bg-slate-50 border my-2 px-3 py-2 rounded-md w-full hover:bg-slate-100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-btn {
 | 
			
		||||
    @apply bg-slate-800 border my-2 px-3 py-2 rounded-md text-slate-50 w-full hover:bg-slate-700;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Neuer Benutzer</h2>
 | 
			
		||||
<h2>Neuer Tag</h2>
 | 
			
		||||
 | 
			
		||||
<form>
 | 
			
		||||
    <input required name="tag" placeholder="Tag" type="text" />
 | 
			
		||||
    <input type="submit" value="Anlegen" hx-post="/add-tag/" hx-target="#page-content" />
 | 
			
		||||
    <input required name="tag" placeholder="Tag eingeben" type="text" />
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Anlegen" hx-post="/add-tag/" hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,53 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Neuer Benutzer</h2>
 | 
			
		||||
<form>
 | 
			
		||||
    <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 class="grid grid-cols-3 gap-4">
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="username">Benutzername</label>
 | 
			
		||||
            <input class="w-full" required name="username" type="text" value="{{.UserName}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="password">Passwort</label>
 | 
			
		||||
            <input class="w-full" required name="password" placeholder="***" type="password" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="password2">Passwort wiederholen</label>
 | 
			
		||||
            <input class="w-full" required name="password2" placeholder="***" type="password" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="first-name">Vorname</label>
 | 
			
		||||
            <input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="last-name">Nachname</label>
 | 
			
		||||
            <input class="w-full" required name="last-name" type="text" value="{{.LastName}}" />
 | 
			
		||||
        </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 class="flex gap-4">
 | 
			
		||||
        <div>
 | 
			
		||||
            <input required id="author" name="role" type="radio" value="3" {{if eq .Role 3 }}checked{{end}} />
 | 
			
		||||
            <label for="author">Autor</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input required id="editor" name="role" type="radio" value="2" {{if eq .Role 2 }}checked{{end}} />
 | 
			
		||||
            <label for="editor">Redakteur</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input required id="publisher" name="role" type="radio" value="1" {{if eq .Role 1 }}checked{{end}} />
 | 
			
		||||
            <label for="publisher">Herausgeber</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input required id="admin" name="role" type="radio" value="0" {{if eq .Role 0 }}checked{{end}} />
 | 
			
		||||
            <label for="admin">Admin</label>
 | 
			
		||||
        </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}} />
 | 
			
		||||
    <label for="editor">Redakteur</label>
 | 
			
		||||
    <input required id="publisher" name="role" type="radio" value="1" {{if eq .Role 1 }}checked{{end}} />
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
    <input type="submit" value="Anlegen" hx-post="/add-user/" hx-target="#page-content" />
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Anlegen" hx-post="/add-user/" hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    var msg = "{{.Msg}}";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,15 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
{{range .}}
 | 
			
		||||
<div>
 | 
			
		||||
    <h1>{{.Title}}</h1>
 | 
			
		||||
    <p>{{.Description}}</p>
 | 
			
		||||
<div class="flex flex-col gap-4">
 | 
			
		||||
    {{range .}}
 | 
			
		||||
    <div class="border px-2 py-1 rounded-md">
 | 
			
		||||
        <h1 class="font-bold text-2xl">{{.Title}}</h1>
 | 
			
		||||
        <p>{{.Description}}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="btn-area">
 | 
			
		||||
    <button class="action-btn" hx-get="/publish-issue/" hx-target="#page-content">Ausgabe publizieren</button>
 | 
			
		||||
    <button class="btn" hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
</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}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,36 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<form>
 | 
			
		||||
    <input name="username" type="text" value="{{.UserName}}" />
 | 
			
		||||
    <input name="first-name" type="text" value="{{.FirstName}}" />
 | 
			
		||||
    <input name="last-name" type="text" value="{{.LastName}}" />
 | 
			
		||||
    <div class="grid grid-cols-3 gap-4">
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="username">Benutzername</label>
 | 
			
		||||
            <input class="w-full" name="username" type="text" value="{{.UserName}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="first-name">Vorname</label>
 | 
			
		||||
            <input class="w-full" name="first-name" type="text" value="{{.FirstName}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="last-name">Nachname</label>
 | 
			
		||||
            <input class="w-full" name="last-name" type="text" value="{{.LastName}}" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="old-password">Altes Passwort</label>
 | 
			
		||||
            <input class="w-full" name="old-password" placeholder="***" type="password" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="password">Passwort</label>
 | 
			
		||||
            <input class="w-full" name="password" placeholder="***" type="password" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <label for="password2">Passwort wiederholen</label>
 | 
			
		||||
            <input class="w-full" name="password2" placeholder="***" type="password" />
 | 
			
		||||
        </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" />
 | 
			
		||||
 | 
			
		||||
    <input type="submit" value="Aktualisieren" hx-post="/update-user/" hx-target="#page-content" />
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Aktualisieren" hx-post="/update-user/"
 | 
			
		||||
            hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,66 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Editor</h2>
 | 
			
		||||
<form>
 | 
			
		||||
    <input name="article-title" placeholder="Titel" type="text" />
 | 
			
		||||
    <textarea name="article-description" placeholder="Beschreibung"></textarea>
 | 
			
		||||
    <textarea name="article-content" placeholder="Artikel"></textarea>
 | 
			
		||||
    {{range .}}
 | 
			
		||||
    <input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" />
 | 
			
		||||
    <label for="{{.Name}}">{{.Name}}</label>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    <input type="submit" value="Senden" hx-post="/submit-article/" hx-target="#page-content" />
 | 
			
		||||
    <div class="flex flex-col gap-y-1">
 | 
			
		||||
        <label for="article-title">Titel</label>
 | 
			
		||||
        <input name="article-title" type="text" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex flex-col">
 | 
			
		||||
        <label for="article-description">Beschreibung</label>
 | 
			
		||||
        <textarea name="article-description"></textarea>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex flex-col">
 | 
			
		||||
        <label for="article-content">Artikel</label>
 | 
			
		||||
        <textarea name="article-content"></textarea>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="flex gap-4">
 | 
			
		||||
        {{range .}}
 | 
			
		||||
        <div>
 | 
			
		||||
            <input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" />
 | 
			
		||||
            <label for="{{.Name}}">{{.Name}}</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        {{end}}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="editor-images">
 | 
			
		||||
        <input class="mb-2" name="article-image" type="file" hx-encoding="multipart/form-data" hx-post="/upload-image/"
 | 
			
		||||
            hx-swap="beforeend" hx-target="#editor-images" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Senden" hx-post="/submit-article/" hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
<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 class="border px-2 py-1 rounded-md flex gap-4 justify-between">
 | 
			
		||||
    <div class="self-center">{{.}}</div>
 | 
			
		||||
    <button class="bg-slate-50 border my-2 px-3 py-2 rounded-md w-32 hover:bg-slate-100"
 | 
			
		||||
        onclick="copyToClipboard('{{.}}')">Kopieren</button>
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,40 @@
 | 
			
		||||
{{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>
 | 
			
		||||
{{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>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if lt . 2}}
 | 
			
		||||
<button hx-get="/this-issue/" hx-target="#page-content">Diese Ausgabe</button>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if eq . 0}}
 | 
			
		||||
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
 | 
			
		||||
{{end}}
 | 
			
		||||
<div class="flex flex-col gap-4">
 | 
			
		||||
    <div class="mb-3">
 | 
			
		||||
        <h2>Autor</h2>
 | 
			
		||||
        <div class="grid grid-cols-2 gap-x-4 gap-y-2">
 | 
			
		||||
            <button class="btn" hx-get="/write-article/" hx-target="#page-content">Artikel schreiben</button>
 | 
			
		||||
            <button class="btn" hx-get="/rejected-articles/" hx-target="#page-content">Abgelehnte Artikel</button>
 | 
			
		||||
            <button class="btn" hx-get="/rss/" hx-target="#page-content">RSS Feed</button>
 | 
			
		||||
            <button class="btn" hx-get="/edit-user/" hx-target="#page-content">Benutzer bearbeiten</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{if lt . 3}}
 | 
			
		||||
    <div class="mb-3">
 | 
			
		||||
        <h2>Redakteur</h2>
 | 
			
		||||
        <div class="grid grid-cols-2 gap-4">
 | 
			
		||||
            <button class="btn" hx-get="/unpublished-articles/" hx-target="#page-content">
 | 
			
		||||
                Unveröffentlichte Artikel
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="btn" hx-get="/create-tag/" hx-target="#page-content">Neuer Tag</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    {{if lt . 2}}
 | 
			
		||||
    <div class="mb-3">
 | 
			
		||||
        <h2>Herausgeber</h2>
 | 
			
		||||
        <div class="grid grid-cols-2 gap-4">
 | 
			
		||||
            <button class="btn" hx-get="/this-issue/" hx-target="#page-content">Diese Ausgabe</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    {{if eq . 0}}
 | 
			
		||||
    <div class="mb-3">
 | 
			
		||||
        <h2>Administrator</h2>
 | 
			
		||||
        <div class="grid grid-cols-2 gap-4">
 | 
			
		||||
            <button class="btn" hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,13 @@
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <title>Orient Editor</title>
 | 
			
		||||
    <link href="web/static/css/style.css" rel="stylesheet">
 | 
			
		||||
    <link href="/web/static/css/style.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <header>
 | 
			
		||||
        <h1>Orient Editor</h1>
 | 
			
		||||
        <button hx-get="logout" hx-target="#page-content">Abmelden</button>
 | 
			
		||||
<body class="flex flex-col justify-between min-h-[100dvh] bg-slate-50">
 | 
			
		||||
    <header class="my-8">
 | 
			
		||||
        <h1 class="font-bold text-4xl text-center">Orient Editor</h1>
 | 
			
		||||
        <button class="btn" hx-get="logout" hx-target="#page-content">Abmelden</button>
 | 
			
		||||
    </header>
 | 
			
		||||
 | 
			
		||||
    <main>
 | 
			
		||||
@@ -19,11 +19,13 @@
 | 
			
		||||
            {{template "page-content" .}}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <script src="web/static/js/htmx.min.js"></script>
 | 
			
		||||
        <script src="/web/static/js/htmx.min.js"></script>
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
    <footer>
 | 
			
		||||
        <p>© 2024 Jason Streifling. Alle Rechte vorbehalten.</p>
 | 
			
		||||
    <footer class="my-8">
 | 
			
		||||
        <p class="text-center text-gray-500 dark:text-gray-400">
 | 
			
		||||
            © 2024 Jason Streifling. Alle Rechte vorbehalten.
 | 
			
		||||
        </p>
 | 
			
		||||
    </footer>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Anmeldung</h2>
 | 
			
		||||
<form>
 | 
			
		||||
    <input name="username" placeholder="Benutzername" type="text" />
 | 
			
		||||
    <input name="password" placeholder="Passwort" type="password" />
 | 
			
		||||
    <input type="submit" value="Anmelden" hx-post="/login/" hx-target="#page-content" />
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="w-full" name="username" placeholder="Benutzername" type="text" />
 | 
			
		||||
        <input class="w-full" name="password" placeholder="Passwort" type="password" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <input class="action-btn" type="submit" value="Anmelden" hx-post="/login/" hx-target="#page-content" />
 | 
			
		||||
</form>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<form>
 | 
			
		||||
<div class="flex flex-col gap-4">
 | 
			
		||||
    {{range .RejectedArticles}}
 | 
			
		||||
    {{if index $.MyIDs .ID}}
 | 
			
		||||
    <input required id="{{.ID}}" name="id" type="radio" value="{{.ID}}" />
 | 
			
		||||
    <label for="{{.ID}}">{{.Title}}</label>
 | 
			
		||||
    <button class="btn" hx-get="/review-rejected-article/{{.ID}}/" hx-target="#page-content">
 | 
			
		||||
        <h1 class="font-bold text-2xl">{{.Title}}</h1>
 | 
			
		||||
        <p>{{.Description}}</p>
 | 
			
		||||
    </button>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    {{end}}
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
    <button class="action-btn" hx-get="/hub/" hx-target="#page-content">Zurück</button>
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,68 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<h2>Editor</h2>
 | 
			
		||||
<form>
 | 
			
		||||
    <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 class="flex flex-col gap-y-1">
 | 
			
		||||
        <label for="article-title">Titel</label>
 | 
			
		||||
        <input name="article-title" type="text" value="{{.Article.Title}}" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex flex-col">
 | 
			
		||||
        <label for="article-description">Beschreibung</label>
 | 
			
		||||
        <textarea name="article-description">{{.Article.Description}}</textarea>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex flex-col">
 | 
			
		||||
        <label for="article-content">Artikel</label>
 | 
			
		||||
        <textarea name="article-content" placeholder="Artikel">{{.Article.Content}}</textarea>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {{range .Tags}}
 | 
			
		||||
    <input id="tag-{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" {{if index $.Selected .ID}}checked{{end}} />
 | 
			
		||||
    <label for="tag-{{.Name}}">{{.Name}}</label>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    <div class="flex gap-4">
 | 
			
		||||
        {{range .Tags}}
 | 
			
		||||
        <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" />
 | 
			
		||||
    <div id="editor-images">
 | 
			
		||||
        <input class="mb-2" name="article-image" type="file" hx-encoding="multipart/form-data" hx-post="/upload-image/"
 | 
			
		||||
            hx-swap="beforeend" hx-target="#editor-images" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Senden" hx-post="/resubmit-article/{{.Article.ID}}/"
 | 
			
		||||
            hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Zurück</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
<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 class="border px-2 py-1 rounded-md flex gap-4 justify-between">
 | 
			
		||||
    <div class="self-center">{{.}}</div>
 | 
			
		||||
    <button class="bg-slate-50 border my-2 px-3 py-2 rounded-md w-32 hover:bg-slate-100"
 | 
			
		||||
        onclick="copyToClipboard('{{.}}')">Kopieren</button>
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,17 @@
 | 
			
		||||
    {{.Article.Content}}
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
    {{range .Tags}}
 | 
			
		||||
    {{.Name}}
 | 
			
		||||
    {{end}}
 | 
			
		||||
        {{range .Tags}}
 | 
			
		||||
        {{.Name}}
 | 
			
		||||
        {{end}}
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <input name="id" type="hidden" value="{{.Article.ID}}" />
 | 
			
		||||
    <input type="submit" value="Veröffentlichen" hx-post="/publish-article/" hx-target="#page-content" />
 | 
			
		||||
    <input type="submit" value="Ablehnen" hx-post="/reject-article/" hx-target="#page-content" />
 | 
			
		||||
    <div class="btn-area">
 | 
			
		||||
        <input class="action-btn" type="submit" value="Veröffentlichen" hx-post="/publish-article/{{.Article.ID}}/"
 | 
			
		||||
            hx-target="#page-content" />
 | 
			
		||||
        <input class="btn" type="submit" value="Ablehnen" hx-post="/reject-article/{{.Article.ID}}/"
 | 
			
		||||
            hx-target="#page-content" />
 | 
			
		||||
        <button class="btn" hx-get="/hub/" hx-target="#page-content">Zurück</button>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
<button hx-get="/hub/" hx-target="#page-content">Zurück</button>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
{{define "page-content"}}
 | 
			
		||||
<form>
 | 
			
		||||
<div class="flex flex-col gap-4">
 | 
			
		||||
    {{range .}}
 | 
			
		||||
    <input required id="{{.ID}}" name="id" type="radio" value="{{.ID}}" />
 | 
			
		||||
    <label for="{{.ID}}">{{.Title}}</label>
 | 
			
		||||
    <button class="btn" hx-get="/review-unpublished-article/{{.ID}}/" hx-target="#page-content">
 | 
			
		||||
        <h1 class="font-bold text-2xl">{{.Title}}</h1>
 | 
			
		||||
        <p>{{.Description}}</p>
 | 
			
		||||
    </button>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    <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>
 | 
			
		||||
    <button class="btn" hx-get="/hub/" hx-target="#page-content">Zurück</button>
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user