diff --git a/cmd/data/articles.go b/cmd/data/articles.go index e4ac918..4733ab8 100644 --- a/cmd/data/articles.go +++ b/cmd/data/articles.go @@ -1,6 +1,9 @@ package data import ( + "encoding/gob" + "fmt" + "os" "sync" "time" @@ -19,11 +22,18 @@ type Article struct { } type ArticleList struct { - addCh chan *Article - delCh chan uuid.UUID - retCh chan *Article - getCh chan []Article - list []*Article + addCh chan *Article + delCh chan uuid.UUID + retCh chan *Article + getCh chan []Article + articles []*Article + wg sync.WaitGroup +} + +type TagList struct { + addCh chan string + getCh chan []string + tags []string wg sync.WaitGroup } @@ -36,22 +46,29 @@ func minArticleList() *ArticleList { } } -func (l *ArticleList) start() { - l.wg.Done() +func minTagList() *TagList { + return &TagList{ + addCh: make(chan string), + getCh: make(chan []string), + } +} + +func (al *ArticleList) start() { + al.wg.Done() for { select { - case article := <-l.addCh: - l.list = append(l.list, article) - case uuid := <-l.delCh: - for i, article := range l.list { + case article := <-al.addCh: + al.articles = append(al.articles, article) + case uuid := <-al.delCh: + for i, article := range al.articles { if article.UUID == uuid { - l.list = append(l.list[:i], l.list[i+1:]...) - l.retCh <- article + al.articles = append(al.articles[:i], al.articles[i+1:]...) + al.retCh <- article } } - case l.getCh <- func() []Article { + case al.getCh <- func() []Article { var list []Article - for _, article := range l.list { + for _, article := range al.articles { list = append(list, *article) } return list @@ -60,9 +77,20 @@ func (l *ArticleList) start() { } } +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 := minArticleList() - list.list = []*Article{} + list.articles = []*Article{} list.wg.Add(1) go list.start() @@ -71,19 +99,104 @@ func NewArticleList() *ArticleList { return list } -func (l *ArticleList) Add(a *Article) { - l.addCh <- a +func (al *ArticleList) Add(a *Article) { + al.addCh <- a } -func (l *ArticleList) Release(uuid uuid.UUID) (*Article, bool) { - l.delCh <- uuid - article := <-l.retCh +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 (l *ArticleList) Get() []Article { - return <-l.getCh +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() + + encoder := gob.NewEncoder(file) + articles := al.Get() + err = encoder.Encode(articles) + if 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) + } + + decoder := gob.NewDecoder(file) + articleList := NewArticleList() + err = decoder.Decode(&articleList.articles) + if err != nil { + return nil, fmt.Errorf("error decoding key: %v", err) + } + + return articleList, nil +} + +func NewTagList() *TagList { + list := minTagList() + list.tags = []string{} + + 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() + + encoder := gob.NewEncoder(file) + tags := tl.Get() + err = encoder.Encode(tags) + if 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) + } + + decoder := gob.NewDecoder(file) + tagList := NewTagList() + err = decoder.Decode(&tagList.tags) + if err != nil { + return nil, fmt.Errorf("error decoding key: %v", err) + } + + return tagList, nil } diff --git a/cmd/data/db.go b/cmd/data/db.go index ccc13dd..a631d28 100644 --- a/cmd/data/db.go +++ b/cmd/data/db.go @@ -34,7 +34,7 @@ func OpenDB(dbName string) (*DB, error) { return &db, nil } -func (db *DB) AddUser(user User, pass string) error { +func (db *DB) AddUser(user *User, pass string) error { hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("error creating password hash: %v", err) diff --git a/cmd/ui/admin.go b/cmd/ui/admin.go index 2d43c51..3ae134e 100644 --- a/cmd/ui/admin.go +++ b/cmd/ui/admin.go @@ -11,11 +11,11 @@ import ( ) type AddUserData struct { + *data.User Msg string - data.User } -func inputsEmpty(user data.User, pass, pass2 string) bool { +func inputsEmpty(user *data.User, pass, pass2 string) bool { return len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || @@ -23,7 +23,7 @@ func inputsEmpty(user data.User, pass, pass2 string) bool { len(pass2) == 0 } -func checkUserStrings(user data.User) (string, int, bool) { +func checkUserStrings(user *data.User) (string, int, bool) { userLen := 15 nameLen := 50 @@ -38,14 +38,12 @@ func checkUserStrings(user data.User) (string, int, bool) { } } -func CreateUser() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - tmpl, err := template.ParseFiles("web/templates/add-user.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) - } +func CreateUser(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFiles("web/templates/add-user.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) } -func AddUser(db *data.DB) http.HandlerFunc { +func AddUser(db *data.DB, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { role, err := strconv.Atoi(r.PostFormValue("role")) if err != nil { @@ -55,7 +53,7 @@ func AddUser(db *data.DB) http.HandlerFunc { } htmlData := AddUserData{ - User: data.User{ + User: &data.User{ UserName: r.PostFormValue("username"), FirstName: r.PostFormValue("first-name"), LastName: r.PostFormValue("last-name"), @@ -94,12 +92,36 @@ func AddUser(db *data.DB) http.HandlerFunc { return } + num, err := db.CountEntries() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if num == 0 { + if htmlData.Role != data.Admin { + htmlData.Msg = "Der erste Benutzer muss ein Administrator sein." + htmlData.Role = data.Admin + tmpl, err := template.ParseFiles("web/templates/add-user.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", htmlData) + return + } + + if err := saveSession(w, r, s, htmlData.User); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + if err := db.AddUser(htmlData.User, pass); 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) + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0) } } diff --git a/cmd/ui/articles.go b/cmd/ui/articles.go index 019f342..6d3823a 100644 --- a/cmd/ui/articles.go +++ b/cmd/ui/articles.go @@ -25,12 +25,14 @@ func ShowHub(s *data.CookieStore) http.HandlerFunc { } } -func WriteArticle(w http.ResponseWriter, r *http.Request) { - tmpl, err := template.ParseFiles("web/templates/editor.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) +func WriteArticle(tl *data.TagList) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFiles("web/templates/editor.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tl.Get()) + } } -func FinishArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { article := new(data.Article) var err error @@ -68,7 +70,8 @@ func FinishArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { article.Created = time.Now() article.AuthorID = session.Values["id"].(int64) - l.Add(article) + al.Add(article) + al.Save("tmp/articles.gob") tmpl, err := template.ParseFiles("web/templates/hub.html") tmpl = template.Must(tmpl, err) @@ -76,14 +79,14 @@ func FinishArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { } } -func ShowUnpublishedArticles(l *data.ArticleList) http.HandlerFunc { +func ShowUnpublishedArticles(al *data.ArticleList) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("web/templates/unpublished-articles.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", l.Get()) + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", al.Get()) } } -func ReviewArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func ReviewArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { uuid, err := uuid.Parse(r.PostFormValue("uuid")) if err != nil { @@ -92,7 +95,7 @@ func ReviewArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { return } - for _, article := range l.Get() { + 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) @@ -113,7 +116,7 @@ func ReviewArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { } } -func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http.HandlerFunc { +func PublishArticle(f *data.Feed, al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { uuid, err := uuid.Parse(r.PostFormValue("uuid")) if err != nil { @@ -122,7 +125,7 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http return } - article, ok := l.Release(uuid) + article, ok := al.Release(uuid) if !ok { // TODO: Warnung anzeigen // msg = "Alle Felder müssen ausgefüllt werden." @@ -131,14 +134,6 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http return } - f.Add(&feeds.Item{ - Title: article.Title, - Created: article.Created, - Description: article.Desc, - Content: article.Content, - }) - f.Save("tmp/rss.gob") - session, err := s.Get(r, "cookie") if err != nil { tmpl, err := template.ParseFiles("web/templates/login.html") @@ -146,6 +141,15 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) } + f.Add(&feeds.Item{ + Title: article.Title, + Author: &feeds.Author{Name: session.Values["name"].(string)}, + Created: article.Created, + Description: article.Desc, + Content: article.Content, + }) + f.Save("tmp/rss.gob") + tmpl, err := template.ParseFiles("web/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) diff --git a/cmd/ui/editor.go b/cmd/ui/editor.go new file mode 100644 index 0000000..a9dd842 --- /dev/null +++ b/cmd/ui/editor.go @@ -0,0 +1,31 @@ +package ui + +import ( + "html/template" + "net/http" + + "streifling.com/jason/cpolis/cmd/data" +) + +func CreateTag(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFiles("web/templates/add-tag.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) +} + +func AddTag(tl *data.TagList, s *data.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tl.Add(r.PostFormValue("tag")) + tl.Save("tmp/tags.gob") + + session, err := s.Get(r, "cookie") + if err != nil { + tmpl, err := template.ParseFiles("web/templates/login.html") + msg := "Session nicht mehr gültig. Bitte erneut anmelden." + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + } + + tmpl, err := template.ParseFiles("web/templates/hub.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) + } +} diff --git a/cmd/ui/sessions.go b/cmd/ui/sessions.go index a6d2d8c..5a89573 100644 --- a/cmd/ui/sessions.go +++ b/cmd/ui/sessions.go @@ -1,6 +1,7 @@ package ui import ( + "fmt" "html/template" "log" "net/http" @@ -8,6 +9,23 @@ import ( "streifling.com/jason/cpolis/cmd/data" ) +func saveSession(w http.ResponseWriter, r *http.Request, s *data.CookieStore, u *data.User) error { + session, err := s.Get(r, "cookie") + if err != nil { + return fmt.Errorf("error getting session: %v", err) + } + + session.Values["authenticated"] = true + session.Values["id"] = u.ID + session.Values["name"] = u.FirstName + u.LastName + session.Values["role"] = u.Role + if err := session.Save(r, w); err != nil { + return fmt.Errorf("error saving session: %v", err) + } + + return nil +} + func HomePage(db *data.DB, s *data.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { numRows, err := db.CountEntries() @@ -60,18 +78,7 @@ func Login(db *data.DB, s *data.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - session.Values["authenticated"] = true - session.Values["id"] = user.ID - session.Values["name"] = user.FirstName + user.LastName - session.Values["role"] = user.Role - if err := session.Save(r, w); err != nil { + if err := saveSession(w, r, s, user); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/main.go b/main.go index 7af490e..63bab8a 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,8 @@ func init() { } func main() { - logFile, err := os.Create("tmp/cpolis.log") + logFile, err := os.OpenFile("tmp/cpolis.log", + os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Fatalln(err) } @@ -46,23 +47,33 @@ func main() { } store := data.NewCookieStore(key) - articleList := data.NewArticleList() + articleList, err := data.LoadArticleList("tmp/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)) + mux.HandleFunc("GET /create-tag/", ui.CreateTag) + 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("POST /add-user/", ui.AddUser(db)) - mux.HandleFunc("POST /create-user/", ui.CreateUser()) + mux.HandleFunc("POST /add-tag/", ui.AddTag(tagList, store)) + mux.HandleFunc("POST /add-user/", ui.AddUser(db, store)) mux.HandleFunc("POST /finish-article/", ui.FinishArticle(articleList, 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 /unpublished-articles/", ui.ShowUnpublishedArticles(articleList)) - mux.HandleFunc("POST /write-article/", ui.WriteArticle) log.Fatalln(http.ListenAndServe(":8080", mux)) } diff --git a/web/templates/add-tag.html b/web/templates/add-tag.html new file mode 100644 index 0000000..13fba97 --- /dev/null +++ b/web/templates/add-tag.html @@ -0,0 +1,8 @@ +{{define "page-content"}} +