From 38ef7b80d5fa301c620e3ad8f2819185b51c5f3e Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Fri, 13 Sep 2024 05:12:57 +0200 Subject: [PATCH 1/4] Cleanup --- cmd/backend/articles.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/backend/articles.go b/cmd/backend/articles.go index dcc15ea..8f947c7 100644 --- a/cmd/backend/articles.go +++ b/cmd/backend/articles.go @@ -269,14 +269,13 @@ func (db *DB) AddArticleToCurrentIssue(id int64) error { func (db *DB) DeleteArticle(id int64) error { articlesTagsQuery := "DELETE FROM articles_tags WHERE article_id = ?" + articlesQuery := "DELETE FROM articles WHERE id = ?" _, err := db.Exec(articlesTagsQuery, id) if err != nil { return fmt.Errorf("error deleting article %v from DB: %v", id, err) } - articlesQuery := "DELETE FROM articles WHERE id = ?" - _, err = db.Exec(articlesQuery, id) if err != nil { return fmt.Errorf("error deleting article %v from DB: %v", id, err) From 065ffcdc30f4876c8be4de6407961a4378711881 Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Sat, 28 Sep 2024 12:17:03 +0200 Subject: [PATCH 2/4] Allow articles to be edited --- cmd/backend/articles.go | 38 +- cmd/backend/rss.go | 2 +- cmd/frontend/articles.go | 463 +++++++++++++----- cmd/main.go | 13 +- create_db.sql | 1 + web/templates/editor.html | 16 +- web/templates/hub.html | 29 +- web/templates/published-articles.html | 5 +- web/templates/rejected-articles.html | 2 +- ...-be-published.html => review-article.html} | 18 +- web/templates/rework-article.html | 76 --- web/templates/to-be-deleted.html | 36 -- web/templates/unpublished-articles.html | 2 +- 13 files changed, 412 insertions(+), 289 deletions(-) rename web/templates/{to-be-published.html => review-article.html} (65%) delete mode 100644 web/templates/rework-article.html delete mode 100644 web/templates/to-be-deleted.html diff --git a/cmd/backend/articles.go b/cmd/backend/articles.go index 8f947c7..07f7ee0 100644 --- a/cmd/backend/articles.go +++ b/cmd/backend/articles.go @@ -18,6 +18,7 @@ type Article struct { ID int64 AuthorID int64 IssueID int64 + EditedID int64 EncLength int Published bool Rejected bool @@ -31,8 +32,9 @@ func (db *DB) AddArticle(a *Article) (int64, error) { selectQuery := "SELECT id FROM issues WHERE published = false" insertQuery := ` INSERT INTO articles - (title, description, link, enc_url, enc_length, enc_type, published, rejected, author_id, issue_id, is_in_issue, auto_generated) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (title, description, link, enc_url, enc_length, enc_type, published, + rejected, author_id, issue_id, edited_id, is_in_issue, auto_generated) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` for i := 0; i < TxMaxRetries; i++ { @@ -51,7 +53,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) { result, err := tx.Exec(insertQuery, a.Title, a.Description, a.Link, a.EncURL, a.EncLength, a.EncType, a.Published, a.Rejected, a.AuthorID, id, - a.IsInIssue, a.AutoGenerated) + 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) @@ -85,7 +87,9 @@ func (db *DB) AddArticle(a *Article) (int64, error) { func (db *DB) GetArticle(id int64) (*Article, error) { query := ` - SELECT title, created, description, link, enc_url, enc_length, enc_type, published, author_id, issue_id, is_in_issue, auto_generated + SELECT + title, created, description, link, enc_url, enc_length, enc_type, + published, author_id, issue_id, edited_id, is_in_issue, auto_generated FROM articles WHERE id = ? ` @@ -97,7 +101,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) { if err := row.Scan(&article.Title, &created, &article.Description, &article.Link, &article.EncURL, &article.EncLength, &article.EncType, - &article.Published, &article.AuthorID, &article.IssueID, + &article.Published, &article.AuthorID, &article.IssueID, &article.EditedID, &article.IsInIssue, &article.AutoGenerated); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } @@ -111,14 +115,15 @@ func (db *DB) GetArticle(id int64) (*Article, error) { return article, nil } -func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) { - query := ` - SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, issue_id, is_in_issue, auto_generated +func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, error) { + query := fmt.Sprintf(` + SELECT + id, title, created, description, link, enc_url, enc_length, enc_type, + author_id, issue_id, published, rejected, is_in_issue, auto_generated FROM articles - WHERE published = ? - AND rejected = ? - ` - rows, err := db.Query(query, published, rejected) + WHERE %s = ? + `, attribute) + rows, err := db.Query(query, value) if err != nil { return nil, fmt.Errorf("error querying articles: %v", err) } @@ -130,12 +135,11 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) { if err = rows.Scan(&article.ID, &article.Title, &created, &article.Description, &article.Link, &article.EncURL, &article.EncLength, - &article.EncType, &article.AuthorID, &article.IssueID, - &article.IsInIssue, &article.AutoGenerated); err != nil { + &article.EncType, &article.AuthorID, &article.IssueID, &article.Published, + &article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } - article.Published = false article.Created, err = time.Parse("2006-01-02 15:04:05", string(created)) if err != nil { return nil, fmt.Errorf("error parsing created: %v", err) @@ -152,7 +156,9 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) { txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} issueQuery := "SELECT id FROM issues WHERE published = false" articlesQuery := ` - SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, auto_generated + SELECT + id, title, created, description, link, enc_url, enc_length, enc_type, + author_id, auto_generated FROM articles WHERE issue_id = ? AND published = true AND is_in_issue = true ` diff --git a/cmd/backend/rss.go b/cmd/backend/rss.go index b03ead5..c33ad8d 100644 --- a/cmd/backend/rss.go +++ b/cmd/backend/rss.go @@ -17,7 +17,7 @@ func GenerateRSS(c *Config, db *DB) (*string, error) { Items: make([]*rss.Item, 0), } - articles, err := db.GetCertainArticles(true, false) + articles, err := db.GetCertainArticles("published", true) if err != nil { return nil, fmt.Errorf("error getting published articles for RSS feed: %v", err) } diff --git a/cmd/frontend/articles.go b/cmd/frontend/articles.go index d90baa0..525675c 100644 --- a/cmd/frontend/articles.go +++ b/cmd/frontend/articles.go @@ -22,6 +22,38 @@ const ( PreviewMode ) +type EditorHTMLData struct { + Selected map[int64]bool + Content string + Action string + ActionTitle string + ActionButton string + HTMLContent template.HTML + Article *b.Article + Tags []*b.Tag +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("error opening source file: %v", err) + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("error opening destination file: %v", err) + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return fmt.Errorf("error copying file: %v", err) + } + + return dstFile.Sync() +} + func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { session, err := getSession(w, r, c, s) @@ -29,22 +61,14 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - type htmlData struct { - Title string - Description string - Content string - HTMLContent template.HTML - Tags []*b.Tag - Mode int - } - var data htmlData + data := new(EditorHTMLData) if session.Values["article"] == nil { - data = htmlData{} + data = &EditorHTMLData{Article: new(b.Article)} } else { - data = session.Values["article"].(htmlData) + data = session.Values["article"].(*EditorHTMLData) } - data.Mode = EditMode + // data.Mode = EditMode data.Tags, err = db.GetTagList() if err != nil { @@ -53,6 +77,8 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + data.Action = "submit" + tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data) } @@ -185,22 +211,41 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } } -func ShowUnpublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { +func ShowUnpublishedUnrejectedAndPublishedRejectedArticles(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 { return } - unpublishedArticles, err := db.GetCertainArticles(false, false) + rejectedArticles, err := db.GetCertainArticles("rejected", true) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } + articles := make([]*b.Article, 0) + for _, article := range rejectedArticles { + if article.Published { + articles = append(articles, article) + } + } + + unpublishedArticles, err := db.GetCertainArticles("published", false) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + for _, article := range unpublishedArticles { + if !article.Rejected { + articles = append(articles, article) + } + } + tmpl, err := template.ParseFiles(c.WebDir + "/templates/unpublished-articles.html") - tmpl = template.Must(tmpl, err) - tmpl.ExecuteTemplate(w, "page-content", unpublishedArticles) + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", articles) } } @@ -216,7 +261,7 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF RejectedArticles []*b.Article }) - data.RejectedArticles, err = db.GetCertainArticles(false, true) + data.RejectedArticles, err = db.GetCertainArticles("rejected", true) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -236,74 +281,12 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF } } -func ReviewUnpublishedArticle(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 { - return - } - - data := new(struct { - Article *b.Article - Content template.HTML - Tags []*b.Tag - }) - - id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - data.Article, err = db.GetArticle(id) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md") - contentBytes, err := os.ReadFile(articleAbsName) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - content, err := b.ConvertToHTML(string(contentBytes)) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - data.Content = template.HTML(content) - - data.Tags, err = db.GetArticleTags(data.Article.ID) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tmpl, err := template.ParseFiles(c.WebDir + "/templates/to-be-published.html") - tmpl = template.Must(tmpl, err) - tmpl.ExecuteTemplate(w, "page-content", data) - } -} - 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 { return } - data := new(struct { - Selected map[int64]bool - Article *b.Article - Content string - Tags []*b.Tag - }) - id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -311,6 +294,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler return } + data := new(EditorHTMLData) data.Article, err = db.GetArticle(id) if err != nil { log.Println(err) @@ -319,14 +303,13 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler } articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md") - contentBytes, err := os.ReadFile(articleAbsName) + content, err := os.ReadFile(articleAbsName) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - - data.Content = string(contentBytes) + data.Content = string(content) data.Tags, err = db.GetTagList() if err != nil { @@ -346,7 +329,9 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler data.Selected[tag.ID] = true } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/rework-article.html") + data.Action = fmt.Sprint("resubmit/", data.Article.ID) + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", data) } @@ -389,6 +374,36 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + if article.EditedID != 0 { + oldArticle, err := db.GetArticle(article.EditedID) + if 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) + return + } + + if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", oldArticle.ID, ".md")); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = db.UpdateAttributes( + &b.Attribute{Table: "articles", ID: id, AttName: "link", Value: fmt.Sprint(c.Domain, "/article/serve/", article.ID)}, + &b.Attribute{Table: "articles", ID: id, AttName: "edited_id", Value: 0}, + ); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + feed, err := b.GenerateRSS(c, db) if err != nil { log.Println(err) @@ -421,9 +436,7 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - if err = db.UpdateAttributes( - &b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: true}, - ); err != nil { + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: true}); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -497,69 +510,75 @@ func UploadArticleImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { } } -func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { +func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := getSession(w, r, c, s); err != nil { return } - publishedArticles, err := db.GetCertainArticles(true, false) + data := new(struct { + Action string + Articles []*b.Article + }) + data.Action = action + + publishedArticles, err := db.GetCertainArticles("published", true) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - filteredArticles := make([]*b.Article, 0) for _, article := range publishedArticles { if !article.AutoGenerated { - filteredArticles = append(filteredArticles, article) + data.Articles = append(data.Articles, article) } } tmpl, err := template.ParseFiles(c.WebDir + "/templates/published-articles.html") tmpl = template.Must(tmpl, err) - tmpl.ExecuteTemplate(w, "page-content", filteredArticles) + tmpl.ExecuteTemplate(w, "page-content", data) } } -func ReviewArticleForDeletion(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { +func ReviewArticle(c *b.Config, db *b.DB, s *b.CookieStore, action, title, button string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := getSession(w, r, c, s); err != nil { return } - var err error - data := new(struct { - Title string - Description string - Content template.HTML - Tags []*b.Tag - ID int64 - }) - - data.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64) + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - article, err := db.GetArticle(data.ID) + article, err := db.GetArticle(id) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - data.Title, err = b.ConvertToPlain(article.Title) + data := &EditorHTMLData{ + Article: &b.Article{ + ID: id, + IsInIssue: article.IsInIssue, + }, + Action: action, + ActionTitle: title, + ActionButton: button, + } + + data.Article.Title, err = b.ConvertToPlain(article.Title) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - data.Description, err = b.ConvertToPlain(article.Description) + data.Article.Description, err = b.ConvertToPlain(article.Description) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -567,31 +586,29 @@ func ReviewArticleForDeletion(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand } articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") - contentBytes, err := os.ReadFile(articleAbsName) + content, err := os.ReadFile(articleAbsName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Content, err = b.ConvertToHTML(string(content)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.HTMLContent = template.HTML(data.Content) + + data.Tags, err = db.GetArticleTags(id) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - content, err := b.ConvertToHTML(string(contentBytes)) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - data.Content = template.HTML(content) - - data.Tags, err = db.GetArticleTags(data.ID) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tmpl, err := template.ParseFiles(c.WebDir + "/templates/to-be-deleted.html") - tmpl = template.Must(tmpl, err) - tmpl.ExecuteTemplate(w, "page-content", data) + tmpl, err := template.ParseFiles(c.WebDir + "/templates/review-article.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data) } } @@ -638,3 +655,201 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)) } } + +func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + + oldID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Println(oldID) + + oldArticle, err := db.GetArticle(oldID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + newArticle := oldArticle + newArticle.Published = false + newArticle.Rejected = true + newArticle.EditedID = oldArticle.ID + + newID, 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 { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = copyFile(fmt.Sprint(c.ArticleDir, "/", oldID, ".md"), fmt.Sprint(c.ArticleDir, "/", newID, ".md")); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)) + } +} + +func EditArticle(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 { + return + } + + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data := new(EditorHTMLData) + + data.Article, err = db.GetArticle(id) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + content, err := os.ReadFile(fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md")) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Content = string(content) + + data.Tags, err = db.GetArticleTags(data.Article.ID) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + selectedTags, err := db.GetArticleTags(id) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + data.Selected = make(map[int64]bool) + for _, tag := range selectedTags { + data.Selected[tag.ID] = true + } + + data.Action = fmt.Sprint("save/", data.Article.ID) + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data) + } +} + +func SaveArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + + session.Values["article"] = nil + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + article := &b.Article{ + Title: r.PostFormValue("article-title"), + Description: r.PostFormValue("article-description"), + Published: false, + Rejected: false, + AuthorID: session.Values["id"].(int64), + IsInIssue: r.PostFormValue("issue") == "on", + AutoGenerated: false, + } + + oldID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Println(oldID) + if oldID != 0 { + if err = db.DeleteArticle(oldID); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = os.Remove(fmt.Sprint(c.ArticleDir, "/", oldID, ".md")); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + } + + article.ID, err = db.AddArticle(article) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") + if err = os.WriteFile(articleAbsName, []byte(r.PostFormValue("article-content")), 0644); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + article.Link = fmt.Sprint(c.Domain, "/article/serve/", article.ID) + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: article.ID, AttName: "link", Value: article.Link}); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + r.ParseForm() + tags := make([]int64, 0) + for _, tag := range r.Form["tags"] { + tagID, err := strconv.ParseInt(tag, 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + tags = append(tags, tagID) + } + if err = db.WriteArticleTags(article.ID, tags); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) + } +} diff --git a/cmd/main.go b/cmd/main.go index acff9f2..81cd90c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -49,15 +49,20 @@ func main() { http.FileServer(http.Dir(config.WebDir+"/static/")))) mux.HandleFunc("/", f.HomePage(config, db, store)) - mux.HandleFunc("GET /article/all-published", f.ShowPublishedArticles(config, db, store)) + mux.HandleFunc("GET /article/allow-edit/{id}", f.AllowEditArticle(config, db, store)) + mux.HandleFunc("GET /article/all-published/review-edit", f.ShowPublishedArticles(config, db, store, "review-edit")) + mux.HandleFunc("GET /article/all-published/delete", f.ShowPublishedArticles(config, db, store, "review-delete")) mux.HandleFunc("GET /article/all-rejected", f.ShowRejectedArticles(config, db, store)) - mux.HandleFunc("GET /article/all-unpublished", f.ShowUnpublishedArticles(config, db, store)) + mux.HandleFunc("GET /article/all-unpublished-unrejected-and-published-rejected", f.ShowUnpublishedUnrejectedAndPublishedRejectedArticles(config, db, store)) mux.HandleFunc("GET /article/delete/{id}", f.DeleteArticle(config, db, store)) + mux.HandleFunc("GET /article/edit/{id}", f.EditArticle(config, db, store)) mux.HandleFunc("GET /article/publish/{id}", f.PublishArticle(config, db, store)) mux.HandleFunc("GET /article/reject/{id}", f.RejectArticle(config, db, store)) - mux.HandleFunc("GET /article/review-deletion/{id}", f.ReviewArticleForDeletion(config, db, store)) + mux.HandleFunc("GET /article/review-delete/{id}", f.ReviewArticle(config, db, store, "delete", "Artikel löschen", "Löschen")) + mux.HandleFunc("GET /article/review-edit/{id}", f.ReviewArticle(config, db, store, "allow-edit", "Artikel bearbeiten", "Bearbeiten erlauben")) mux.HandleFunc("GET /article/review-rejected/{id}", f.ReviewRejectedArticle(config, db, store)) - mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewUnpublishedArticle(config, db, store)) + mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewArticle(config, db, store, "publish", "Artikel veröffentlichen", "Veröffentlichen")) + mux.HandleFunc("GET /article/save/{id}", f.SaveArticle(config, db, store)) mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db)) mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) diff --git a/create_db.sql b/create_db.sql index 5cde280..54f8129 100644 --- a/create_db.sql +++ b/create_db.sql @@ -33,6 +33,7 @@ CREATE TABLE articles ( rejected BOOL NOT NULL, author_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), diff --git a/web/templates/editor.html b/web/templates/editor.html index 1db7de4..ca433cc 100644 --- a/web/templates/editor.html +++ b/web/templates/editor.html @@ -4,39 +4,41 @@
- +
- +
- +
Tags
- +
{{range .Tags}}
- - + +
{{end}}
- +
diff --git a/web/templates/hub.html b/web/templates/hub.html index 43f9e05..eb8999d 100644 --- a/web/templates/hub.html +++ b/web/templates/hub.html @@ -7,13 +7,13 @@

Artikel

- - {{if lt . 3}}{{end}} - {{if lt . 2}}{{end}} + + {{if lt . 3}}{{end}} + {{if lt . 2}}{{end}} + {{if lt . 2}}{{end}} {{if lt . 3}}{{end}}
@@ -38,15 +38,12 @@

Benutzer

- {{if eq . 0}}{{end}} - {{if eq . 0}}{{end}} - {{if eq . 0}}{{end}} + {{if eq . 0}}{{end}} + {{if eq . 0}}{{end}} + {{if eq . 0}}{{end}}
{{end}} diff --git a/web/templates/published-articles.html b/web/templates/published-articles.html index 4a12863..00d9c3f 100644 --- a/web/templates/published-articles.html +++ b/web/templates/published-articles.html @@ -2,12 +2,13 @@

Artikel löschen

- {{range .}} - {{end}} +
{{end}} diff --git a/web/templates/rejected-articles.html b/web/templates/rejected-articles.html index fab22e5..54f2571 100644 --- a/web/templates/rejected-articles.html +++ b/web/templates/rejected-articles.html @@ -1,5 +1,5 @@ {{define "page-content"}} -

Abgelehnte Artikel

+

Artikel bearbeiten

{{range .RejectedArticles}} diff --git a/web/templates/to-be-published.html b/web/templates/review-article.html similarity index 65% rename from web/templates/to-be-published.html rename to web/templates/review-article.html index ea047e1..6d85855 100644 --- a/web/templates/to-be-published.html +++ b/web/templates/review-article.html @@ -1,5 +1,5 @@ {{define "page-content"}} -

Artikel veröffentlichen

+

{{.ActionTitle}}

Titel @@ -14,8 +14,8 @@ Artikel
-
- {{.Content}} +
+ {{.HTMLContent}}
@@ -31,12 +31,20 @@ {{end}}
+ {{if eq .Action "publish"}}
- - +
+ {{else}} +
+ + +
+ {{end}}
{{end}} diff --git a/web/templates/rework-article.html b/web/templates/rework-article.html deleted file mode 100644 index 4dad35f..0000000 --- a/web/templates/rework-article.html +++ /dev/null @@ -1,76 +0,0 @@ -{{define "page-content"}} -

Editor

- -
-
- - -
- -
- - -
- -
- - - -
- -
- Tags -
-
- - -
- - {{range .Tags}} -
- - -
- {{end}} -
-
- -
- - -
-
- - -{{end}} diff --git a/web/templates/to-be-deleted.html b/web/templates/to-be-deleted.html deleted file mode 100644 index 61d07f4..0000000 --- a/web/templates/to-be-deleted.html +++ /dev/null @@ -1,36 +0,0 @@ -{{define "page-content"}} -

Artikel löschen

- -
- Titel -
- {{.Title}} -
- - Beschreibung -
- {{.Description}} -
- - Artikel -
-
- {{.Content}} -
-
- - Tags -
- {{range .Tags}} - {{.Name}} -
- {{end}} -
- -
- - -
-
-{{end}} diff --git a/web/templates/unpublished-articles.html b/web/templates/unpublished-articles.html index 9013e3d..52ae8c6 100644 --- a/web/templates/unpublished-articles.html +++ b/web/templates/unpublished-articles.html @@ -1,5 +1,5 @@ {{define "page-content"}} -

Unveröffentlichte Artikel

+

Artikel veröffentlichen

{{range .}} From 2743899b65aad9374b1be103b70daba3354edd62 Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Sat, 28 Sep 2024 12:19:18 +0200 Subject: [PATCH 3/4] Changed version number and disclaimer --- web/templates/index.html | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/web/templates/index.html b/web/templates/index.html index 0895b65..9842930 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -34,13 +34,8 @@
-

- © 2024 Jason Streifling. Alle Rechte vorbehalten. -

-

- v0.10.3 - Hinweis: Diese Software befindet sich noch in der Entwicklung und kann Fehler - enthalten. -

+

© 2024 Jason Streifling. Alle Rechte vorbehalten.

+

v0.11.0 - Alpha: Änderungen und Fehler möglich.

From 4bd255a7c4443ca7007c1fe45e70b1b5a757ee8f Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Sat, 28 Sep 2024 12:34:15 +0200 Subject: [PATCH 4/4] Strictly require title for issue --- cmd/frontend/issues.go | 17 +++++++++++------ web/templates/current-articles.html | 2 +- web/templates/index.html | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/frontend/issues.go b/cmd/frontend/issues.go index 10eefce..a17b625 100644 --- a/cmd/frontend/issues.go +++ b/cmd/frontend/issues.go @@ -30,6 +30,14 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun return } + title := r.PostFormValue("issue-title") + if len(title) == 0 { + err = fmt.Errorf("error: no title for issue specified") + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if session.Values["issue-image"] == nil { err := "error: Image required" log.Println(err) @@ -55,14 +63,11 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun return } - imgSize := imgInfo.Size() - mimeType := mime.TypeByExtension(filepath.Ext(imgAbsName)) - article := &b.Article{ - Title: r.PostFormValue("issue-title"), + Title: title, EncURL: fmt.Sprint(c.Domain, "/image/serve/", imgFileName), - EncLength: int(imgSize), - EncType: mimeType, + EncLength: int(imgInfo.Size()), + EncType: mime.TypeByExtension(filepath.Ext(imgAbsName)), Published: true, Rejected: false, Created: time.Now(), diff --git a/web/templates/current-articles.html b/web/templates/current-articles.html index 177bae4..4e9ebdb 100644 --- a/web/templates/current-articles.html +++ b/web/templates/current-articles.html @@ -18,7 +18,7 @@

Titelseite

- + diff --git a/web/templates/index.html b/web/templates/index.html index 9842930..1afc3c4 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -35,7 +35,7 @@

© 2024 Jason Streifling. Alle Rechte vorbehalten.

-

v0.11.0 - Alpha: Änderungen und Fehler möglich.

+

v0.11.0 - Alpha: Drastische Änderungen und Fehler vorbehalten.