diff --git a/.air.toml b/.air.toml index 6118030..2157d00 100644 --- a/.air.toml +++ b/.air.toml @@ -4,6 +4,7 @@ tmp_dir = "tmp" [build] args_bin = [ + "-articles tmp/articles", "-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'", "-domain localhost", "-key tmp/key.gob", diff --git a/cmd/backend/articles.go b/cmd/backend/articles.go index 392c882..fc47ec7 100644 --- a/cmd/backend/articles.go +++ b/cmd/backend/articles.go @@ -12,7 +12,10 @@ type Article struct { Title string Created time.Time Description string - Content string + Link string + EncURL string + EncLength int + EncType string Published bool Rejected bool ID int64 @@ -26,8 +29,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) { selectQuery := "SELECT id FROM issues WHERE published = false" insertQuery := ` INSERT INTO articles - (title, description, content, published, rejected, author_id, issue_id) - VALUES (?, ?, ?, ?, ?, ?, ?) + (title, description, link, enc_url, enc_length, enc_type, published, rejected, author_id, issue_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` for i := 0; i < TxMaxRetries; i++ { @@ -44,8 +47,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) { return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err) } - result, err := tx.Exec(insertQuery, a.Title, a.Description, - a.Content, a.Published, a.Rejected, a.AuthorID, id) + result, err := tx.Exec(insertQuery, a.Title, a.Description, a.Link, + a.EncURL, a.EncLength, a.EncType, a.Published, a.Rejected, a.AuthorID, id) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) @@ -79,7 +82,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) { func (db *DB) GetArticle(id int64) (*Article, error) { query := ` - SELECT title, created, description, content, published, author_id + SELECT title, created, description, link, enc_url, enc_length, enc_type, published, author_id FROM articles WHERE id = ? ` @@ -90,7 +93,8 @@ func (db *DB) GetArticle(id int64) (*Article, error) { var err error if err := row.Scan(&article.Title, &created, &article.Description, - &article.Content, &article.Published, &article.AuthorID); err != nil { + &article.Link, &article.EncURL, &article.EncLength, &article.EncType, + &article.Published, &article.AuthorID); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } @@ -105,7 +109,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) { func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) { query := ` - SELECT id, title, created, description, content, author_id, issue_id + SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, issue_id FROM articles WHERE published = ? AND rejected = ? @@ -121,8 +125,8 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) { var created []byte if err = rows.Scan(&article.ID, &article.Title, &created, - &article.Description, &article.Content, &article.AuthorID, - &article.IssueID); err != nil { + &article.Description, &article.Link, &article.EncURL, &article.EncLength, + &article.EncType, &article.AuthorID, &article.IssueID); err != nil { return nil, fmt.Errorf("error scanning article row: %v", err) } @@ -143,7 +147,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) { txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} issueQuery := "SELECT id FROM issues WHERE published = false" articlesQuery := ` - SELECT id, title, created, description, content, author_id + SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id FROM articles WHERE issue_id = ? AND published = true ` @@ -177,7 +181,8 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) { var created []byte if err = rows.Scan(&article.ID, &article.Title, &created, - &article.Description, &article.Content, &article.AuthorID); err != nil { + &article.Description, &article.Link, &article.EncURL, &article.EncLength, + &article.EncType, &article.AuthorID); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) } diff --git a/cmd/backend/config.go b/cmd/backend/config.go index 7f5441b..416b325 100644 --- a/cmd/backend/config.go +++ b/cmd/backend/config.go @@ -11,6 +11,7 @@ import ( ) type Config struct { + ArticleDir string DBName string Description string Domain string @@ -28,6 +29,7 @@ type Config struct { func newConfig() *Config { return &Config{ + ArticleDir: "/var/www/cpolis/articles", DBName: "cpolis", FirebaseKey: "/var/www/cpolis/serviceAccountKey.json", KeyFile: "/var/www/cpolis/cpolis.key", @@ -77,6 +79,7 @@ func (c *Config) handleCliArgs() error { var err error port := 8080 + flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory") flag.StringVar(&c.DBName, "db", c.DBName, "DB name") flag.StringVar(&c.Description, "desc", c.Description, "channel description") flag.StringVar(&c.Domain, "domain", c.Domain, "domain name") @@ -92,6 +95,14 @@ func (c *Config) handleCliArgs() error { flag.IntVar(&port, "port", port, "port") flag.Parse() + c.ArticleDir, err = filepath.Abs(c.ArticleDir) + if err != nil { + return fmt.Errorf("error finding absolute path for articles directory: %v", err) + } + if err = os.MkdirAll(c.ArticleDir, 0755); err != nil { + return fmt.Errorf("error creating articles directory: %v", err) + } + c.FirebaseKey, err = filepath.Abs(c.FirebaseKey) if err != nil { return fmt.Errorf("error finding absolute path for Firebase service account key file: %v", err) @@ -109,12 +120,18 @@ func (c *Config) handleCliArgs() error { c.PDFDir, err = filepath.Abs(c.PDFDir) if err != nil { - return fmt.Errorf("error finding absolute path for pdfs dir: %v", err) + return fmt.Errorf("error finding absolute path for pdfs directory: %v", err) + } + if err = os.MkdirAll(c.PDFDir, 0755); err != nil { + return fmt.Errorf("error creating pdfs directory: %v", err) } c.PicsDir, err = filepath.Abs(c.PicsDir) if err != nil { - return fmt.Errorf("error finding absolute path for pics dir: %v", err) + return fmt.Errorf("error finding absolute path for pics directory: %v", err) + } + if err = os.MkdirAll(c.PicsDir, 0755); err != nil { + return fmt.Errorf("error creating pics directory: %v", err) } c.Port = fmt.Sprint(":", port) @@ -126,7 +143,10 @@ func (c *Config) handleCliArgs() error { c.WebDir, err = filepath.Abs(c.WebDir) if err != nil { - return fmt.Errorf("error finding absolute path for web dir: %v", err) + return fmt.Errorf("error finding absolute path for web directory: %v", err) + } + if err = os.MkdirAll(c.WebDir, 0755); err != nil { + return fmt.Errorf("error creating web directory: %v", err) } return nil diff --git a/cmd/backend/rss.go b/cmd/backend/rss.go index c68395e..35927d2 100644 --- a/cmd/backend/rss.go +++ b/cmd/backend/rss.go @@ -9,52 +9,11 @@ import ( "git.streifling.com/jason/rss" ) -func GetChannel(db *DB, title, link, description string) (*rss.Channel, error) { +func GenerateRSS(c *Config, db *DB) (*string, error) { channel := &rss.Channel{ - Title: title, - Link: link, - Description: description, - Items: make([]*rss.Item, 0), - } - - articles, err := db.GetCertainArticles(true, false) - if err != nil { - return nil, fmt.Errorf("error fetching published articles: %v", err) - } - - for _, article := range articles { - tags, err := db.GetArticleTags(article.ID) - if err != nil { - return nil, fmt.Errorf("error fetching tags for article %v: %v", article.Title, err) - } - tagNames := make([]string, 0) - for _, tag := range tags { - tagNames = append(tagNames, tag.Name) - } - - user, err := db.GetUser(article.AuthorID) - if err != nil { - return nil, fmt.Errorf("error finding user %v: %v", article.AuthorID, err) - } - - channel.Items = append(channel.Items, &rss.Item{ - Title: article.Title, - Author: user.FirstName + user.LastName, - PubDate: article.Created.Format(time.RFC1123Z), - Description: article.Description, - Content: &rss.Content{Value: article.Content}, - Categories: tagNames, - }) - } - - return channel, nil -} - -func GenerateRSS(db *DB, title, link, desc string) (*string, error) { - channel := &rss.Channel{ - Title: title, - Link: link, - Description: desc, + Title: c.Title, + Link: c.Link, + Description: c.Description, Items: make([]*rss.Item, 0), } @@ -89,19 +48,26 @@ func GenerateRSS(db *DB, title, link, desc string) (*string, error) { return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err) } - articleContent, err := ConvertToHTML(article.Content) - if err != nil { - return nil, fmt.Errorf("error converting content to HTML for RSS feed: %v", err) + item := &rss.Item{ + Author: fmt.Sprint(user.FirstName, " ", user.LastName), + Categories: tagNames, + Description: articleDescription, + Guid: string(article.ID), + Link: article.Link, + PubDate: article.Created.Format(time.RFC1123Z), + Title: articleTitle, + } + fmt.Println(article.Link, ": ", len(article.Link)) + + if article.Title == "Autogenerated cpolis Issue Article" { + item.Enclosure = &rss.Enclosure{ + Url: article.EncURL, + Lenght: article.EncLength, + Type: article.EncType, + } } - channel.Items = append(channel.Items, &rss.Item{ - Title: articleTitle, - Author: user.FirstName + " " + user.LastName, - PubDate: article.Created.Format(time.RFC1123Z), - Description: articleDescription, - Content: &rss.Content{Value: articleContent}, - Categories: tagNames, - }) + channel.Items = append(channel.Items, item) } feed := rss.NewFeed() diff --git a/cmd/calls/articles.go b/cmd/calls/articles.go new file mode 100644 index 0000000..0c327c0 --- /dev/null +++ b/cmd/calls/articles.go @@ -0,0 +1,54 @@ +package calls + +import ( + "fmt" + "log" + "net/http" + "os" + "strconv" + + b "streifling.com/jason/cpolis/cmd/backend" +) + +func ServeArticle(c *b.Config, db *b.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !tokenIsVerified(w, r, c) { + return + } + + idString := r.PathValue("id") + id, err := strconv.ParseInt(idString, 10, 64) + if 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 + } + + if !article.Published { + return + } + + contentBytes, err := os.ReadFile(article.Link) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + content, err := b.ConvertToHTML(string(contentBytes)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprint(w, content) + } +} diff --git a/cmd/frontend/images.go b/cmd/calls/images.go similarity index 86% rename from cmd/frontend/images.go rename to cmd/calls/images.go index 5718ee5..dd7a491 100644 --- a/cmd/frontend/images.go +++ b/cmd/calls/images.go @@ -1,4 +1,4 @@ -package frontend +package calls import ( "log" @@ -10,7 +10,7 @@ import ( func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if _, err := getSession(w, r, c, s); err != nil { + if !tokenIsVerified(w, r, c) { return } diff --git a/cmd/calls/pdf.go b/cmd/calls/pdf.go index 8779634..c0213eb 100644 --- a/cmd/calls/pdf.go +++ b/cmd/calls/pdf.go @@ -12,33 +12,37 @@ import ( func ServePDFList(c *b.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if tokenIsVerified(w, r, c) { - files, err := os.ReadDir(c.PDFDir) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if !tokenIsVerified(w, r, c) { + return + } - fileNames := make([]string, 0) - for _, file := range files { - fileNames = append(fileNames, file.Name()) - } + files, err := os.ReadDir(c.PDFDir) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } - w.Header().Set("Content-Type", "application/json") - if err = json.NewEncoder(w).Encode(fileNames); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + fileNames := make([]string, 0) + for _, file := range files { + fileNames = append(fileNames, file.Name()) + } + + w.Header().Set("Content-Type", "application/json") + if err = json.NewEncoder(w).Encode(fileNames); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } func ServePDF(c *b.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if tokenIsVerified(w, r, c) { - http.ServeFile(w, r, fmt.Sprint(c.PDFDir, "/", r.PathValue("id"))) + if !tokenIsVerified(w, r, c) { + return } + + http.ServeFile(w, r, fmt.Sprint(c.PDFDir, "/", r.PathValue("id"))) } } diff --git a/cmd/calls/rss.go b/cmd/calls/rss.go index f8bc83a..d2757a7 100644 --- a/cmd/calls/rss.go +++ b/cmd/calls/rss.go @@ -8,8 +8,10 @@ import ( func ServeRSS(c *b.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if tokenIsVerified(w, r, c) { - http.ServeFile(w, r, c.RSSFile) + if !tokenIsVerified(w, r, c) { + return } + + http.ServeFile(w, r, c.RSSFile) } } diff --git a/cmd/frontend/articles.go b/cmd/frontend/articles.go index 5fc9ee6..7b34023 100644 --- a/cmd/frontend/articles.go +++ b/cmd/frontend/articles.go @@ -5,7 +5,6 @@ import ( "fmt" "html/template" "io" - "io/fs" "log" "net/http" "os" @@ -76,7 +75,6 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { article := &b.Article{ Title: r.PostFormValue("article-title"), Description: r.PostFormValue("article-description"), - Content: r.PostFormValue("article-content"), Published: false, Rejected: false, AuthorID: session.Values["id"].(int64), @@ -89,6 +87,20 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } + articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") + if err = os.WriteFile(articleAbsName, []byte(r.PostFormValue("article-content")), 0644); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + article.Link = fmt.Sprint("http://", c.Domain, c.Port, "/article/serve/", article.ID) + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: article.ID, AttName: "link", Value: article.Link}); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + r.ParseForm() tags := make([]int64, 0) for _, tag := range r.Form["tags"] { @@ -130,10 +142,16 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { description := r.PostFormValue("article-description") content := r.PostFormValue("article-content") + link := fmt.Sprint(c.ArticleDir, "/", id, ".md") + if err = os.WriteFile(link, []byte(content), 0644); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err = db.UpdateAttributes( &b.Attribute{Table: "articles", ID: id, AttName: "title", Value: title}, &b.Attribute{Table: "articles", ID: id, AttName: "description", Value: description}, - &b.Attribute{Table: "articles", ID: id, AttName: "content", Value: content}, &b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false}, ); err != nil { log.Println(err) @@ -261,7 +279,15 @@ func ReviewUnpublishedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand return } - content, err := b.ConvertToHTML(article.Content) + articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") + contentBytes, err := os.ReadFile(articleAbsName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + content, err := b.ConvertToHTML(string(contentBytes)) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -291,6 +317,7 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler type htmlData struct { Selected map[int64]bool Article *b.Article + Content string Tags []*b.Tag } data := new(htmlData) @@ -309,6 +336,16 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler return } + articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md") + contentBytes, err := os.ReadFile(articleAbsName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data.Content = string(contentBytes) + data.Tags, err = db.GetTagList() if err != nil { log.Println(err) @@ -363,7 +400,7 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - feed, err := b.GenerateRSS(db, c.Title, c.Link, c.Description) + feed, err := b.GenerateRSS(c, db) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -427,7 +464,7 @@ func ShowCurrentArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFu } } -func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { +func UploadArticleImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := getSession(w, r, c, s); err != nil { return @@ -436,7 +473,7 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { file, header, err := r.FormFile("article-image") if err != nil { log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } defer file.Close() @@ -451,12 +488,6 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return } - if err = os.MkdirAll(fmt.Sprint(c.PicsDir, "/"), fs.FileMode(0755)); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - img, err := os.Create(absFilepath) if err != nil { log.Println(err) @@ -471,7 +502,7 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return } - url := fmt.Sprint(c.Domain, "/pics/", filename) + url := fmt.Sprint(c.Domain, "/image/serve/", filename) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(url) } @@ -541,7 +572,14 @@ func ReviewArticleForDeletion(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand return } - content, err := b.ConvertToHTML(article.Content) + contentBytes, err := os.ReadFile(article.Link) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + content, err := b.ConvertToHTML(string(contentBytes)) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -582,7 +620,7 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - feed, err := b.GenerateRSS(db, c.Title, c.Link, c.Description) + feed, err := b.GenerateRSS(c, db) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/cmd/frontend/issues.go b/cmd/frontend/issues.go index 5361e48..b1c7258 100644 --- a/cmd/frontend/issues.go +++ b/cmd/frontend/issues.go @@ -1,10 +1,18 @@ package frontend import ( + "fmt" "html/template" + "io" "log" + "mime" "net/http" + "os" + "path/filepath" + "strings" + "time" + "github.com/google/uuid" b "streifling.com/jason/cpolis/cmd/backend" ) @@ -15,14 +23,169 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun return } + session.Values["article"] = nil + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if session.Values["issue-image"] == nil { + err := "error: Image required" + log.Println(err) + http.Error(w, err, http.StatusBadRequest) + return + } + + imgFileName := session.Values["issue-image"].(string) + imgAbsName := fmt.Sprint(c.PicsDir, "/", imgFileName) + + imgFile, err := os.Open(imgAbsName) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer imgFile.Close() + + imgInfo, err := imgFile.Stat() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + imgSize := imgInfo.Size() + mimeType := mime.TypeByExtension(filepath.Ext(imgAbsName)) + + article := &b.Article{ + Title: "Autogenerated cpolis Issue Article", + EncURL: fmt.Sprint("http://", c.Domain, c.Port, "/image/serve/", imgFileName), + EncLength: int(imgSize), + EncType: mimeType, + Published: true, + Rejected: false, + Created: time.Now(), + AuthorID: session.Values["id"].(int64), + } + fmt.Println(article.Link) + + article.ID, err = db.AddArticle(article) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md") + if err = os.WriteFile(articleAbsName, []byte(r.PostFormValue("article-content")), 0644); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + article.Link = fmt.Sprint("http://", c.Domain, c.Port, "/article/serve/", article.ID) + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: article.ID, AttName: "link", Value: article.Link}); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = db.UpdateAttributes(&b.Attribute{Table: "articles", ID: article.ID, AttName: "link", Value: article.Link}); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = db.AddArticleToCurrentIssue(article.ID); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err := db.PublishLatestIssue(); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } + feed, err := b.GenerateRSS(c, db) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err = b.SaveRSS(c.RSSFile, feed); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + session.Values["issue-image"] = nil + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) } } + +func UploadIssueImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + + if err := r.ParseMultipartForm(10 << 20); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, header, err := r.FormFile("issue-image") + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() + + nameStrings := strings.Split(header.Filename, ".") + extension := "." + nameStrings[len(nameStrings)-1] + filename := fmt.Sprint(uuid.New(), extension) + absFilepath, err := filepath.Abs(fmt.Sprint(c.PicsDir, "/", filename)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + img, err := os.Create(absFilepath) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer img.Close() + + if _, err = io.Copy(img, file); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + session.Values["issue-image"] = filename + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } +} diff --git a/cmd/frontend/sessions.go b/cmd/frontend/sessions.go index 514d9cf..3a53a28 100644 --- a/cmd/frontend/sessions.go +++ b/cmd/frontend/sessions.go @@ -60,7 +60,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { id, ok := db.GetID(userName) if !ok { - http.Error(w, fmt.Sprintf("no such user: %v", userName), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("no such user: %v", userName), http.StatusBadRequest) return } @@ -96,7 +96,6 @@ func Logout(c *b.Config, s *b.CookieStore) http.HandlerFunc { } session.Options.MaxAge = -1 - if err = session.Save(r, w); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/cmd/frontend/users.go b/cmd/frontend/users.go index 3eefa7f..848a033 100644 --- a/cmd/frontend/users.go +++ b/cmd/frontend/users.go @@ -198,10 +198,6 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if _, err := getSession(w, r, c, s); err != nil { - return - } - var err error htmlData := UserData{ User: &b.User{ diff --git a/cmd/main.go b/cmd/main.go index 7bb9a61..79d7b32 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -49,41 +49,43 @@ func main() { http.FileServer(http.Dir(config.WebDir+"/static/")))) mux.HandleFunc("/", f.HomePage(config, db, store)) - mux.HandleFunc("GET /create-tag", f.CreateTag(config, store)) - mux.HandleFunc("GET /create-user", f.CreateUser(config, store)) - mux.HandleFunc("GET /delete-article/{id}", f.DeleteArticle(config, db, store)) - mux.HandleFunc("GET /delete-user/{id}", f.DeleteUser(config, db, store)) - mux.HandleFunc("GET /edit-self", f.EditSelf(config, db, store)) - mux.HandleFunc("GET /edit-user/{id}", f.EditUser(config, db, store)) + mux.HandleFunc("GET /article/all-published", f.ShowPublishedArticles(config, db, store)) + mux.HandleFunc("GET /article/all-rejected", f.ShowRejectedArticles(config, db, store)) + mux.HandleFunc("GET /article/all-unpublished", f.ShowUnpublishedArticles(config, db, store)) + mux.HandleFunc("GET /article/delete/{id}", f.DeleteArticle(config, db, store)) + mux.HandleFunc("GET /article/publish/{id}", f.PublishArticle(config, db, store)) + mux.HandleFunc("GET /article/reject/{id}", f.RejectArticle(config, db, store)) + mux.HandleFunc("GET /article/review-deletion/{id}", f.ReviewArticleForDeletion(config, db, store)) + mux.HandleFunc("GET /article/review-rejected/{id}", f.ReviewRejectedArticle(config, db, store)) + mux.HandleFunc("GET /article/review-unpublished/{id}", f.ReviewUnpublishedArticle(config, db, store)) + mux.HandleFunc("GET /article/serve/{id}", c.ServeArticle(config, db)) + mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) + mux.HandleFunc("GET /image/serve/{pic}", c.ServeImage(config, store)) + mux.HandleFunc("GET /issue/this", f.ShowCurrentArticles(config, db, store)) mux.HandleFunc("GET /logout", f.Logout(config, store)) mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) - mux.HandleFunc("GET /pdf/{id}", c.ServePDF(config)) - mux.HandleFunc("GET /pics/{pic}", f.ServeImage(config, store)) - mux.HandleFunc("GET /publish-article/{id}", f.PublishArticle(config, db, store)) - mux.HandleFunc("GET /publish-issue", f.PublishLatestIssue(config, db, store)) - mux.HandleFunc("GET /published-articles", f.ShowPublishedArticles(config, db, store)) - mux.HandleFunc("GET /reject-article/{id}", f.RejectArticle(config, db, store)) - mux.HandleFunc("GET /rejected-articles", f.ShowRejectedArticles(config, db, store)) - mux.HandleFunc("GET /review-article-for-deletion/{id}", f.ReviewArticleForDeletion(config, db, store)) - mux.HandleFunc("GET /review-rejected-article/{id}", f.ReviewRejectedArticle(config, db, store)) - mux.HandleFunc("GET /review-unpublished-article/{id}", f.ReviewUnpublishedArticle(config, db, store)) - mux.HandleFunc("GET /rss", c.ServeRSS(config)) - mux.HandleFunc("GET /show-all-users-edit", f.ShowAllUsers(config, db, store, "edit-user")) - mux.HandleFunc("GET /show-all-users-delete", f.ShowAllUsers(config, db, store, "delete-user")) - mux.HandleFunc("GET /this-issue", f.ShowCurrentArticles(config, db, store)) - mux.HandleFunc("GET /unpublished-articles", f.ShowUnpublishedArticles(config, db, store)) - mux.HandleFunc("GET /write-article", f.WriteArticle(config, db, store)) + mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config)) + mux.HandleFunc("GET /rss/serve", c.ServeRSS(config)) + mux.HandleFunc("GET /tag/create", f.CreateTag(config, store)) + mux.HandleFunc("GET /user/create", f.CreateUser(config, store)) + mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store)) + mux.HandleFunc("GET /user/edit/{id}", f.EditUser(config, db, store)) + mux.HandleFunc("GET /user/edit/self", f.EditSelf(config, db, store)) + mux.HandleFunc("GET /user/show-all/delete", f.ShowAllUsers(config, db, store, "delete")) + mux.HandleFunc("GET /user/show-all/edit", f.ShowAllUsers(config, db, store, "edit")) - mux.HandleFunc("POST /add-first-user", f.AddFirstUser(config, db, store)) - mux.HandleFunc("POST /add-tag", f.AddTag(config, db, store)) - mux.HandleFunc("POST /add-user", f.AddUser(config, db, store)) + mux.HandleFunc("POST /article/resubmit/{id}", f.ResubmitArticle(config, db, store)) + mux.HandleFunc("POST /article/submit", f.SubmitArticle(config, db, store)) + mux.HandleFunc("POST /article/upload-image", f.UploadArticleImage(config, store)) + mux.HandleFunc("POST /issue/publish", f.PublishLatestIssue(config, db, store)) + mux.HandleFunc("POST /issue/upload-image", f.UploadIssueImage(config, store)) mux.HandleFunc("POST /login", f.Login(config, db, store)) - mux.HandleFunc("POST /resubmit-article/{id}", f.ResubmitArticle(config, db, store)) - mux.HandleFunc("POST /submit-article", f.SubmitArticle(config, db, store)) - mux.HandleFunc("POST /update-self", f.UpdateSelf(config, db, store)) - mux.HandleFunc("POST /update-user/{id}", f.UpdateUser(config, db, store)) - mux.HandleFunc("POST /upload-image", f.UploadImage(config, store)) + mux.HandleFunc("POST /tag/add", f.AddTag(config, db, store)) + mux.HandleFunc("POST /user/add", f.AddUser(config, db, store)) + mux.HandleFunc("POST /user/add-first", f.AddFirstUser(config, db, store)) + mux.HandleFunc("POST /user/update/{id}", f.UpdateUser(config, db, store)) + mux.HandleFunc("POST /user/update/self", f.UpdateSelf(config, db, store)) log.Fatalln(http.ListenAndServe(config.Port, mux)) } diff --git a/create_db.sql b/create_db.sql index 1a0f873..23eb491 100644 --- a/create_db.sql +++ b/create_db.sql @@ -11,13 +11,13 @@ CREATE TABLE users ( first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, role INT NOT NULL, - PRIMARY KEY(id) + PRIMARY KEY (id) ); CREATE TABLE issues ( id INT AUTO_INCREMENT, published BOOL NOT NULL, - PRIMARY KEY(id) + PRIMARY KEY (id) ); CREATE TABLE articles ( @@ -25,26 +25,29 @@ CREATE TABLE articles ( title VARCHAR(255) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, description TEXT NOT NULL, - content TEXT NOT NULL, + link VARCHAR(255), + enc_url VARCHAR(255), + enc_length INT, + enc_type VARCHAR(255), published BOOL NOT NULL, rejected BOOL NOT NULL, author_id INT NOT NULL, issue_id INT NOT NULL, - PRIMARY KEY(id), - FOREIGN KEY(author_id) REFERENCES users(id), - FOREIGN KEY(issue_id) REFERENCES issues(id) + PRIMARY KEY (id), + FOREIGN KEY (author_id) REFERENCES users (id), + FOREIGN KEY (issue_id) REFERENCES issues (id) ); CREATE TABLE tags ( id INT AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, - PRIMARY KEY(id) + PRIMARY KEY (id) ); CREATE TABLE articles_tags ( article_id INT, tag_id INT, - PRIMARY KEY(article_id, tag_id), - FOREIGN KEY(article_id) REFERENCES articles(id), - FOREIGN KEY(tag_id) REFERENCES tags(id) + PRIMARY KEY (article_id, tag_id), + FOREIGN KEY (article_id) REFERENCES articles (id), + FOREIGN KEY (tag_id) REFERENCES tags (id) ); diff --git a/go.mod b/go.mod index f50229c..c4f0419 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.0 require ( firebase.google.com/go/v4 v4.14.1 - git.streifling.com/jason/rss v0.1.2 + git.streifling.com/jason/rss v0.1.3 github.com/BurntSushi/toml v1.3.2 github.com/go-sql-driver/mysql v1.7.1 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index d2d9396..2ded0a3 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJah cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= -git.streifling.com/jason/rss v0.1.2 h1:UB3UHJXMt5WDDh9y8n0Z6nS1XortbPXjEr7QZTdovY4= -git.streifling.com/jason/rss v0.1.2/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg= +git.streifling.com/jason/rss v0.1.3 h1:fd3j4ZtcLehapcmmroo3AP3X34gRHC4xzpfV6bDV1ZU= +git.streifling.com/jason/rss v0.1.3/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= diff --git a/web/templates/add-tag.html b/web/templates/add-tag.html index e79865a..29532af 100644 --- a/web/templates/add-tag.html +++ b/web/templates/add-tag.html @@ -4,7 +4,7 @@
diff --git a/web/templates/add-user.html b/web/templates/add-user.html index 10376e1..a3c9cf7 100644 --- a/web/templates/add-user.html +++ b/web/templates/add-user.html @@ -45,7 +45,7 @@{{.Description}}
+© 2024 Jason Streifling. Alle Rechte vorbehalten.
++ Hinweis: Diese Software befindet sich noch in der Entwicklung und kann Fehler enthalten. +
diff --git a/web/templates/published-articles.html b/web/templates/published-articles.html index b2d04cf..4a12863 100644 --- a/web/templates/published-articles.html +++ b/web/templates/published-articles.html @@ -3,7 +3,7 @@{{.Description}}
{{.Description}}