Compare commits

..

29 Commits

Author SHA1 Message Date
ff96d6e563 Try out different rss package 2024-03-05 16:04:49 +01:00
6502aa7ec1 Added partial support for tags 2024-03-03 13:56:49 +01:00
e5bdc235b6 Initial sessions implementation 2024-03-03 09:16:49 +01:00
655992c8b2 Just a bit of cleaning up 2024-03-02 09:09:55 +01:00
aa7fcd6075 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
f9cc90a948 Changed articles and rss to channels 2024-03-01 21:01:38 +01:00
7f2433c30b Implemented proper User struct 2024-03-01 12:25:53 +01:00
f34efc95dd Added ability to publish articles 2024-03-01 11:30:31 +01:00
935d0a1ca4 Added article list for written but non-published articles 2024-02-27 14:10:27 +01:00
48d4d482b2 Convert title and description to plain text 2024-02-27 09:03:21 +01:00
8dae3ca21e Implemented hub 2024-02-24 15:31:33 +01:00
6f02852212 Add ability to display feed 2024-02-24 14:49:29 +01:00
4cc2110c4b Added messages and field memory for adding user 2024-02-24 13:25:32 +01:00
04cbee097c Require all fields to be filled out when creating a new user 2024-02-24 12:10:34 +01:00
93423ae606 Implemented logging to file 2024-02-24 11:41:01 +01:00
41113b24a8 Check if user already exists and bug fix 2024-02-24 10:56:12 +01:00
2247f316a3 Added ability to add user 2024-02-24 10:28:12 +01:00
9beedf9b2b Added ability to login 2024-02-24 09:54:25 +01:00
7d6f96a185 Check user credentials before adding user 2024-02-22 20:12:09 +01:00
8d47146a7c Added ability to update Passwords 2024-02-22 19:27:41 +01:00
4853184ba1 Added ability to add user 2024-02-22 18:49:51 +01:00
50895249df Changed error messages 2024-02-22 15:23:29 +01:00
6e91253908 Added HTML sanitizer 2024-02-22 15:22:45 +01:00
9bb6010319 Added initial support for MySQL databases 2024-02-18 16:37:13 +01:00
75a0af055c Handle misssed errors for encoding and decoding feeds 2024-02-18 14:31:28 +01:00
171a0dd250 Added description and a way to save and restore the RSS feed. 2024-02-18 14:01:06 +01:00
372882a252 Create RSS from HTML 2024-02-18 12:41:49 +01:00
2d0b53a254 Show HTML on website 2024-02-18 10:48:37 +01:00
2447f50bac First implementation of web based editor to HTML pipeline 2024-02-18 10:07:49 +01:00
15 changed files with 292 additions and 202 deletions

View File

@ -1,6 +1,9 @@
package data package data
import ( import (
"encoding/gob"
"fmt"
"os"
"sync" "sync"
"time" "time"
@ -8,22 +11,29 @@ import (
) )
type Article struct { type Article struct {
Tags *TagList
Title string Title string
Author string Author string
Created time.Time Created time.Time
Desc string Desc string
Content string Content string
Tags []string
UUID uuid.UUID UUID uuid.UUID
AuthorID int64 AuthorID int64
} }
type ArticleList struct { type ArticleList struct {
addCh chan *Article addCh chan *Article
delCh chan uuid.UUID delCh chan uuid.UUID
retCh chan *Article retCh chan *Article
getCh chan []Article getCh chan []Article
list []*Article articles []*Article
wg sync.WaitGroup
}
type TagList struct {
addCh chan string
getCh chan []string
tags []string
wg sync.WaitGroup wg sync.WaitGroup
} }
@ -36,22 +46,29 @@ func minArticleList() *ArticleList {
} }
} }
func (l *ArticleList) start() { func minTagList() *TagList {
l.wg.Done() return &TagList{
addCh: make(chan string),
getCh: make(chan []string),
}
}
func (al *ArticleList) start() {
al.wg.Done()
for { for {
select { select {
case article := <-l.addCh: case article := <-al.addCh:
l.list = append(l.list, article) al.articles = append(al.articles, article)
case uuid := <-l.delCh: case uuid := <-al.delCh:
for i, article := range l.list { for i, article := range al.articles {
if article.UUID == uuid { if article.UUID == uuid {
l.list = append(l.list[:i], l.list[i+1:]...) al.articles = append(al.articles[:i], al.articles[i+1:]...)
l.retCh <- article al.retCh <- article
} }
} }
case l.getCh <- func() []Article { case al.getCh <- func() []Article {
var list []Article var list []Article
for _, article := range l.list { for _, article := range al.articles {
list = append(list, *article) list = append(list, *article)
} }
return list 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 { func NewArticleList() *ArticleList {
list := minArticleList() list := minArticleList()
list.list = []*Article{} list.articles = []*Article{}
list.wg.Add(1) list.wg.Add(1)
go list.start() go list.start()
@ -71,19 +99,104 @@ func NewArticleList() *ArticleList {
return list return list
} }
func (l *ArticleList) Add(a *Article) { func (al *ArticleList) Add(a *Article) {
l.addCh <- a al.addCh <- a
} }
func (l *ArticleList) Release(uuid uuid.UUID) (*Article, bool) { func (al *ArticleList) Release(uuid uuid.UUID) (*Article, bool) {
l.delCh <- uuid al.delCh <- uuid
article := <-l.retCh article := <-al.retCh
if article == nil { if article == nil {
return nil, false return nil, false
} }
return article, true return article, true
} }
func (l *ArticleList) Get() []Article { func (al *ArticleList) Get() []Article {
return <-l.getCh 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
} }

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)

View File

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

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,14 +38,12 @@ func checkUserStrings(user data.User) (string, int, bool) {
} }
} }
func CreateUser() http.HandlerFunc { func CreateUser(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("web/templates/add-user.html")
tmpl, err := template.ParseFiles("web/templates/add-user.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil)
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) { 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 {
@ -55,7 +53,7 @@ func AddUser(db *data.DB) 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"),
@ -94,12 +92,36 @@ func AddUser(db *data.DB) 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", nil) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0)
} }
} }

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,12 +25,14 @@ func ShowHub(s *data.CookieStore) http.HandlerFunc {
} }
} }
func WriteArticle(w http.ResponseWriter, r *http.Request) { func WriteArticle(tl *data.TagList) http.HandlerFunc {
tmpl, err := template.ParseFiles("web/templates/editor.html") return func(w http.ResponseWriter, r *http.Request) {
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) 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) { return func(w http.ResponseWriter, r *http.Request) {
article := new(data.Article) article := new(data.Article)
var err error var err error
@ -68,7 +70,8 @@ func FinishArticle(l *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)
l.Add(article) al.Add(article)
al.Save("tmp/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)
@ -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) { 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", 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) { 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 {
@ -92,7 +95,7 @@ func ReviewArticle(l *data.ArticleList, s *data.CookieStore) http.HandlerFunc {
return return
} }
for _, article := range l.Get() { for _, article := range al.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)
@ -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 *rss.Feed, al *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 {
@ -122,7 +125,7 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http
return return
} }
article, ok := l.Release(uuid) article, ok := al.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."
@ -131,14 +134,6 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http
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")
@ -146,6 +141,14 @@ func PublishArticle(f *data.Feed, l *data.ArticleList, s *data.CookieStore) http
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
} }
item := rss.NewItem()
item.Title = article.Title
item.Author = article.Author
item.Description = article.Desc
item.Content = &rss.Content{Value: article.Content}
f.Channels[0].AddItem(item)
f.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"])

31
cmd/ui/editor.go Normal file
View File

@ -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"])
}
}

