From ca43ec1a810a09ef86ac58ee55ff2cde4696fb9d Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Fri, 27 Dec 2024 10:30:15 +0100 Subject: [PATCH] Add support for multiple authors and contributors --- cmd/backend/articles.go | 26 ++- cmd/backend/articles_authors.go | 11 + cmd/backend/articles_contributors.go | 11 + cmd/backend/atom.go | 15 ++ cmd/backend/users.go | 41 +++- cmd/frontend/articles.go | 304 +++++++++++++++++++++++---- cmd/frontend/users.go | 14 +- create_db.sql | 5 +- web/templates/editor.html | 46 +++- web/templates/review-article.html | 24 ++- 10 files changed, 436 insertions(+), 61 deletions(-) diff --git a/cmd/backend/articles.go b/cmd/backend/articles.go index 63c0f48..cfbc49c 100644 --- a/cmd/backend/articles.go +++ b/cmd/backend/articles.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "log" + "os" "time" ) @@ -14,6 +15,7 @@ type Article struct { BannerLink string Summary string ID int64 + CreatorID int64 IssueID int64 EditedID int64 Published bool @@ -28,8 +30,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) { selectQuery := "SELECT id FROM issues WHERE published = false" insertQuery := ` INSERT INTO articles - (title, banner_link, summary, published, rejected, issue_id, edited_id, is_in_issue, auto_generated) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + (title, banner_link, summary, published, rejected, creator_id, issue_id, edited_id, is_in_issue, auto_generated) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` for i := 0; i < TxMaxRetries; i++ { @@ -46,7 +48,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) { return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err) } - result, err := tx.Exec(insertQuery, a.Title, a.BannerLink, a.Summary, a.Published, a.Rejected, id, a.EditedID, a.IsInIssue, a.AutoGenerated) + result, err := tx.Exec(insertQuery, a.Title, a.BannerLink, a.Summary, a.Published, a.Rejected, a.CreatorID, id, a.EditedID, a.IsInIssue, a.AutoGenerated) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) @@ -80,7 +82,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) { func (db *DB) GetArticle(id int64) (*Article, error) { query := ` - SELECT title, created, banner_link, summary, published, issue_id, edited_id, is_in_issue, auto_generated + SELECT title, created, banner_link, summary, published, creator_id, issue_id, edited_id, is_in_issue, auto_generated FROM articles WHERE id = ? ` @@ -90,7 +92,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) { var created []byte var err error - if err := row.Scan(&article.Title, &created, &article.BannerLink, &article.Summary, &article.Published, &article.IssueID, &article.EditedID, &article.IsInIssue, &article.AutoGenerated); err != nil { + if err := row.Scan(&article.Title, &created, &article.BannerLink, &article.Summary, &article.Published, &article.CreatorID, &article.IssueID, &article.EditedID, &article.IsInIssue, &article.AutoGenerated); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } @@ -105,7 +107,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) { func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, error) { query := fmt.Sprintf(` - SELECT id, title, created, banner_link, summary, issue_id, published, rejected, is_in_issue, auto_generated + SELECT id, title, created, banner_link, summary, creator_id, issue_id, published, rejected, is_in_issue, auto_generated FROM articles WHERE %s = ? `, attribute) @@ -119,7 +121,7 @@ func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, erro article := new(Article) var created []byte - if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.IssueID, &article.Published, &article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil { + if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.CreatorID, &article.IssueID, &article.Published, &article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } @@ -268,3 +270,13 @@ func (db *DB) DeleteArticle(id int64) error { return nil } + +func WriteArticleToFile(c *Config, articleID int64, content []byte) error { + articleAbsName := fmt.Sprint(c.ArticleDir, "/", articleID, ".md") + + if err := os.WriteFile(articleAbsName, content, 0644); err != nil { + return fmt.Errorf("error writing article %v to file: %v", articleID, err) + } + + return nil +} diff --git a/cmd/backend/articles_authors.go b/cmd/backend/articles_authors.go index 0f4ef9e..2b5d451 100644 --- a/cmd/backend/articles_authors.go +++ b/cmd/backend/articles_authors.go @@ -112,3 +112,14 @@ func (db *DB) UpdateArticleAuthors(articleID int64, authorIDs []int64) error { } return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) } + +func (db *DB) DeleteArticleAuthors(articleID int64) error { + query := "DELETE FROM articles_authors WHERE article_id = ?" + + _, err := db.Exec(query, articleID) + if err != nil { + return fmt.Errorf("error deleting articles_authors %v from DB: %v", articleID, err) + } + + return nil +} diff --git a/cmd/backend/articles_contributors.go b/cmd/backend/articles_contributors.go index 696c606..4f6182d 100644 --- a/cmd/backend/articles_contributors.go +++ b/cmd/backend/articles_contributors.go @@ -112,3 +112,14 @@ func (db *DB) UpdateArticleContributors(articleID int64, contributorIDs []int64) } return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) } + +func (db *DB) DeleteArticleContributors(articleID int64) error { + query := "DELETE FROM articles_contributors WHERE article_id = ?" + + _, err := db.Exec(query, articleID) + if err != nil { + return fmt.Errorf("error deleting articles_contributors %v from DB: %v", articleID, err) + } + + return nil +} diff --git a/cmd/backend/atom.go b/cmd/backend/atom.go index 4b4b685..f3ec0c5 100644 --- a/cmd/backend/atom.go +++ b/cmd/backend/atom.go @@ -60,10 +60,25 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) { if err != nil { return nil, fmt.Errorf("error getting user info for Atom feed: %v", err) } + authorID := entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName)) entry.Authors[authorID].URI = c.Domain + "/image/serve/" + user.ProfilePicLink } + contributors, err := db.GetArticleContributors(c, article.ID) + if err != nil { + return nil, fmt.Errorf("error getting article's contributors for Atom feed: %v", err) + } + for _, contributor := range contributors { + user, err := db.GetUser(c, contributor.ID) + if err != nil { + return nil, fmt.Errorf("error getting user info for Atom feed: %v", err) + } + + contributorID := entry.AddContributor(atom.NewPerson(user.FirstName + " " + user.LastName)) + entry.Contributors[contributorID].URI = c.Domain + "/image/serve/" + user.ProfilePicLink + } + tags, err := db.GetArticleTags(article.ID) if err != nil { return nil, fmt.Errorf("error getting tags for articles for Atom feed: %v", err) diff --git a/cmd/backend/users.go b/cmd/backend/users.go index a9f893e..47d76e4 100644 --- a/cmd/backend/users.go +++ b/cmd/backend/users.go @@ -448,7 +448,46 @@ func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) { return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) } -func (db *DB) GetAllUsers(c *Config) (map[int64]*User, error) { +func (db *DB) GetAllUsers(c *Config) ([]*User, error) { + var aesFirstName, aesLastName, aesEmail string + var err error + + query := "SELECT id, username, first_name, last_name, email, profile_pic_link, role FROM users" + + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("error getting all users from DB: %v", err) + } + + users := make([]*User, 0) + for rows.Next() { + user := new(User) + if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.ProfilePicLink, &user.Role); err != nil { + return nil, fmt.Errorf("error getting user info: %v", err) + } + + user.FirstName, err = aesDecrypt(c, aesFirstName) + if err != nil { + return nil, fmt.Errorf("error decrypting first name: %v", err) + } + + user.LastName, err = aesDecrypt(c, aesLastName) + if err != nil { + return nil, fmt.Errorf("error decrypting last name: %v", err) + } + + user.Email, err = aesDecrypt(c, aesEmail) + if err != nil { + return nil, fmt.Errorf("error decrypting email: %v", err) + } + + users = append(users, user) + } + + return users, nil +} + +func (db *DB) GetAllUsersMap(c *Config) (map[int64]*User, error) { var aesFirstName, aesLastName, aesEmail string var err error diff --git a/cmd/frontend/articles.go b/cmd/frontend/articles.go index 019fcc6..be873e4 100644 --- a/cmd/frontend/articles.go +++ b/cmd/frontend/articles.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" b "streifling.com/jason/cpolis/cmd/backend" @@ -17,6 +18,17 @@ const ( PreviewMode ) +const ( + None = iota + Author + Contributor +) + +type ArticleUser struct { + *b.User + ArticleRole int +} + type EditorHTMLData struct { Selected map[int64]bool Content string @@ -27,6 +39,10 @@ type EditorHTMLData struct { HTMLContent template.HTML Article *b.Article Tags []*b.Tag + ArticleUsers map[string]*ArticleUser // A map is way more efficient in ReviewRejectedArticle() + Creator *ArticleUser + Authors []*b.User + Contributors []*b.User } func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { @@ -40,11 +56,30 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { var data *EditorHTMLData if session.Values["article"] == nil { - data = &EditorHTMLData{Action: "submit", Article: new(b.Article)} + data = &EditorHTMLData{Action: "submit", Article: new(b.Article), ArticleUsers: make(map[string]*ArticleUser)} } else { data = session.Values["article"].(*EditorHTMLData) } + users, err := db.GetAllUsers(c) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for _, user := range users { + data.ArticleUsers[fmt.Sprint(user.LastName, user.FirstName, user.ID)] = &ArticleUser{User: user, ArticleRole: None} + } + + creator, err := db.GetUser(c, session.Values["id"].(int64)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Creator = data.ArticleUsers[fmt.Sprint(creator.LastName, creator.FirstName, creator.ID)] + delete(data.ArticleUsers, fmt.Sprint(creator.LastName, creator.FirstName, creator.ID)) + data.Tags, err = db.GetTagList() if err != nil { log.Println(err) @@ -81,9 +116,9 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { Title: r.PostFormValue("article-title"), BannerLink: r.PostFormValue("article-banner-url"), Summary: r.PostFormValue("article-summary"), + CreatorID: session.Values["id"].(int64), Published: false, Rejected: false, - AuthorID: session.Values["id"].(int64), IsInIssue: r.PostFormValue("issue") == "on", AutoGenerated: false, } @@ -97,6 +132,38 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + r.ParseForm() + authors := make([]int64, 0) + contributors := make([]int64, 0) + + for key, values := range r.Form { + if strings.HasPrefix(key, "user-") && len(values) > 0 { + id, err := strconv.ParseInt(strings.Split(key, "-")[1], 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + switch values[0] { + case "author": + authors = append(authors, id) + case "contributor": + contributors = append(contributors, id) + } + } + } + + if r.PostFormValue("creator") == "contributor" { + contributors = append(contributors, article.CreatorID) + } else { + authors = append(authors, article.CreatorID) + } + if len(authors) == 0 { + http.Error(w, "Es muss mindestens einen Autor geben.", http.StatusBadRequest) + return + } + article.ID, err = db.AddArticle(article) if err != nil { log.Println(err) @@ -109,23 +176,34 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest) return } - - articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") - if err = os.WriteFile(articleAbsName, content, 0644); err != nil { + if err := b.WriteArticleToFile(c, article.ID, content); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - r.ParseForm() + if err = db.WriteArticleAuthors(article.ID, authors); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(contributors) > 0 { + if err = db.WriteArticleContributors(article.ID, contributors); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + 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) + http.Error(w, err.Error(), http.StatusBadRequest) return } + tags = append(tags, tagID) } if err = db.WriteArticleTags(article.ID, tags); err != nil { @@ -156,32 +234,68 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + article := &b.Article{ + Title: r.PostFormValue("article-title"), + BannerLink: r.PostFormValue("article-banner-url"), + Summary: r.PostFormValue("article-summary"), + CreatorID: session.Values["id"].(int64), + IsInIssue: r.PostFormValue("issue") == "on", } - title := r.PostFormValue("article-title") - if len(title) == 0 { + if len(article.Title) == 0 { http.Error(w, "Bitte den Titel eingeben.", http.StatusBadRequest) return } - - summary := r.PostFormValue("article-summary") - if len(summary) == 0 { + if len(article.Summary) == 0 { http.Error(w, "Bitte die Beschreibung eingeben.", http.StatusBadRequest) return } + r.ParseForm() + authors := make([]int64, 0) + contributors := make([]int64, 0) + + for key, values := range r.Form { + if strings.HasPrefix(key, "user-") && len(values) > 0 { + id, err := strconv.ParseInt(strings.Split(key, "-")[1], 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + switch values[0] { + case "author": + authors = append(authors, id) + case "contributor": + contributors = append(contributors, id) + } + } + } + + if r.PostFormValue("creator") == "contributor" { + contributors = append(contributors, article.CreatorID) + } else { + authors = append(authors, article.CreatorID) + } + if len(authors) == 0 { + http.Error(w, "Es muss mindestens einen Autor geben.", http.StatusBadRequest) + return + } + + article.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + content := r.PostFormValue("article-content") if len(content) == 0 { http.Error(w, "Bitte den Artikel eingeben.", http.StatusBadRequest) return } - - contentLink := fmt.Sprint(c.ArticleDir, "/", id, ".md") + contentLink := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") if err = os.WriteFile(contentLink, []byte(content), 0644); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -189,18 +303,30 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } if err = db.UpdateAttributes( - &b.Attribute{Table: "articles", ID: id, AttName: "title", Value: title}, - &b.Attribute{Table: "articles", ID: id, AttName: "banner_link", Value: r.PostFormValue("article-banner-url")}, - &b.Attribute{Table: "articles", ID: id, AttName: "summary", Value: summary}, - &b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false}, - &b.Attribute{Table: "articles", ID: id, AttName: "is_in_issue", Value: r.PostFormValue("issue") == "on"}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "title", Value: article.Title}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "banner_link", Value: article.BannerLink}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "summary", Value: article.Summary}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "rejected", Value: false}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "is_in_issue", Value: article.IsInIssue}, ); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - r.ParseForm() + if err = db.UpdateArticleAuthors(article.ID, authors); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(contributors) > 0 { + if err = db.UpdateArticleContributors(article.ID, contributors); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + tags := make([]int64, 0) for _, tag := range r.Form["tags"] { tagID, err := strconv.ParseInt(tag, 10, 64) @@ -211,7 +337,7 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } tags = append(tags, tagID) } - if err = db.UpdateArticleTags(id, tags); err != nil { + if err = db.UpdateArticleTags(article.ID, tags); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -297,7 +423,7 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF data.MyIDs = make(map[int64]bool) for _, article := range data.RejectedArticles { - if article.AuthorID == session.Values["id"].(int64) { + if article.CreatorID == session.Values["id"].(int64) { data.MyIDs[article.ID] = true } } @@ -314,7 +440,8 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if _, err := GetSession(w, r, c, s); err != nil { + session, err := GetSession(w, r, c, s) + if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -353,6 +480,46 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler return } + data.ArticleUsers = make(map[string]*ArticleUser) + users, err := db.GetAllUsers(c) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for _, user := range users { + data.ArticleUsers[fmt.Sprint(user.LastName, user.FirstName, user.ID)] = &ArticleUser{User: user, ArticleRole: None} + } + + authors, err := db.GetArticleAuthors(c, data.Article.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for _, author := range authors { + data.ArticleUsers[fmt.Sprint(author.LastName, author.FirstName, author.ID)].ArticleRole = Author + } + + contributors, err := db.GetArticleContributors(c, data.Article.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for _, contributor := range contributors { + data.ArticleUsers[fmt.Sprint(contributor.LastName, contributor.FirstName, contributor.ID)].ArticleRole = Contributor + } + + creator, err := db.GetUser(c, session.Values["id"].(int64)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Creator = data.ArticleUsers[fmt.Sprint(creator.LastName, creator.FirstName, creator.ID)] + delete(data.ArticleUsers, fmt.Sprint(creator.LastName, creator.FirstName, creator.ID)) + selectedTags, err := db.GetArticleTags(id) if err != nil { log.Println(err) @@ -406,9 +573,9 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } if err = db.UpdateAttributes( - &b.Attribute{Table: "articles", ID: id, AttName: "published", Value: true}, - &b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false}, - &b.Attribute{Table: "articles", ID: id, AttName: "created", Value: time.Now().Format("2006-01-02 15:04:05")}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "published", Value: true}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "rejected", Value: false}, + &b.Attribute{Table: "articles", ID: article.ID, AttName: "created", Value: time.Now().Format("2006-01-02 15:04:05")}, ); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -423,6 +590,18 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + if err = db.DeleteArticleContributors(oldArticle.ID); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = db.DeleteArticleAuthors(oldArticle.ID); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err = db.DeleteArticle(oldArticle.ID); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -435,10 +614,7 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - if err = db.UpdateAttributes( - &b.Attribute{Table: "articles", ID: id, AttName: "content_link", Value: fmt.Sprint(c.Domain, "/article/serve/", article.ID)}, - &b.Attribute{Table: "articles", ID: id, AttName: "edited_id", Value: 0}, - ); err != nil { + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: article.ID, AttName: "edited_id", Value: 0}); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -629,6 +805,22 @@ func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, butto } data.HTMLContent = template.HTML(data.Content) + data.Authors, err = db.GetArticleAuthors(c, data.Article.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sortUsersByName(data.Authors) + + data.Contributors, err = db.GetArticleContributors(c, data.Article.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sortUsersByName(data.Contributors) + data.Tags, err = db.GetArticleTags(id) if err != nil { log.Println(err) @@ -721,25 +913,59 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc return } - newArticle := oldArticle + newArticle := *oldArticle newArticle.Published = false newArticle.Rejected = true newArticle.EditedID = oldArticle.ID - newID, err := db.AddArticle(newArticle) + newArticle.ID, err = db.AddArticle(&newArticle) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: oldID, AttName: "edited_id", Value: newID}); err != nil { + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: oldArticle.ID, AttName: "edited_id", Value: newArticle.ID}); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - if err = b.CopyFile(fmt.Sprint(c.ArticleDir, "/", oldID, ".md"), fmt.Sprint(c.ArticleDir, "/", newID, ".md")); err != nil { + src := fmt.Sprint(c.ArticleDir, "/", oldArticle.ID, ".md") + dst := fmt.Sprint(c.ArticleDir, "/", newArticle.ID, ".md") + if err = b.CopyFile(src, dst); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + authors, err := db.GetArticleAuthors(c, oldArticle.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + authorIDs := make([]int64, len(authors)) + for i, author := range authors { + authorIDs[i] = author.ID + } + if err = db.WriteArticleAuthors(newArticle.ID, authorIDs); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + contributors, err := db.GetArticleContributors(c, oldArticle.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + contributorIDs := make([]int64, len(contributors)) + for i, contributor := range contributors { + contributorIDs[i] = contributor.ID + } + if err = db.WriteArticleContributors(newArticle.ID, contributorIDs); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/cmd/frontend/users.go b/cmd/frontend/users.go index be3c978..39447a5 100644 --- a/cmd/frontend/users.go +++ b/cmd/frontend/users.go @@ -5,6 +5,7 @@ import ( "html/template" "log" "net/http" + "sort" "strconv" b "streifling.com/jason/cpolis/cmd/backend" @@ -33,6 +34,15 @@ func checkUserStrings(user *b.User) (string, int, bool) { } } +func sortUsersByName(users []*b.User) { + sort.SliceStable(users, func(i, j int) bool { + if users[i].LastName == users[j].LastName { + return users[i].FirstName < users[j].FirstName + } + return users[i].LastName < users[j].LastName + }) +} + func CreateUser(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := GetSession(w, r, c, s); err != nil { @@ -332,14 +342,14 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H }) data.Action = action - data.Users, err = db.GetAllUsers(c) + data.Users, err = db.GetAllUsersMap(c) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - delete(data.Users, session.Values["id"].(int64)) + tmpl, err := template.ParseFiles(c.WebDir + "/templates/show-all-users.html") if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil { log.Println(err) diff --git a/create_db.sql b/create_db.sql index 2fc4dfa..6f136ed 100644 --- a/create_db.sql +++ b/create_db.sql @@ -1,4 +1,6 @@ DROP TABLE IF EXISTS articles_tags; +DROP TABLE IF EXISTS articles_contributors; +DROP TABLE IF EXISTS articles_authors; DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS articles; DROP TABLE IF EXISTS issues; @@ -30,12 +32,13 @@ CREATE TABLE articles ( summary TEXT NOT NULL, published BOOL NOT NULL, rejected BOOL NOT NULL, + creator_id INT NOT NULL, issue_id INT NOT NULL, edited_id INT, is_in_issue BOOL NOT NULL, auto_generated BOOL NOT NULL, PRIMARY KEY (id), - FOREIGN KEY (author_id) REFERENCES users (id), + FOREIGN KEY (creator_id) REFERENCES users (id), FOREIGN KEY (issue_id) REFERENCES issues (id) ); diff --git a/web/templates/editor.html b/web/templates/editor.html index b619163..81cd163 100644 --- a/web/templates/editor.html +++ b/web/templates/editor.html @@ -7,7 +7,7 @@
- +

Titel

@@ -20,12 +20,18 @@
- +

Beschreibung

+
+

Artikel

+ + +
+
- Tags +

Tags

@@ -42,10 +48,36 @@
-
- - - +
+

Beteiligte

+ {{range .ArticleUsers}} +
+ {{.FirstName}} {{.LastName}}: + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ {{end}} +
+ +
+ +
diff --git a/web/templates/review-article.html b/web/templates/review-article.html index 973f4d9..2c225e7 100644 --- a/web/templates/review-article.html +++ b/web/templates/review-article.html @@ -6,24 +6,24 @@ Banner Image
- Titel +

Titel

{{.Article.Title}}
- Beschreibung +

Beschreibung

{{.Article.Summary}}
- Artikel +

Artikel

{{.HTMLContent}}
- Tags +

Tags

{{if .Article.IsInIssue}} Orient Express @@ -35,6 +35,22 @@ {{end}}
+

Autoren

+
+ {{range .Authors}} + {{.FirstName}} {{.LastName}} +
+ {{end}} +
+ +

Mitwirkende

+
+ {{range .Contributors}} + {{.FirstName}} {{.LastName}} +
+ {{end}} +
+ {{if eq .Action "publish"}}