Compare commits

..

27 Commits

Author SHA1 Message Date
45036fe286 Initial sessions implementation 2024-03-03 09:16:49 +01:00
8f7ac979a3 Just a bit of cleaning up 2024-03-02 09:09:55 +01:00
2da17014e4 Created func for minimum spec for rss and article structs, thereby crushing an annoying bug that was caused by not initializing channels but waiting for messages to go through them 2024-03-02 00:28:42 +01:00
4e2cae74bb Changed articles and rss to channels 2024-03-01 21:01:38 +01:00
4b5929911e Implemented proper User struct 2024-03-01 12:25:53 +01:00
f59321b9c6 Added ability to publish articles 2024-03-01 11:30:31 +01:00
cba3c663c9 Added article list for written but non-published articles 2024-02-27 14:10:27 +01:00
59029c86a9 Convert title and description to plain text 2024-02-27 09:03:21 +01:00
8f5739fb68 Implemented hub 2024-02-24 15:31:33 +01:00
49988edd82 Add ability to display feed 2024-02-24 14:49:29 +01:00
36f7a92a06 Added messages and field memory for adding user 2024-02-24 13:25:32 +01:00
f716e9f0b5 Require all fields to be filled out when creating a new user 2024-02-24 12:10:34 +01:00
f3c8cd6fa5 Implemented logging to file 2024-02-24 11:41:01 +01:00
280e88a526 Check if user already exists and bug fix 2024-02-24 10:56:12 +01:00
8ef6b6472d Added ability to add user 2024-02-24 10:28:12 +01:00
2e08600814 Added ability to login 2024-02-24 09:54:25 +01:00
068bf045a7 Check user credentials before adding user 2024-02-22 20:12:09 +01:00
96fe38726c Added ability to update Passwords 2024-02-22 19:27:41 +01:00
75a21eeb9f Added ability to add user 2024-02-22 18:49:51 +01:00
6020b24e44 Changed error messages 2024-02-22 15:23:29 +01:00
ebfe01069c Added HTML sanitizer 2024-02-22 15:22:45 +01:00
5d41543543 Added initial support for MySQL databases 2024-02-18 16:37:13 +01:00
2ccc9c7397 Handle misssed errors for encoding and decoding feeds 2024-02-18 14:31:28 +01:00
c5623fe4fd Added description and a way to save and restore the RSS feed. 2024-02-18 14:01:06 +01:00
ee04a2a351 Create RSS from HTML 2024-02-18 12:41:49 +01:00
aa034701df Show HTML on website 2024-02-18 10:48:37 +01:00
ad9bfb2439 First implementation of web based editor to HTML pipeline 2024-02-18 10:07:49 +01:00
16 changed files with 154 additions and 358 deletions

View File

