Incorporated issues
This commit is contained in:
		@@ -50,6 +50,7 @@ func main() {
 | 
				
			|||||||
	mux.HandleFunc("GET /edit-user/", view.EditUser(db, store))
 | 
						mux.HandleFunc("GET /edit-user/", view.EditUser(db, store))
 | 
				
			||||||
	mux.HandleFunc("GET /hub/", view.ShowHub(db, store))
 | 
						mux.HandleFunc("GET /hub/", view.ShowHub(db, store))
 | 
				
			||||||
	mux.HandleFunc("GET /logout/", view.Logout(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 /rejected-articles/", view.ShowRejectedArticles(db, store))
 | 
				
			||||||
	mux.HandleFunc("GET /rss/", view.ShowRSS(
 | 
						mux.HandleFunc("GET /rss/", view.ShowRSS(
 | 
				
			||||||
		db,
 | 
							db,
 | 
				
			||||||
@@ -57,6 +58,7 @@ func main() {
 | 
				
			|||||||
		"https://distrikt-ni-st.de",
 | 
							"https://distrikt-ni-st.de",
 | 
				
			||||||
		"Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität",
 | 
							"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 /unpublished-articles/", view.ShowUnpublishedArticles(db))
 | 
				
			||||||
	mux.HandleFunc("GET /write-article/", view.WriteArticle(db))
 | 
						mux.HandleFunc("GET /write-article/", view.WriteArticle(db))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,10 @@
 | 
				
			|||||||
package model
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,26 +17,64 @@ type Article struct {
 | 
				
			|||||||
	Rejected    bool
 | 
						Rejected    bool
 | 
				
			||||||
	ID          int64
 | 
						ID          int64
 | 
				
			||||||
	AuthorID    int64
 | 
						AuthorID    int64
 | 
				
			||||||
 | 
						IssueID     int64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DB) AddArticle(a *Article) (int64, error) {
 | 
					func (db *DB) AddArticle(a *Article) (int64, error) {
 | 
				
			||||||
	query := `
 | 
						var id int64
 | 
				
			||||||
 | 
						txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
 | 
				
			||||||
 | 
						selectQuery := "SELECT id FROM issues WHERE published = false"
 | 
				
			||||||
 | 
						insertQuery := `
 | 
				
			||||||
    INSERT INTO articles
 | 
					    INSERT INTO articles
 | 
				
			||||||
        (title, description, content, published, rejected, author_id)
 | 
					        (title, description, content, published, rejected, author_id, issue_id)
 | 
				
			||||||
    VALUES (?, ?, ?, ?, ?, ?)
 | 
					    VALUES (?, ?, ?, ?, ?, ?, ?)
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result, err := db.Exec(query, a.Title, a.Description, a.Content,
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
		a.Published, a.Rejected, a.AuthorID)
 | 
							id, err := func() (int64, error) {
 | 
				
			||||||
	if err != nil {
 | 
								tx, err := db.BeginTx(context.Background(), txOptions)
 | 
				
			||||||
		return 0, fmt.Errorf("error inserting article into DB: %v", err)
 | 
								if err != nil {
 | 
				
			||||||
	}
 | 
									return 0, fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
								}
 | 
				
			||||||
	if err != nil {
 | 
					
 | 
				
			||||||
		return 0, fmt.Errorf("error retrieving last ID: %v", err)
 | 
								if err = tx.QueryRow(selectQuery).Scan(&id); err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								result, err := tx.Exec(insertQuery, a.Title, a.Description,
 | 
				
			||||||
 | 
									a.Content, a.Published, a.Rejected, a.AuthorID, id)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("error inserting article into DB: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								id, err := result.LastInsertId()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("error retrieving ID of added article: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.Commit(); err != nil {
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("error committing transaction when adding article to DB: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return id, nil
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return id, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							wait(i)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return id, nil
 | 
						return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DB) GetArticle(id int64) (*Article, error) {
 | 
					func (db *DB) GetArticle(id int64) (*Article, error) {
 | 
				
			||||||
@@ -79,8 +120,8 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
 | 
				
			|||||||
		article := new(Article)
 | 
							article := new(Article)
 | 
				
			||||||
		var created []byte
 | 
							var created []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err = rows.Scan(&article.ID, &article.Title, &created, &article.Description,
 | 
							if err = rows.Scan(&article.ID, &article.Title, &created,
 | 
				
			||||||
			&article.Content, &article.AuthorID); err != nil {
 | 
								&article.Description, &article.Content, &article.AuthorID); err != nil {
 | 
				
			||||||
			return nil, fmt.Errorf("error scanning article row: %v", err)
 | 
								return nil, fmt.Errorf("error scanning article row: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,3 +136,122 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return articleList, nil
 | 
						return articleList, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
 | 
				
			||||||
 | 
						var issueID int64
 | 
				
			||||||
 | 
						txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
 | 
				
			||||||
 | 
						issueQuery := "SELECT id FROM issues WHERE published = false"
 | 
				
			||||||
 | 
						articlesQuery := `
 | 
				
			||||||
 | 
					    SELECT id, title, created, description, content, author_id
 | 
				
			||||||
 | 
					    FROM articles
 | 
				
			||||||
 | 
					    WHERE issue_id = ? AND published = true
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
 | 
							id, err := func() ([]*Article, error) {
 | 
				
			||||||
 | 
								tx, err := db.BeginTx(context.Background(), txOptions)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								row := tx.QueryRow(issueQuery)
 | 
				
			||||||
 | 
								if err := row.Scan(&issueID); err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("error querying DB for unpublished issue: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								rows, err := tx.Query(articlesQuery, issueID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("error querying DB for articles of issue %v: %v", issueID, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								articleList := make([]*Article, 0)
 | 
				
			||||||
 | 
								for rows.Next() {
 | 
				
			||||||
 | 
									article := new(Article)
 | 
				
			||||||
 | 
									var created []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if err = rows.Scan(&article.ID, &article.Title, &created,
 | 
				
			||||||
 | 
										&article.Description, &article.Content, &article.AuthorID); err != nil {
 | 
				
			||||||
 | 
										if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
											log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("error scanning article from issue %v: %v", issueID, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									article.Created, err = time.Parse("2006-01-02 15:04:05", string(created))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
											log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("error parsing created: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									articleList = append(articleList, article)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.Commit(); err != nil {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("error committing transaction when getting articles of issue %v: %v", issueID, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return articleList, nil
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return id, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							wait(i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (db *DB) AddArticleToCurrentIssue(id int64) error {
 | 
				
			||||||
 | 
						var issueID int64
 | 
				
			||||||
 | 
						txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
 | 
				
			||||||
 | 
						selectQuery := "SELECT id FROM issues WHERE published = false"
 | 
				
			||||||
 | 
						updateQuery := "UPDATE articles SET issue_id = ? WHERE id = ?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
 | 
							err := func() error {
 | 
				
			||||||
 | 
								tx, err := db.BeginTx(context.Background(), txOptions)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.QueryRow(selectQuery).Scan(&issueID); err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return fmt.Errorf("error scanning row: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = db.Exec(updateQuery, issueID, id)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return fmt.Errorf("error updating issueID for article: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.Commit(); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error committing transaction when getting articles of issue %v: %v", issueID, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							wait(i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,14 @@ package model
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math"
 | 
					 | 
				
			||||||
	"math/rand/v2"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
 | 
					func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
 | 
				
			||||||
 | 
						query := `
 | 
				
			||||||
 | 
					    INSERT INTO articles_tags (article_id, tag_id)
 | 
				
			||||||
 | 
					    VALUES (?, ?)
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < TxMaxRetries; i++ {
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
		err := func() error {
 | 
							err := func() error {
 | 
				
			||||||
			tx, err := db.Begin()
 | 
								tx, err := db.Begin()
 | 
				
			||||||
@@ -17,13 +19,9 @@ func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for _, tagID := range tagIDs {
 | 
								for _, tagID := range tagIDs {
 | 
				
			||||||
				query := `
 | 
					 | 
				
			||||||
                INSERT INTO articles_tags (article_id, tag_id)
 | 
					 | 
				
			||||||
                VALUES (?, ?)
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
				if _, err := tx.Exec(query, articleID, tagID); err != nil {
 | 
									if _, err := tx.Exec(query, articleID, tagID); err != nil {
 | 
				
			||||||
					if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
										if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
						log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
											log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return fmt.Errorf("error inserting into articles_tags: %v", err)
 | 
										return fmt.Errorf("error inserting into articles_tags: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -39,9 +37,7 @@ func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Println(err)
 | 
							log.Println(err)
 | 
				
			||||||
		waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
 | 
							wait(i)
 | 
				
			||||||
		jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
 | 
					 | 
				
			||||||
		time.Sleep(waitTime + jitter)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
						return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -70,3 +66,29 @@ func (db *DB) GetArticleTags(articleID int64) ([]*Tag, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return tags, nil
 | 
						return tags, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (db *DB) UpdateArticleTags(articleID int64, tagIDs []int64) error {
 | 
				
			||||||
 | 
						query := `
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
 | 
							err := func() error {
 | 
				
			||||||
 | 
								tx, err := db.Begin()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.Commit(); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error committing transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							wait(i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								cmd/model/issues.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								cmd/model/issues.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (db *DB) AddIssue() (int64, error) {
 | 
				
			||||||
 | 
						query := "INSERT INTO issues (published) VALUES (?)"
 | 
				
			||||||
 | 
						result, err := db.Exec(query, false)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("error inserting issue into DB: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						id, err := result.LastInsertId()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("error getting ID of added issue: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return id, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 (?)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
 | 
							err := func() error {
 | 
				
			||||||
 | 
								tx, err := db.BeginTx(context.Background(), txOptions)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if _, err := tx.Exec(updateQuery, id); err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return fmt.Errorf("error publishing issue: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if _, err := tx.Exec(insertQuery, false); err != nil {
 | 
				
			||||||
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return fmt.Errorf("error inserting new issue into DB: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = tx.Commit(); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error committing transaction when publishing issue: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							wait(i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,17 +3,15 @@ package model
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math"
 | 
					 | 
				
			||||||
	"math/rand/v2"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"golang.org/x/crypto/bcrypt"
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	Admin = iota
 | 
						Admin = iota
 | 
				
			||||||
 | 
						Publisher
 | 
				
			||||||
	Editor
 | 
						Editor
 | 
				
			||||||
	Writer
 | 
						Author
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type User struct {
 | 
					type User struct {
 | 
				
			||||||
@@ -24,21 +22,26 @@ type User struct {
 | 
				
			|||||||
	Role      int
 | 
						Role      int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DB) AddUser(user *User, pass string) error {
 | 
					func (db *DB) AddUser(u *User, pass string) (int64, error) {
 | 
				
			||||||
	hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
 | 
						hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("error creating password hash: %v", err)
 | 
							return 0, fmt.Errorf("error creating password hash: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	query := `
 | 
						query := `
 | 
				
			||||||
    INSERT INTO users (username, password, first_name, last_name, role)
 | 
					    INSERT INTO users (username, password, first_name, last_name, role)
 | 
				
			||||||
    VALUES (?, ?, ?, ?, ?)
 | 
					    VALUES (?, ?, ?, ?, ?)
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
	if _, err = db.Exec(query, user.UserName, string(hashedPass), user.FirstName, user.LastName, user.Role); err != nil {
 | 
						result, err := db.Exec(query, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role)
 | 
				
			||||||
		return fmt.Errorf("error inserting user into DB: %v", err)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, err := result.LastInsertId()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("error inserting user into DB: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return id, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DB) GetID(userName string) (int64, bool) {
 | 
					func (db *DB) GetID(userName string) (int64, bool) {
 | 
				
			||||||
@@ -87,14 +90,14 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
 | 
				
			|||||||
	row := tx.QueryRow(getQuery, id)
 | 
						row := tx.QueryRow(getQuery, id)
 | 
				
			||||||
	if err := row.Scan(&queriedPass); err != nil {
 | 
						if err := row.Scan(&queriedPass); err != nil {
 | 
				
			||||||
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
							if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
			log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
								log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("error reading password from DB: %v", err)
 | 
							return fmt.Errorf("error reading password from DB: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(oldPass)); err != nil {
 | 
						if err := bcrypt.CompareHashAndPassword([]byte(queriedPass), []byte(oldPass)); err != nil {
 | 
				
			||||||
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
							if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
			log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
								log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("incorrect password: %v", err)
 | 
							return fmt.Errorf("incorrect password: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -102,7 +105,7 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
 | 
				
			|||||||
	newHashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
 | 
						newHashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
							if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
			log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
								log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("error creating password hash: %v", err)
 | 
							return fmt.Errorf("error creating password hash: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -114,7 +117,7 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
 | 
				
			|||||||
    `
 | 
					    `
 | 
				
			||||||
	if _, err = tx.Exec(setQuery, string(newHashedPass), id); err != nil {
 | 
						if _, err = tx.Exec(setQuery, string(newHashedPass), id); err != nil {
 | 
				
			||||||
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
							if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
			log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
								log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("error updating password in DB: %v", err)
 | 
							return fmt.Errorf("error updating password in DB: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -132,7 +135,8 @@ func (db *DB) GetUser(id int64) (*User, error) {
 | 
				
			|||||||
    `
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	row := db.QueryRow(query, id)
 | 
						row := db.QueryRow(query, id)
 | 
				
			||||||
	if err := row.Scan(&user.ID, &user.UserName, &user.FirstName, &user.LastName, &user.Role); err != nil {
 | 
						if err := row.Scan(&user.ID, &user.UserName, &user.FirstName,
 | 
				
			||||||
 | 
							&user.LastName, &user.Role); err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("error reading user information: %v", err)
 | 
							return nil, fmt.Errorf("error reading user information: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -161,7 +165,7 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass
 | 
				
			|||||||
			if !passwordEmpty {
 | 
								if !passwordEmpty {
 | 
				
			||||||
				if err = tx.ChangePassword(id, oldPass, newPass); err != nil {
 | 
									if err = tx.ChangePassword(id, oldPass, newPass); err != nil {
 | 
				
			||||||
					if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
										if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
						log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
											log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return fmt.Errorf("error changing password: %v", err)
 | 
										return fmt.Errorf("error changing password: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -173,7 +177,7 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass
 | 
				
			|||||||
				&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last},
 | 
									&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last},
 | 
				
			||||||
			); err != nil {
 | 
								); err != nil {
 | 
				
			||||||
				if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
									if rollbackErr := tx.Rollback(); rollbackErr != nil {
 | 
				
			||||||
					log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
										log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return fmt.Errorf("error updating attributes in DB: %v", err)
 | 
									return fmt.Errorf("error updating attributes in DB: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -189,9 +193,7 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Println(err)
 | 
							log.Println(err)
 | 
				
			||||||
		waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
 | 
							wait(i)
 | 
				
			||||||
		jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
 | 
					 | 
				
			||||||
		time.Sleep(waitTime + jitter)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
						return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -272,6 +272,12 @@ func PublishArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
 | 
				
			|||||||
			template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
 | 
								template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = db.AddArticleToCurrentIssue(id); err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err = db.UpdateAttributes(
 | 
							if err = db.UpdateAttributes(
 | 
				
			||||||
			&model.Attribute{Table: "articles", ID: id, AttName: "published", Value: true},
 | 
								&model.Attribute{Table: "articles", ID: id, AttName: "published", Value: true},
 | 
				
			||||||
			&model.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false},
 | 
								&model.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false},
 | 
				
			||||||
@@ -317,3 +323,30 @@ func RejectArticle(db *model.DB, s *control.CookieStore) http.HandlerFunc {
 | 
				
			|||||||
		tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
 | 
							tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ShowCurrentArticles(db *model.DB) http.HandlerFunc {
 | 
				
			||||||
 | 
						return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							articles, err := db.GetCurrentIssueArticles()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tmpl, err := template.ParseFiles("web/templates/current-articles.html")
 | 
				
			||||||
 | 
							template.Must(tmpl, err).ExecuteTemplate(w, "page-content", articles)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func PublishLatestIssue(db *model.DB) 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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tmpl, err := template.ParseFiles("web/templates/hub.html")
 | 
				
			||||||
 | 
							template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package view
 | 
					package view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
@@ -38,6 +39,7 @@ func ShowRSS(db *model.DB, title, link, desc string) http.HandlerFunc {
 | 
				
			|||||||
			for _, tag := range tags {
 | 
								for _, tag := range tags {
 | 
				
			||||||
				tagNames = append(tagNames, tag.Name)
 | 
									tagNames = append(tagNames, tag.Name)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								tagNames = append(tagNames, fmt.Sprint("Orient Express ", article.IssueID))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			user, err := db.GetUser(article.AuthorID)
 | 
								user, err := db.GetUser(article.AuthorID)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,19 +86,21 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		num, err := db.CountEntries("users")
 | 
							htmlData.ID, err = db.AddUser(htmlData.User, pass)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Println(err)
 | 
								log.Println(err)
 | 
				
			||||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
								http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if num == 0 {
 | 
					
 | 
				
			||||||
			if htmlData.Role != model.Admin {
 | 
							if htmlData.ID == 1 {
 | 
				
			||||||
				htmlData.Msg = "Der erste Benutzer muss ein Administrator sein."
 | 
								htmlData.Role = model.Admin
 | 
				
			||||||
				htmlData.Role = model.Admin
 | 
					
 | 
				
			||||||
				tmpl, err := template.ParseFiles("web/templates/add-user.html")
 | 
								if err = db.UpdateAttributes(
 | 
				
			||||||
				tmpl = template.Must(tmpl, err)
 | 
									&model.Attribute{Table: "users", ID: id, AttName: "role", Value: htmlData.Role},
 | 
				
			||||||
				tmpl.ExecuteTemplate(w, "page-content", htmlData)
 | 
								); err != nil {
 | 
				
			||||||
 | 
									log.Println(err)
 | 
				
			||||||
 | 
									http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,12 +109,12 @@ func AddUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
 | 
				
			|||||||
				http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
									http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := db.AddUser(htmlData.User, pass); err != nil {
 | 
								if _, err := db.AddIssue(); err != nil {
 | 
				
			||||||
			log.Println(err)
 | 
									log.Println(err)
 | 
				
			||||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
									http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
			return
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tmpl, err := template.ParseFiles("web/templates/hub.html")
 | 
							tmpl, err := template.ParseFiles("web/templates/hub.html")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,12 @@
 | 
				
			|||||||
    <input required name="first-name" placeholder="Vorname" type="text" value="{{.FirstName}}" />
 | 
					    <input required name="first-name" placeholder="Vorname" type="text" value="{{.FirstName}}" />
 | 
				
			||||||
    <input required name="last-name" placeholder="Nachname" type="text" value="{{.LastName}}" />
 | 
					    <input required name="last-name" placeholder="Nachname" type="text" value="{{.LastName}}" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <input required id="writer" name="role" type="radio" value="2" {{if eq .Role 2 }}checked{{end}} />
 | 
					    <input required id="author" name="role" type="radio" value="3" {{if eq .Role 3 }}checked{{end}} />
 | 
				
			||||||
    <label for="writer">Schreiber</label>
 | 
					    <label for="author">Autor</label>
 | 
				
			||||||
    <input required id="editor" name="role" type="radio" value="1" {{if eq .Role 1 }}checked{{end}} />
 | 
					    <input required id="editor" name="role" type="radio" value="2" {{if eq .Role 2 }}checked{{end}} />
 | 
				
			||||||
    <label for="editor">Redakteur</label>
 | 
					    <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}} />
 | 
					    <input required id="admin" name="role" type="radio" value="0" {{if eq .Role 0 }}checked{{end}} />
 | 
				
			||||||
    <label for="admin">Admin</label>
 | 
					    <label for="admin">Admin</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								web/templates/current-articles.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								web/templates/current-articles.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					{{define "page-content"}}
 | 
				
			||||||
 | 
					{{range .}}
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					    <h1>{{.Title}}</h1>
 | 
				
			||||||
 | 
					    <p>{{.Description}}</p>
 | 
				
			||||||
 | 
					</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}}
 | 
				
			||||||
@@ -4,10 +4,13 @@
 | 
				
			|||||||
<button hx-get="/rejected-articles/" hx-target="#page-content">Abgelehnte Artikel</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="/rss/" hx-target="#page-content">RSS Feed</button>
 | 
				
			||||||
<button hx-get="/edit-user/" hx-target="#page-content">Benutzer bearbeiten</button>
 | 
					<button hx-get="/edit-user/" hx-target="#page-content">Benutzer bearbeiten</button>
 | 
				
			||||||
{{if lt . 2}}
 | 
					{{if lt . 3}}
 | 
				
			||||||
<button hx-get="/unpublished-articles/" hx-target="#page-content">Unveröffentlichte Artikel</button>
 | 
					<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>
 | 
					<button hx-get="/create-tag/" hx-target="#page-content">Neuer Tag</button>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
 | 
					{{if lt . 2}}
 | 
				
			||||||
 | 
					<button hx-get="/this-issue/" hx-target="#page-content">Diese Ausgabe</button>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
{{if eq . 0}}
 | 
					{{if eq . 0}}
 | 
				
			||||||
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
 | 
					<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user