Incorporated issues
This commit is contained in:
parent
304d3aa2e0
commit
78addbd8e3
@ -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}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user