@ -1,9 +1,6 @@
package data package data
import ( import (
"encoding/gob"
"fmt"
"os"
"sync" "sync"
"time" "time"
@ -21,56 +18,40 @@ type Article struct {
AuthorID int64 AuthorID int64
} }
// TODO: setCh
type ArticleList struct { type ArticleList struct {
addCh chan *Article addCh chan *Article
delCh chan uuid.UUID delCh chan uuid.UUID
getCh chan []Article retCh chan *Article
retCh chan *Article getCh chan []Article
articles []*Article list []*Article
wg sync.WaitGroup
}
// TODO: setCh
type TagList struct {
addCh chan string
getCh chan []string
tags []string
wg sync.WaitGroup wg sync.WaitGroup
} }
func initArticleList() *ArticleList { func minArticleList() *ArticleList {
return &ArticleList{ return &ArticleList{
addCh: make(chan *Article), addCh: make(chan *Article),
delCh: make(chan uuid.UUID), delCh: make(chan uuid.UUID),
getCh: make(chan []Article),
retCh: make(chan *Article), retCh: make(chan *Article),
getCh: make(chan []Article),
} }
} }
func initTagList() *TagList { func (l *ArticleList) start() {
return &TagList{ l.wg.Done()
addCh: make(chan string),
getCh: make(chan []string),
}
}
func (al *ArticleList) start() {
al.wg.Done()
for { for {
select { select {
case article := <-al.addCh: case article := <-l.addCh:
al.articles = append(al.articles, article) l.list = append(l.list, article)
case uuid := <-al.delCh: case uuid := <-l.delCh:
for i, article := range al.articles { for i, article := range l.list {
if article.UUID == uuid { if article.UUID == uuid {
al.articles = append(al.articles[:i], al.articles[i+1:]...) l.list = append(l.list[:i], l.list[i+1:]...)
al.retCh <- article l.retCh <- article
} }
} }
case al.getCh <- func() []Article { case l.getCh <- func() []Article {
var list []Article var list []Article
for _, article := range al.articles { for _, article := range l.list {
list = append(list, *article) list = append(list, *article)
} }
return list return list
@ -79,20 +60,9 @@ func (al *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 { func NewArticleList() *ArticleList {
list := initArticleList() list := minArticleList()
list.articles = make([]*Article, 0) list.list = []*Article{}
list.wg.Add(1) list.wg.Add(1)
go list.start() go list.start()
@ -101,98 +71,19 @@ func NewArticleList() *ArticleList {
return list return list
} }
func (al *ArticleList) Add(a *Article) { func (l *ArticleList) Add(a *Article) {
al.addCh <- a l.addCh <- a
} }
func (al *ArticleList) Release(uuid uuid.UUID) (*Article, bool) { func (l *ArticleList) Release(uuid uuid.UUID) (*Article, bool) {
al.delCh <- uuid l.delCh <- uuid
article := <-al.retCh article := <-l.retCh
if article == nil { if article == nil {
return nil, false return nil, false
} }
return article, true return article, true
} }
func (al *ArticleList) Get() []Article { func (l *ArticleList) Get() []Article {
return <-al.getCh return <-l.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 := NewArticleList()
if err = gob.NewDecoder(file).Decode(&articleList.articles); err != nil {
return nil, fmt.Errorf("error decoding key: %v", err)
}
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 := NewTagList()
if err = gob.NewDecoder(file).Decode(&tagList.tags); err != nil {
return nil, fmt.Errorf("error decoding key: %v", err)
}
return tagList, nil
} }

View File

@ -34,7 +34,7 @@ func OpenDB(dbName string) (*DB, error) {
return &db, nil 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) hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil { if err != nil {
return fmt.Errorf("error creating password hash: %v", err) return fmt.Errorf("error creating password hash: %v", err)
@ -45,7 +45,8 @@ func (db *DB) AddUser(user *User, pass string) error {
(username, password, first_name, last_name, role) (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 { _, err = db.Exec(query, user.UserName, string(hashedPass), user.FirstName, user.LastName, user.Role)
if err != nil {
return fmt.Errorf("error inserting user into DB: %v", err) return fmt.Errorf("error inserting user into DB: %v", err)
} }
@ -103,7 +104,8 @@ func (db *DB) ChangePassword(id int64, oldPass, newPass string) error {
SET password = ? SET password = ?
WHERE id = ? WHERE id = ?
` `
if _, err = db.Exec(query, string(newHashedPass), id); err != nil { _, err = db.Exec(query, string(newHashedPass), id)
if err != nil {
return fmt.Errorf("error updating password in DB: %v", err) return fmt.Errorf("error updating password in DB: %v", err)
} }
@ -132,7 +134,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)
} }

View File

@ -6,99 +6,101 @@ import (
"os" "os"
"sync" "sync"
"git.streifling.com/jason/rss" "github.com/gorilla/feeds"
) )
type Channel struct { type Feed struct {
addCh chan *rss.Item addCh chan *feeds.Item
setCh chan rss.Channel setCh chan feeds.Feed
getCh chan rss.Channel getCh chan feeds.Feed
channel rss.Channel feed feeds.Feed
wg sync.WaitGroup wg sync.WaitGroup
} }
func initChannel() *Channel { func minFeed() *Feed {
return &Channel{ return &Feed{
addCh: make(chan *rss.Item), addCh: make(chan *feeds.Item),
setCh: make(chan rss.Channel), setCh: make(chan feeds.Feed),
getCh: make(chan rss.Channel), getCh: make(chan feeds.Feed),
channel: rss.Channel{
Items: make([]*rss.Item, 0),
},
} }
} }
func (c *Channel) start() { func (f *Feed) start() {
c.wg.Done() f.wg.Done()
for { for {
select { select {
case item := <-c.addCh: case item := <-f.addCh:
c.channel.Items = append(c.channel.Items, item) f.feed.Items = append(f.feed.Items, item)
case c.getCh <- c.channel: case f.getCh <- f.feed:
case c.channel = <-c.setCh: case f.feed = <-f.setCh:
} }
} }
} }
func NewChannel(title, link, desc string) *Channel { func NewFeed(title, link, desc string) *Feed {
channel := initChannel() feed := minFeed()
channel.channel = rss.Channel{ feed.feed = feeds.Feed{
Title: title, Title: title,
Link: link, Link: &feeds.Link{Href: link},
Description: desc, Description: desc,
} }
channel.wg.Add(1) feed.wg.Add(1)
go channel.start() go feed.start()
channel.wg.Wait() feed.wg.Wait()
return channel return feed
} }
func (c *Channel) Get() rss.Channel { func (f *Feed) Get() feeds.Feed {
return <-c.getCh return <-f.getCh
} }
func (f *Channel) Set(channel rss.Channel) { func (f *Feed) Set(feed feeds.Feed) {
f.setCh <- channel f.setCh <- feed
} }
func LoadChannel(filename string) (*Channel, error) { func OpenFeed(filename string) (*Feed, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error opening file %v: %v", filename, err) return nil, fmt.Errorf("error opening file %v: %v", filename, err)
} }
defer file.Close() defer file.Close()
channel := initChannel() feed := minFeed()
channel.wg.Add(1) feed.wg.Add(1)
go channel.start() go feed.start()
channel.wg.Wait() feed.wg.Wait()
tmpChannel := new(rss.Channel) decoder := gob.NewDecoder(file)
if err = gob.NewDecoder(file).Decode(tmpChannel); err != nil { tmpFeed := new(feeds.Feed)
return nil, fmt.Errorf("error decoding channel from file %v: %v", filename, err) err = decoder.Decode(tmpFeed)
if err != nil {
return nil, fmt.Errorf("error decoding file %v: %v", filename, err)
} }
channel.Set(*tmpChannel) feed.Set(*tmpFeed)
return channel, nil
return feed, nil
} }
func (c *Channel) Save(filename string) error { func (f *Feed) Save(filename string) error {
file, err := os.Create(filename) file, err := os.Create(filename)
if err != nil { if err != nil {
return fmt.Errorf("error creating file %v: %v", filename, err) return fmt.Errorf("error creating file %v: %v", filename, err)
} }
defer file.Close() defer file.Close()
channel := c.Get() encoder := gob.NewEncoder(file)
if err = gob.NewEncoder(file).Encode(channel); err != nil { feed := f.Get()
err = encoder.Encode(feed)
if err != nil {
return fmt.Errorf("error encoding file %v: %v", filename, err) return fmt.Errorf("error encoding file %v: %v", filename, err)
} }
return nil return nil
} }
func (c *Channel) Add(i *rss.Item) { func (f *Feed) Add(i *feeds.Item) {
c.addCh <- i f.addCh <- i
} }

View File

@ -17,7 +17,8 @@ type CookieStore struct {
func NewKey() ([]byte, error) { func NewKey() ([]byte, error) {
key := make([]byte, 32) key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil { _, err := io.ReadFull(rand.Reader, key)
if err != nil {
return nil, fmt.Errorf("error generating key: %v", err) return nil, fmt.Errorf("error generating key: %v", err)
} }
@ -32,7 +33,9 @@ func SaveKey(key []byte, filename string) error {
defer file.Close() defer file.Close()
file.Chmod(0600) file.Chmod(0600)
if err = gob.NewEncoder(file).Encode(key); err != nil { encoder := gob.NewEncoder(file)
err = encoder.Encode(key)
if err != nil {
return fmt.Errorf("error ecoding key: %v", err) return fmt.Errorf("error ecoding key: %v", err)
} }
@ -46,7 +49,9 @@ func LoadKey(filename string) ([]byte, error) {
} }
key := make([]byte, 32) key := make([]byte, 32)
if err = gob.NewDecoder(file).Decode(&key); err != nil { decoder := gob.NewDecoder(file)
err = decoder.Decode(&key)
if err != nil {
return nil, fmt.Errorf("error decoding key: %v", err) return nil, fmt.Errorf("error decoding key: %v", err)
} }

View File

@ -11,11 +11,11 @@ import (
) )
type AddUserData struct { type AddUserData struct {
*data.User
Msg string 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 || return len(user.UserName) == 0 ||
len(user.FirstName) == 0 || len(user.FirstName) == 0 ||
len(user.LastName) == 0 || len(user.LastName) == 0 ||
@ -23,7 +23,7 @@ func inputsEmpty(user *data.User, pass, pass2 string) bool {
len(pass2) == 0 len(pass2) == 0
} }
func checkUserStrings(user *data.User) (string, int, bool) { func checkUserStrings(user data.User) (string, int, bool) {
userLen := 15 userLen := 15
nameLen := 50 nameLen := 50
@ -38,12 +38,14 @@ func checkUserStrings(user *data.User) (string, int, bool) {
} }
} }
func CreateUser(w http.ResponseWriter, r *http.Request) { func CreateUser() http.HandlerFunc {
tmpl, err := template.ParseFiles("web/templates/add-user.html") return func(w http.ResponseWriter, r *http.Request) {
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) tmpl, err := template.ParseFiles("web/templates/add-user.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
}
} }
func AddUser(db *data.DB, s *data.CookieStore) http.HandlerFunc { func AddUser(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
role, err := strconv.Atoi(r.PostFormValue("role")) role, err := strconv.Atoi(r.PostFormValue("role"))
if err != nil { if err != nil {
@ -53,7 +55,7 @@ func AddUser(db *data.DB, s *data.CookieStore) http.HandlerFunc {
} }
htmlData := AddUserData{ htmlData := AddUserData{
User: &data.User{ User: data.User{
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
@ -92,36 +94,12 @@ func AddUser(db *data.DB, s *data.CookieStore) http.HandlerFunc {
return 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 { if err := db.AddUser(htmlData.User, pass); 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")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
} }
} }

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"time" "time"
"git.streifling.com/jason/rss"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/feeds"
"streifling.com/jason/cpolis/cmd/data" "streifling.com/jason/cpolis/cmd/data"
) )
@ -25,14 +25,12 @@ func ShowHub(s *data.CookieStore) http.HandlerFunc {
} }
} }
func WriteArticle(tl *data.TagList) http.HandlerFunc { func WriteArticle(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("web/templates/editor.html")
tmpl, err := template.ParseFiles("web/templates/editor.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tl.Get())
}
} }
func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { func FinishArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
article := new(data.Article) article := new(data.Article)
var err error var err error
@ -58,9 +56,6 @@ func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return return
} }
r.ParseForm()
article.Tags = append(article.Tags, r.Form["tags"]...)
session, err := s.Get(r, "cookie") session, err := s.Get(r, "cookie")
if err != nil { if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html") tmpl, err := template.ParseFiles("web/templates/login.html")
@ -73,8 +68,7 @@ func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
article.Created = time.Now() article.Created = time.Now()
article.AuthorID = session.Values["id"].(int64) article.AuthorID = session.Values["id"].(int64)
al.Add(article) l.Add(article)
al.Save("tmp/unpublished-articles.gob")
tmpl, err := template.ParseFiles("web/templates/hub.html") tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
@ -82,14 +76,14 @@ func FinishArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
} }
} }
func ShowUnpublishedArticles(al *data.ArticleList) http.HandlerFunc { func ShowUnpublishedArticles(l *data.ArticleList) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("web/templates/unpublished-articles.html") 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", l.Get())
} }
} }
func ReviewArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc { func ReviewArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
uuid, err := uuid.Parse(r.PostFormValue("uuid")) uuid, err := uuid.Parse(r.PostFormValue("uuid"))
if err != nil { if err != nil {
@ -98,7 +92,7 @@ func ReviewArticle(al *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return return
} }
for _, article := range al.Get() { for _, article := range l.Get() {
if article.UUID == uuid { if article.UUID == uuid {
tmpl, err := template.ParseFiles("web/templates/to-be-published.html") tmpl, err := template.ParseFiles("web/templates/to-be-published.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", article) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", article)
@ -119,7 +113,7 @@ 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(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
uuid, err := uuid.Parse(r.PostFormValue("uuid")) uuid, err := uuid.Parse(r.PostFormValue("uuid"))
if err != nil { if err != nil {
@ -128,7 +122,7 @@ func PublishArticle(c *data.Channel, al *data.ArticleList, s *data.CookieStore)
return return
} }
article, ok := al.Release(uuid) article, ok := l.Release(uuid)
if !ok { if !ok {
// TODO: Warnung anzeigen // TODO: Warnung anzeigen
// msg = "Alle Felder müssen ausgefüllt werden." // msg = "Alle Felder müssen ausgefüllt werden."
@ -137,6 +131,14 @@ func PublishArticle(c *data.Channel, al *data.ArticleList, s *data.CookieStore)
return 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") session, err := s.Get(r, "cookie")
if err != nil { if err != nil {
tmpl, err := template.ParseFiles("web/templates/login.html") tmpl, err := template.ParseFiles("web/templates/login.html")
@ -144,16 +146,6 @@ func PublishArticle(c *data.Channel, al *data.ArticleList, s *data.CookieStore)
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
} }
c.Add(&rss.Item{
Title: article.Title,
Author: session.Values["name"].(string),
PubDate: article.Created.Format(time.RFC1123Z),
Description: article.Desc,
Content: &rss.Content{Value: article.Content},
Categories: article.Tags,
})
c.Save("tmp/rss.gob")
tmpl, err := template.ParseFiles("web/templates/hub.html") tmpl, err := template.ParseFiles("web/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"])

View File

@ -1,31 +0,0 @@
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"])
}
}

View File

@ -5,16 +5,13 @@ import (
"log" "log"
"net/http" "net/http"
"git.streifling.com/jason/rss"
"streifling.com/jason/cpolis/cmd/data" "streifling.com/jason/cpolis/cmd/data"
) )
func ShowRSS(c *data.Channel) http.HandlerFunc { func ShowRSS(f *data.Feed) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
channel := c.Get() feed := f.Get()
feed := rss.NewFeed() rss, err := feed.ToRss()
feed.Channels = append(feed.Channels, &channel)
rss, err := feed.ToXML()
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)

View File

@ -1,7 +1,6 @@
package ui package ui
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
@ -9,23 +8,6 @@ import (
"streifling.com/jason/cpolis/cmd/data" "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 { func HomePage(db *data.DB, s *data.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
numRows, err := db.CountEntries() numRows, err := db.CountEntries()
@ -78,7 +60,18 @@ func Login(db *data.DB, s *data.CookieStore) http.HandlerFunc {
return return
} }
if err := saveSession(w, r, s, user); err != nil { 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 {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

1
go.mod
View File

@ -3,7 +3,6 @@ module streifling.com/jason/cpolis
go 1.22.0 go 1.22.0
require ( require (
git.streifling.com/jason/rss v0.0.0-20240305164907-524bf9676188
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2 github.com/gorilla/feeds v1.1.2

8
go.sum
View File

@ -1,11 +1,3 @@
git.streifling.com/jason/rss v0.0.0-20240305152729-9d8cc6464565 h1:/eO9NTksh+9yLz3HiYNR7BJo/iMTxxW5/d9h8I6vR6E=
git.streifling.com/jason/rss v0.0.0-20240305152729-9d8cc6464565/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
git.streifling.com/jason/rss v0.0.0-20240305160544-c8551159fe32 h1:G25NZzsD73rOkGYgV2vPUDviB0JXk5qi+dXOwB6J56U=
git.streifling.com/jason/rss v0.0.0-20240305160544-c8551159fe32/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
git.streifling.com/jason/rss v0.0.0-20240305160829-6cd08bb65d2a h1:TWQ9gwe7eWjaLUrZ0CJSc+sUUOw3VoGHlR3F8mH6vqs=
git.streifling.com/jason/rss v0.0.0-20240305160829-6cd08bb65d2a/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
git.streifling.com/jason/rss v0.0.0-20240305164907-524bf9676188 h1:C8M/j3f+cl5Y7YfGpU/ynb/SC/4tTYMDsyGFt3rswM8=
git.streifling.com/jason/rss v0.0.0-20240305164907-524bf9676188/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=

View File

@ -15,8 +15,7 @@ func init() {
} }
func main() { func main() {
logFile, err := os.OpenFile("tmp/cpolis.log", logFile, err := os.Create("tmp/cpolis.log")
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@ -29,10 +28,10 @@ func main() {
} }
defer db.Close() defer db.Close()
feed, err := data.LoadChannel("tmp/rss.gob") feed, err := data.OpenFeed("tmp/rss.gob")
if err != nil { if err != nil {
log.Println(err) log.Println(err)
feed = data.NewChannel("Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt", feed = data.NewFeed("Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt",
"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")
} }
@ -47,33 +46,23 @@ func main() {
} }
store := data.NewCookieStore(key) store := data.NewCookieStore(key)
articleList, err := data.LoadArticleList("tmp/unpublished-articles.gob") articleList := data.NewArticleList()
if err != nil {
articleList = data.NewArticleList()
}
tagList, err := data.LoadTagList("tmp/tags.gob")
if err != nil {
tagList = data.NewTagList()
}
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/web/static/", http.StripPrefix("/web/static/", http.FileServer(http.Dir("web/static/")))) mux.Handle("/web/static/", http.StripPrefix("/web/static/", http.FileServer(http.Dir("web/static/"))))
mux.HandleFunc("/", ui.HomePage(db, store)) 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 /hub/", ui.ShowHub(store))
mux.HandleFunc("GET /rss/", ui.ShowRSS(feed)) 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-tag/", ui.AddTag(tagList, store)) mux.HandleFunc("POST /add-user/", ui.AddUser(db))
mux.HandleFunc("POST /add-user/", ui.AddUser(db, store)) mux.HandleFunc("POST /create-user/", ui.CreateUser())
mux.HandleFunc("POST /finish-article/", ui.FinishArticle(articleList, store)) mux.HandleFunc("POST /finish-article/", ui.FinishArticle(articleList, store))
mux.HandleFunc("POST /login/", ui.Login(db, store)) mux.HandleFunc("POST /login/", ui.Login(db, store))
mux.HandleFunc("POST /review-article/", ui.ReviewArticle(articleList, store)) mux.HandleFunc("POST /review-article/", ui.ReviewArticle(articleList, store))
mux.HandleFunc("POST /publish-article/", ui.PublishArticle(feed, 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)) log.Fatalln(http.ListenAndServe(":8080", mux))
} }

View File

@ -1,8 +0,0 @@
{{define "page-content"}}
<h2>Neuer Benutzer</h2>
<form>
<input required name="tag" placeholder="Tag" type="text" />
<input type="submit" value="Anlegen" hx-post="/add-tag/" hx-target="#page-content" />
</form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
{{end}}

View File

@ -8,16 +8,15 @@
<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}} />
<label for="writer">Schreiber</label> <label for="writer">Schreiber</label>
<input required id="editor" name="role" type="radio" value="1" {{if eq .Role 1 }}checked{{end}} /> <input required id="writer" 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="admin" name="role" type="radio" value="0" {{if eq .Role 0 }}checked{{end}} /> <input required id="editor" name="role" type="radio" value="1" {{if eq .Role "1" }}checked{{end}} />
<label for="admin">Admin</label> <label for="admin">Admin</label>
<input required id="admin" name="role" type="radio" value="0" {{if eq .Role "0" }}checked{{end}} />
<input type="submit" value="Anlegen" hx-post="/add-user/" hx-target="#page-content" /> <input type="submit" value="Anlegen" hx-post="/add-user/" hx-target="#page-content" />
</form> </form>
<button hx-get="/hub/" hx-target="#page-content">Abbrechen</button>
<script> <script>
var msg = "{{.Msg}}"; var msg = "{{.Msg}}";

View File

@ -4,10 +4,6 @@
<input name="editor-title" placeholder="Titel" type="text" /> <input name="editor-title" placeholder="Titel" type="text" />
<textarea name="editor-desc" placeholder="Beschreibung"></textarea> <textarea name="editor-desc" placeholder="Beschreibung"></textarea>
<textarea name="editor-text" placeholder="Artikel"></textarea> <textarea name="editor-text" placeholder="Artikel"></textarea>
{{range .}}
<input id="{{.}}" name="tags" type="checkbox" value="{{.}}" />
<label for="{{.}}">{{.}}</label>
{{end}}
<input type="submit" value="Senden" hx-post="/finish-article/" hx-target="#page-content" /> <input type="submit" value="Senden" hx-post="/finish-article/" hx-target="#page-content" />
</form> </form>
{{end}} {{end}}

View File

@ -1,12 +1,11 @@
{{define "page-content"}} {{define "page-content"}}
<h2>Hub</h2> <h2>Hub</h2>
<button hx-get="/write-article/" hx-target="#page-content">Artikel schreiben</button> <button hx-post="/write-article/" hx-target="#page-content">Artikel schreiben</button>
<button hx-get="/rss/" hx-target="#page-content">RSS Feed</button> <button hx-post="/rss/" hx-target="#page-content">RSS Feed</button>
{{if lt . 2}}
<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>
{{end}}
{{if eq . 0}} {{if eq . 0}}
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button> <button hx-post="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
{{end}}
{{if lt . 2}}
<button hx-post="/unpublished-articles/" hx-target="#page-content">Unveröffentlichte Artikel</button>
{{end}} {{end}}
{{end}} {{end}}