View File

@ -5,13 +5,12 @@ import (
"log" "log"
"net/http" "net/http"
"streifling.com/jason/cpolis/cmd/data" "git.streifling.com/jason/rss"
) )
func ShowRSS(f *data.Feed) http.HandlerFunc { func ShowRSS(f *rss.Feed) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
feed := f.Get() rss, err := f.ToXML()
rss, err := feed.ToRss()
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,6 +1,7 @@
package ui package ui
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
@ -8,6 +9,23 @@ 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()
@ -60,18 +78,7 @@ func Login(db *data.DB, s *data.CookieStore) http.HandlerFunc {
return return
} }
session, err := s.Get(r, "cookie") if err := saveSession(w, r, s, user); err != nil {
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

2
go.mod
View File

@ -3,9 +3,9 @@ module streifling.com/jason/cpolis
go 1.22.0 go 1.22.0
require ( require (
git.streifling.com/jason/rss v0.0.0-20240305145359-7d49b2cb25fc
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/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark v1.7.0

12
go.sum
View File

@ -1,3 +1,7 @@
git.streifling.com/jason/rss v0.0.0-20240305140009-a59d3f112892 h1:miU3U9H8zSoYprEaG7xaGLMb4CcGLjGt7McC8Wrf+Vs=
git.streifling.com/jason/rss v0.0.0-20240305140009-a59d3f112892/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg=
git.streifling.com/jason/rss v0.0.0-20240305145359-7d49b2cb25fc h1:vJ36ouI2wTK+jFktnqyAfFHoYnoznAgAo1nUzvMzCvQ=
git.streifling.com/jason/rss v0.0.0-20240305145359-7d49b2cb25fc/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=
@ -8,20 +12,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=

23
main.go
View File

@ -15,7 +15,8 @@ func init() {
} }
func main() { 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 { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@ -46,23 +47,33 @@ func main() {
} }
store := data.NewCookieStore(key) 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 := 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-user/", ui.AddUser(db)) mux.HandleFunc("POST /add-tag/", ui.AddTag(tagList, store))
mux.HandleFunc("POST /create-user/", ui.CreateUser()) 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(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

@ -0,0 +1,8 @@
{{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,15 +8,16 @@
<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="writer" name="role" type="radio" value="2" {{if eq .Role "2" }}checked{{end}} /> <input required id="editor" name="role" type="radio" value="1" {{if eq .Role 1 }}checked{{end}} />
<label for="editor">Redakteur</label> <label for="editor">Redakteur</label>
<input required id="editor" name="role" type="radio" value="1" {{if eq .Role "1" }}checked{{end}} /> <input required id="admin" name="role" type="radio" value="0" {{if eq .Role 0 }}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,6 +4,10 @@
<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,11 +1,12 @@
{{define "page-content"}} {{define "page-content"}}
<h2>Hub</h2> <h2>Hub</h2>
<button hx-post="/write-article/" hx-target="#page-content">Artikel schreiben</button> <button hx-get="/write-article/" hx-target="#page-content">Artikel schreiben</button>
<button hx-post="/rss/" hx-target="#page-content">RSS Feed</button> <button hx-get="/rss/" hx-target="#page-content">RSS Feed</button>
{{if eq . 0}}
<button hx-post="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
{{end}}
{{if lt . 2}} {{if lt . 2}}
<button hx-post="/unpublished-articles/" hx-target="#page-content">Unveröffentlichte Artikel</button> <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}}
<button hx-get="/create-user/" hx-target="#page-content">Benutzer hinzufügen</button>
{{end}} {{end}}
{{end}} {{end}}