From 582f25bec7832c7cafb7dc1a704e93bfc1338feb Mon Sep 17 00:00:00 2001 From: Jason Streifling Date: Wed, 6 Mar 2024 20:53:17 +0100 Subject: [PATCH] Converted articles and tags to DB base --- cmd/data/articles.go | 206 ++----------------------------------------- cmd/data/db.go | 94 ++++++++++++++++++++ cmd/data/tags.go | 6 ++ cmd/main.go | 22 ++--- cmd/ui/articles.go | 66 ++++++++------ cmd/ui/editor.go | 5 +- 6 files changed, 154 insertions(+), 245 deletions(-) create mode 100644 cmd/data/tags.go diff --git a/cmd/data/articles.go b/cmd/data/articles.go index 32722de..6fdc370 100644 --- a/cmd/data/articles.go +++ b/cmd/data/articles.go @@ -1,206 +1,16 @@ package data import ( - "encoding/gob" - "fmt" - "os" - "sync" "time" - - "github.com/google/uuid" ) type Article struct { - Title string - Author string - Created time.Time - Desc string - Content string - Tags []string - UUID uuid.UUID - AuthorID int64 -} - -// TODO: setCh -type ArticleList struct { - addCh chan *Article - delCh chan uuid.UUID - getCh chan []Article - retCh chan *Article - articles []*Article - wg sync.WaitGroup -} - -// TODO: setCh -type TagList struct { - addCh chan string - getCh chan []string - tags []string - wg sync.WaitGroup -} - -func initArticleList() *ArticleList { - return &ArticleList{ - addCh: make(chan *Article), - delCh: make(chan uuid.UUID), - getCh: make(chan []Article), - retCh: make(chan *Article), - } -} - -func initTagList() *TagList { - return &TagList{ - addCh: make(chan string), - getCh: make(chan []string), - } -} - -func (al *ArticleList) start() { - al.wg.Done() - for { - select { - case article := <-al.addCh: - al.articles = append(al.articles, article) - case uuid := <-al.delCh: - for i, article := range al.articles { - if article.UUID == uuid { - al.articles = append(al.articles[:i], al.articles[i+1:]...) - al.retCh <- article - } - } - case al.getCh <- func() []Article { - var list []Article - for _, article := range al.articles { - list = append(list, *article) - } - return list - }(): - } - } -} - -func (tl *TagList) start() { - tl.wg.Done() - for { - select { - case tag := <-tl.addCh: - tl.tags = append(tl.tags, tag) - case tl.getCh <- tl.tags: - } - } -} - -func NewArticleList() *ArticleList { - list := initArticleList() - list.articles = make([]*Article, 0) - - list.wg.Add(1) - go list.start() - list.wg.Wait() - - return list -} - -func (al *ArticleList) Add(a *Article) { - al.addCh <- a -} - -func (al *ArticleList) Release(uuid uuid.UUID) (*Article, bool) { - al.delCh <- uuid - article := <-al.retCh - - if article == nil { - return nil, false - } - return article, true -} - -func (al *ArticleList) Get() []Article { - return <-al.getCh -} - -func (al *ArticleList) Save(filename string) error { - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("error creating key file: %v", err) - } - defer file.Close() - - articles := al.Get() - if err = gob.NewEncoder(file).Encode(articles); err != nil { - return fmt.Errorf("error ecoding key: %v", err) - } - - return nil -} - -func LoadArticleList(filename string) (*ArticleList, error) { - file, err := os.Open(filename) - if err != nil { - return nil, fmt.Errorf("error opening key file: %v", err) - } - - articleList := initArticleList() - if err = gob.NewDecoder(file).Decode(&articleList.articles); err != nil { - return nil, fmt.Errorf("error decoding key: %v", err) - } - - articleList.wg.Add(1) - go articleList.start() - articleList.wg.Wait() - - return articleList, nil -} - -func NewTagList() *TagList { - list := initTagList() - list.tags = make([]string, 0) - - list.wg.Add(1) - go list.start() - list.wg.Wait() - - return list -} - -func (tl *TagList) Add(tag string) { - tl.addCh <- tag -} - -func (tl *TagList) Get() []string { - return <-tl.getCh -} - -func (tl *TagList) Save(filename string) error { - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("error creating key file: %v", err) - } - defer file.Close() - - tags := tl.Get() - if err = gob.NewEncoder(file).Encode(tags); err != nil { - return fmt.Errorf("error ecoding key: %v", err) - } - - return nil -} - -func LoadTagList(filename string) (*TagList, error) { - file, err := os.Open(filename) - if err != nil { - return nil, fmt.Errorf("error opening key file: %v", err) - } - defer file.Close() - - tagList := initTagList() - if err = gob.NewDecoder(file).Decode(&tagList.tags); err != nil { - return nil, fmt.Errorf("error decoding key: %v", err) - } - - tagList.wg.Add(1) - go tagList.start() - tagList.wg.Wait() - - return tagList, nil + Title string + Created time.Time + Desc string + Content string + Tags []string + Published bool + ID int64 + AuthorID int64 } diff --git a/cmd/data/db.go b/cmd/data/db.go index 84a43af..7d5be05 100644 --- a/cmd/data/db.go +++ b/cmd/data/db.go @@ -171,3 +171,97 @@ func (db *DB) GetUser(id int64) (*User, error) { return user, nil } + +func (db *DB) AddTag(tagName string) error { + query := "INSERT INTO tags name VALUES ?" + if _, err := db.Exec(query, tagName); err != nil { + return fmt.Errorf("error inserting tag into DB: %v", err) + } + return nil +} + +func (db *DB) GetTagList() ([]Tag, error) { + query := "SELECT id, name FROM tags" + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("error querying tags: %v", err) + } + + var tagList []Tag + for rows.Next() { + var tag Tag + if err = rows.Scan(&tag.ID, &tag.Name); err != nil { + return nil, fmt.Errorf("error scanning tag row: %v", err) + } + tagList = append(tagList, tag) + } + + return tagList, nil +} + +func (db *DB) AddArticle(a *Article) error { + query := ` + INSERT INTO articles + (title, description, content, published, author_id) + VALUES + (?, ?, ?, ?) + ` + if _, err := db.Exec(query, a.Title, a.Desc, a.Content, a.Published, a.AuthorID); err != nil { + return fmt.Errorf("error inserting article into DB: %v", err) + } + return nil +} + +func (db *DB) GetArticle(id int64) (*Article, error) { + query := ` + SELECT title, created, description, content, published, author_id + FROM articles + WHERE id = ? + ` + row := db.QueryRow(query, id) + + article := new(Article) + if err := row.Scan(&article.Title, &article.Created, &article.Desc, + &article.Content, &article.Published, &article.AuthorID); err != nil { + return nil, fmt.Errorf("error scanning article row: %v", err) + } + + return article, nil +} + +func (db *DB) GetUnpublishedArticles() ([]Article, error) { + query := ` + SELECT id, title, created, description, content, published, author_id + FROM articles + WHERE published = ? + ` + rows, err := db.Query(query, false) + if err != nil { + return nil, fmt.Errorf("error querying articles: %v", err) + } + + var articleList []Article + for rows.Next() { + var article Article + if err = rows.Scan(&article.ID, &article.Title, &article.Created, + &article.Desc, &article.Content, &article.Published, + &article.AuthorID); err != nil { + return nil, fmt.Errorf("error scanning article row: %v", err) + } + articleList = append(articleList, article) + } + + return articleList, nil +} + +func (db *DB) UpdateArticle(id int64, attribute string, val interface{}) error { + query := ` + UPDATE articles + SET ? = ? + WHERE id = ? + ` + if _, err := db.Exec(query, attribute, val, id); err != nil { + return fmt.Errorf("error updating article in DB: %v", err) + } + return nil +} diff --git a/cmd/data/tags.go b/cmd/data/tags.go new file mode 100644 index 0000000..8cb0fff --- /dev/null +++ b/cmd/data/tags.go @@ -0,0 +1,6 @@ +package data + +type Tag struct { + ID int64 + Name string +} diff --git a/cmd/main.go b/cmd/main.go index 51694d9..b115de6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -47,16 +47,6 @@ func main() { } store := data.NewCookieStore(key) - articleList, err := data.LoadArticleList("tmp/unpublished-articles.gob") - if err != nil { - articleList = data.NewArticleList() - } - - tagList, err := data.LoadTagList("tmp/tags.gob") - if err != nil { - tagList = data.NewTagList() - } - mux := http.NewServeMux() mux.Handle("/web/static/", http.StripPrefix("/web/static/", http.FileServer(http.Dir("web/static/")))) mux.HandleFunc("/", ui.HomePage(db, store)) @@ -65,15 +55,15 @@ func main() { mux.HandleFunc("GET /create-user/", ui.CreateUser) mux.HandleFunc("GET /hub/", ui.ShowHub(store)) mux.HandleFunc("GET /rss/", ui.ShowRSS(feed)) - mux.HandleFunc("GET /unpublished-articles/", ui.ShowUnpublishedArticles(articleList)) - mux.HandleFunc("GET /write-article/", ui.WriteArticle(tagList)) + mux.HandleFunc("GET /unpublished-articles/", ui.ShowUnpublishedArticles(db)) + mux.HandleFunc("GET /write-article/", ui.WriteArticle(db)) - mux.HandleFunc("POST /add-tag/", ui.AddTag(tagList, store)) + mux.HandleFunc("POST /add-tag/", ui.AddTag(db, store)) mux.HandleFunc("POST /add-user/", ui.AddUser(db, store)) - mux.HandleFunc("POST /finish-article/", ui.FinishArticle(articleList, store)) + mux.HandleFunc("POST /finish-article/", ui.FinishArticle(db, store)) mux.HandleFunc("POST /login/", ui.Login(db, store)) - mux.HandleFunc("POST /review-article/", ui.ReviewArticle(articleList, store)) - mux.HandleFunc("POST /publish-article/", ui.PublishArticle(feed, articleList, store)) + mux.HandleFunc("POST /review-article/", ui.ReviewArticle(db, store)) + mux.HandleFunc("POST /publish-article/", ui.PublishArticle(db, feed, store)) log.Fatalln(http.ListenAndServe(":8080", mux)) } diff --git a/cmd/ui/articles.go b/cmd/ui/articles.go index 0f4c6cc..cb53e04 100644 --- a/cmd/ui/articles.go +++ b/cmd/ui/articles.go @@ -4,10 +4,10 @@ import ( "html/template" "log" "net/http" + "strconv" "time" "git.streifling.com/jason/rss" - "github.com/google/uuid" "streifling.com/jason/cpolis/cmd/data" ) @@ -25,14 +25,21 @@ func ShowHub(s *data.CookieStore) http.HandlerFunc { } } -func WriteArticle(tl *data.TagList) http.HandlerFunc { +func WriteArticle(db *data.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + tags, err := db.GetTagList() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + tmpl, err := template.ParseFiles("web/templates/editor.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tl.Get()) + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tags) } } -func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func FinishArticle(db *data.DB, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { article := new(data.Article) var err error @@ -68,13 +75,8 @@ func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) } - article.UUID = uuid.New() - article.Author = session.Values["name"].(string) - article.Created = time.Now() article.AuthorID = session.Values["id"].(int64) - - al.Add(article) - al.Save("tmp/unpublished-articles.gob") + db.AddArticle(article) tmpl, err := template.ParseFiles("web/templates/hub.html") tmpl = template.Must(tmpl, err) @@ -82,28 +84,33 @@ func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { } } -func ShowUnpublishedArticles(al *data.ArticleList) http.HandlerFunc { +func ShowUnpublishedArticles(db *data.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + articles, err := db.GetUnpublishedArticles() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } tmpl, err := template.ParseFiles("web/templates/unpublished-articles.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", al.Get()) + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", articles) } } -func ReviewArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func ReviewArticle(db *data.DB, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - uuid, err := uuid.Parse(r.PostFormValue("uuid")) + id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - for _, article := range al.Get() { - if article.UUID == uuid { - tmpl, err := template.ParseFiles("web/templates/to-be-published.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", article) - return - } + article, err := db.GetArticle(id) + if err != nil { + tmpl, err := template.ParseFiles("web/templates/to-be-published.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", article) + return } session, err := s.Get(r, "cookie") @@ -119,21 +126,24 @@ func ReviewArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { } } -func PublishArticle(c *data.Channel, al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func PublishArticle(db *data.DB, c *data.Channel, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - uuid, err := uuid.Parse(r.PostFormValue("uuid")) + id, err := strconv.ParseInt(r.PostFormValue("id"), 10, 64) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - article, ok := al.Release(uuid) - if !ok { - // TODO: Warnung anzeigen - // msg = "Alle Felder müssen ausgefüllt werden." - // tmpl, err := template.ParseFiles("web/templates/add-user.html") - // template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + if err = db.UpdateArticle(id, "published", true); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + article, err := db.GetArticle(id) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/cmd/ui/editor.go b/cmd/ui/editor.go index a9dd842..f09e6d0 100644 --- a/cmd/ui/editor.go +++ b/cmd/ui/editor.go @@ -12,10 +12,9 @@ func CreateTag(w http.ResponseWriter, r *http.Request) { template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) } -func AddTag(tl *data.TagList, s *data.CookieStore) http.HandlerFunc { +func AddTag(db *data.DB, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - tl.Add(r.PostFormValue("tag")) - tl.Save("tmp/tags.gob") + db.AddTag(r.PostFormValue("tag")) session, err := s.Get(r, "cookie") if err != nil {