Instead of having entire articles in the RSS feed, items now contain just a link
This commit is contained in:
parent
4a11e1a497
commit
3f1b18c29f
@ -4,6 +4,7 @@ tmp_dir = "tmp"
|
|||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = [
|
args_bin = [
|
||||||
|
"-articles tmp/articles",
|
||||||
"-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'",
|
"-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'",
|
||||||
"-domain localhost",
|
"-domain localhost",
|
||||||
"-key tmp/key.gob",
|
"-key tmp/key.gob",
|
||||||
|
@ -13,6 +13,7 @@ type Article struct {
|
|||||||
Created time.Time
|
Created time.Time
|
||||||
Description string
|
Description string
|
||||||
Content string
|
Content string
|
||||||
|
Link string
|
||||||
Published bool
|
Published bool
|
||||||
Rejected bool
|
Rejected bool
|
||||||
ID int64
|
ID int64
|
||||||
@ -26,8 +27,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
|||||||
selectQuery := "SELECT id FROM issues WHERE published = false"
|
selectQuery := "SELECT id FROM issues WHERE published = false"
|
||||||
insertQuery := `
|
insertQuery := `
|
||||||
INSERT INTO articles
|
INSERT INTO articles
|
||||||
(title, description, content, published, rejected, author_id, issue_id)
|
(title, description, content, link, published, rejected, author_id, issue_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
for i := 0; i < TxMaxRetries; i++ {
|
for i := 0; i < TxMaxRetries; i++ {
|
||||||
@ -45,7 +46,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := tx.Exec(insertQuery, a.Title, a.Description,
|
result, err := tx.Exec(insertQuery, a.Title, a.Description,
|
||||||
a.Content, a.Published, a.Rejected, a.AuthorID, id)
|
a.Content, a.Link, a.Published, a.Rejected, a.AuthorID, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||||
@ -79,7 +80,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
|||||||
|
|
||||||
func (db *DB) GetArticle(id int64) (*Article, error) {
|
func (db *DB) GetArticle(id int64) (*Article, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT title, created, description, content, published, author_id
|
SELECT title, created, description, content, link, published, author_id
|
||||||
FROM articles
|
FROM articles
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`
|
`
|
||||||
@ -90,7 +91,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if err := row.Scan(&article.Title, &created, &article.Description,
|
if err := row.Scan(&article.Title, &created, &article.Description,
|
||||||
&article.Content, &article.Published, &article.AuthorID); err != nil {
|
&article.Content, &article.Link, &article.Published, &article.AuthorID); err != nil {
|
||||||
return nil, fmt.Errorf("error scanning article row: %v", err)
|
return nil, fmt.Errorf("error scanning article row: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
|
|||||||
|
|
||||||
func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
|
func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT id, title, created, description, content, author_id, issue_id
|
SELECT id, title, created, description, content, link, author_id, issue_id
|
||||||
FROM articles
|
FROM articles
|
||||||
WHERE published = ?
|
WHERE published = ?
|
||||||
AND rejected = ?
|
AND rejected = ?
|
||||||
@ -121,8 +122,8 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
|
|||||||
var created []byte
|
var created []byte
|
||||||
|
|
||||||
if err = rows.Scan(&article.ID, &article.Title, &created,
|
if err = rows.Scan(&article.ID, &article.Title, &created,
|
||||||
&article.Description, &article.Content, &article.AuthorID,
|
&article.Description, &article.Content, &article.Link,
|
||||||
&article.IssueID); err != nil {
|
&article.AuthorID, &article.IssueID); err != nil {
|
||||||
return nil, fmt.Errorf("error scanning article row: %v", err)
|
return nil, fmt.Errorf("error scanning article row: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
|
|||||||
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
|
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
|
||||||
issueQuery := "SELECT id FROM issues WHERE published = false"
|
issueQuery := "SELECT id FROM issues WHERE published = false"
|
||||||
articlesQuery := `
|
articlesQuery := `
|
||||||
SELECT id, title, created, description, content, author_id
|
SELECT id, title, created, description, content, link, author_id
|
||||||
FROM articles
|
FROM articles
|
||||||
WHERE issue_id = ? AND published = true
|
WHERE issue_id = ? AND published = true
|
||||||
`
|
`
|
||||||
@ -177,7 +178,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
|
|||||||
var created []byte
|
var created []byte
|
||||||
|
|
||||||
if err = rows.Scan(&article.ID, &article.Title, &created,
|
if err = rows.Scan(&article.ID, &article.Title, &created,
|
||||||
&article.Description, &article.Content, &article.AuthorID); err != nil {
|
&article.Description, &article.Content, &article.Link, &article.AuthorID); err != nil {
|
||||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
ArticleDir string
|
||||||
DBName string
|
DBName string
|
||||||
Description string
|
Description string
|
||||||
Domain string
|
Domain string
|
||||||
@ -28,6 +29,7 @@ type Config struct {
|
|||||||
|
|
||||||
func newConfig() *Config {
|
func newConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
|
ArticleDir: "/var/www/cpolis/articles",
|
||||||
DBName: "cpolis",
|
DBName: "cpolis",
|
||||||
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
|
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
|
||||||
KeyFile: "/var/www/cpolis/cpolis.key",
|
KeyFile: "/var/www/cpolis/cpolis.key",
|
||||||
@ -77,6 +79,7 @@ func (c *Config) handleCliArgs() error {
|
|||||||
var err error
|
var err error
|
||||||
port := 8080
|
port := 8080
|
||||||
|
|
||||||
|
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
|
||||||
flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
|
flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
|
||||||
flag.StringVar(&c.Description, "desc", c.Description, "channel description")
|
flag.StringVar(&c.Description, "desc", c.Description, "channel description")
|
||||||
flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
|
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.IntVar(&port, "port", port, "port")
|
||||||
flag.Parse()
|
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)
|
c.FirebaseKey, err = filepath.Abs(c.FirebaseKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding absolute path for Firebase service account key file: %v", err)
|
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)
|
c.PDFDir, err = filepath.Abs(c.PDFDir)
|
||||||
if err != nil {
|
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)
|
c.PicsDir, err = filepath.Abs(c.PicsDir)
|
||||||
if err != nil {
|
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)
|
c.Port = fmt.Sprint(":", port)
|
||||||
@ -126,7 +143,10 @@ func (c *Config) handleCliArgs() error {
|
|||||||
|
|
||||||
c.WebDir, err = filepath.Abs(c.WebDir)
|
c.WebDir, err = filepath.Abs(c.WebDir)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
|
@ -39,7 +39,7 @@ func GetChannel(db *DB, title, link, description string) (*rss.Channel, error) {
|
|||||||
|
|
||||||
channel.Items = append(channel.Items, &rss.Item{
|
channel.Items = append(channel.Items, &rss.Item{
|
||||||
Title: article.Title,
|
Title: article.Title,
|
||||||
Author: user.FirstName + user.LastName,
|
Author: fmt.Sprint(user.FirstName, " ", user.LastName),
|
||||||
PubDate: article.Created.Format(time.RFC1123Z),
|
PubDate: article.Created.Format(time.RFC1123Z),
|
||||||
Description: article.Description,
|
Description: article.Description,
|
||||||
Content: &rss.Content{Value: article.Content},
|
Content: &rss.Content{Value: article.Content},
|
||||||
@ -89,14 +89,22 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
|
|||||||
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
|
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.Items = append(channel.Items, &rss.Item{
|
item := &rss.Item{
|
||||||
Author: fmt.Sprint(user.FirstName, user.LastName),
|
Author: fmt.Sprint(user.FirstName, " ", user.LastName),
|
||||||
Categories: tagNames,
|
Categories: tagNames,
|
||||||
Description: articleDescription,
|
Description: articleDescription,
|
||||||
Link: fmt.Sprintf("http://%s/article/serve/%d", c.Domain, article.ID),
|
|
||||||
PubDate: article.Created.Format(time.RFC1123Z),
|
PubDate: article.Created.Format(time.RFC1123Z),
|
||||||
Title: articleTitle,
|
Title: articleTitle,
|
||||||
})
|
}
|
||||||
|
fmt.Println(article.Link, ": ", len(article.Link))
|
||||||
|
|
||||||
|
if article.Link == "" {
|
||||||
|
item.Link = fmt.Sprint("http://", c.Domain, "/article/serve/", article.ID, ".html")
|
||||||
|
} else {
|
||||||
|
item.Link = article.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.Items = append(channel.Items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
feed := rss.NewFeed()
|
feed := rss.NewFeed()
|
||||||
|
@ -11,23 +11,25 @@ import (
|
|||||||
|
|
||||||
func ServeArticle(c *b.Config, db *b.DB) http.HandlerFunc {
|
func ServeArticle(c *b.Config, db *b.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if tokenIsVerified(w, r, c) {
|
if !tokenIsVerified(w, r, c) {
|
||||||
idString := r.PathValue("id")
|
return
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprint(w, article.Content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, article.Content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,17 @@ import (
|
|||||||
|
|
||||||
func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if tokenIsVerified(w, r, c) {
|
if !tokenIsVerified(w, r, c) {
|
||||||
absFilepath, err := filepath.Abs(c.PicsDir)
|
return
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.ServeFile(w, r, absFilepath+"/"+r.PathValue("pic"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
absFilepath, err := filepath.Abs(c.PicsDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeFile(w, r, absFilepath+"/"+r.PathValue("pic"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,33 +12,37 @@ import (
|
|||||||
|
|
||||||
func ServePDFList(c *b.Config) http.HandlerFunc {
|
func ServePDFList(c *b.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if tokenIsVerified(w, r, c) {
|
if !tokenIsVerified(w, r, c) {
|
||||||
files, err := os.ReadDir(c.PDFDir)
|
return
|
||||||
if err != nil {
|
}
|
||||||
log.Println(err)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileNames := make([]string, 0)
|
files, err := os.ReadDir(c.PDFDir)
|
||||||
for _, file := range files {
|
if err != nil {
|
||||||
fileNames = append(fileNames, file.Name())
|
log.Println(err)
|
||||||
}
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
fileNames := make([]string, 0)
|
||||||
if err = json.NewEncoder(w).Encode(fileNames); err != nil {
|
for _, file := range files {
|
||||||
log.Println(err)
|
fileNames = append(fileNames, file.Name())
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServePDF(c *b.Config) http.HandlerFunc {
|
func ServePDF(c *b.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if tokenIsVerified(w, r, c) {
|
if !tokenIsVerified(w, r, c) {
|
||||||
http.ServeFile(w, r, fmt.Sprint(c.PDFDir, "/", r.PathValue("id")))
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http.ServeFile(w, r, fmt.Sprint(c.PDFDir, "/", r.PathValue("id")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ import (
|
|||||||
|
|
||||||
func ServeRSS(c *b.Config) http.HandlerFunc {
|
func ServeRSS(c *b.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if tokenIsVerified(w, r, c) {
|
if !tokenIsVerified(w, r, c) {
|
||||||
http.ServeFile(w, r, c.RSSFile)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http.ServeFile(w, r, c.RSSFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -347,6 +346,27 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article, err := db.GetArticle(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := b.ConvertToHTML(article.Content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprint(c.ArticleDir, "/", article.ID, ".html"), []byte(content), 0444)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = db.AddArticleToCurrentIssue(id); err != nil {
|
if err = db.AddArticleToCurrentIssue(id); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
@ -427,7 +447,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) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, err := getSession(w, r, c, s); err != nil {
|
if _, err := getSession(w, r, c, s); err != nil {
|
||||||
return
|
return
|
||||||
@ -436,7 +456,7 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
|||||||
file, header, err := r.FormFile("article-image")
|
file, header, err := r.FormFile("article-image")
|
||||||
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.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@ -451,12 +471,6 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
|||||||
return
|
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)
|
img, err := os.Create(absFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -471,7 +485,7 @@ func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprint(c.Domain, "/pics/", filename)
|
url := fmt.Sprint(c.Domain, "/image/serve/", filename)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(url)
|
json.NewEncoder(w).Encode(url)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
b "streifling.com/jason/cpolis/cmd/backend"
|
b "streifling.com/jason/cpolis/cmd/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,14 +22,141 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
|
|||||||
return
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
article := &b.Article{
|
||||||
|
Title: "Autogenerated Issue Article",
|
||||||
|
Content: r.PostFormValue("issue-content"),
|
||||||
|
Link: session.Values["issue-image"].(string),
|
||||||
|
Published: true,
|
||||||
|
Rejected: false,
|
||||||
|
Created: time.Now(),
|
||||||
|
AuthorID: session.Values["id"].(int64),
|
||||||
|
}
|
||||||
|
fmt.Println(article.Link)
|
||||||
|
|
||||||
|
content, err := b.ConvertToHTML(article.Content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprint(c.ArticleDir, "/", article.ID, ".html"), []byte(content), 0444)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
article.ID, err = db.AddArticle(article)
|
||||||
|
if 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 {
|
if err := db.PublishLatestIssue(); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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, err := template.ParseFiles(c.WebDir + "/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"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -60,7 +60,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
|||||||
|
|
||||||
id, ok := db.GetID(userName)
|
id, ok := db.GetID(userName)
|
||||||
if !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,6 @@ func Logout(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
|
|
||||||
if err = session.Save(r, w); err != nil {
|
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)
|
||||||
|
@ -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 {
|
func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, err := getSession(w, r, c, s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
htmlData := UserData{
|
htmlData := UserData{
|
||||||
User: &b.User{
|
User: &b.User{
|
||||||
|
@ -62,7 +62,6 @@ func main() {
|
|||||||
mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store))
|
mux.HandleFunc("GET /article/write", f.WriteArticle(config, db, store))
|
||||||
mux.HandleFunc("GET /hub", f.ShowHub(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 /image/serve/{pic}", c.ServeImage(config, store))
|
||||||
mux.HandleFunc("GET /issue/publish", f.PublishLatestIssue(config, db, store))
|
|
||||||
mux.HandleFunc("GET /issue/this", f.ShowCurrentArticles(config, db, store))
|
mux.HandleFunc("GET /issue/this", f.ShowCurrentArticles(config, db, store))
|
||||||
mux.HandleFunc("GET /logout", f.Logout(config, store))
|
mux.HandleFunc("GET /logout", f.Logout(config, store))
|
||||||
mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config))
|
mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config))
|
||||||
@ -78,7 +77,9 @@ func main() {
|
|||||||
|
|
||||||
mux.HandleFunc("POST /article/resubmit/{id}", f.ResubmitArticle(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/submit", f.SubmitArticle(config, db, store))
|
||||||
mux.HandleFunc("POST /image/upload", f.UploadImage(config, 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 /login", f.Login(config, db, store))
|
||||||
mux.HandleFunc("POST /tag/add", f.AddTag(config, db, 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", f.AddUser(config, db, store))
|
||||||
|
@ -25,7 +25,7 @@ CREATE TABLE articles (
|
|||||||
title VARCHAR(255) NOT NULL,
|
title VARCHAR(255) NOT NULL,
|
||||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
content TEXT NOT NULL,
|
link VARCHAR(255),
|
||||||
published BOOL NOT NULL,
|
published BOOL NOT NULL,
|
||||||
rejected BOOL NOT NULL,
|
rejected BOOL NOT NULL,
|
||||||
author_id INT NOT NULL,
|
author_id INT NOT NULL,
|
||||||
|
@ -1,17 +1,68 @@
|
|||||||
{{define "page-content"}}
|
{{define "page-content"}}
|
||||||
<h2>Aktuelle Artikel</h2>
|
<h2>Diese Ausgabe</h2>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<form hx-encoding="multipart/form-data">
|
||||||
{{range .}}
|
<div class="flex flex-col gap-4">
|
||||||
<div class="border px-2 py-1 rounded-md">
|
<div>
|
||||||
<h1 class="font-bold text-2xl">{{.Title}}</h1>
|
<h3>Aktuelle Artikel</h3>
|
||||||
<p>{{.Description}}</p>
|
<div class="flex flex-col gap-4">
|
||||||
|
{{range .}}
|
||||||
|
<div class="border px-2 py-1 rounded-md">
|
||||||
|
<h1 class="font-bold text-2xl">{{.Title}}</h1>
|
||||||
|
<p>{{.Description}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>Cover</h3>
|
||||||
|
<input id="image-upload" name="issue-image" type="file" required hx-post="/issue/upload-image" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>Über diese Ausgabe</h3>
|
||||||
|
<div>
|
||||||
|
<textarea id="easyMDE" placeholder="Beschreibung dieser Ausgabe"></textarea>
|
||||||
|
<input id="issue-content" name="issue-content" type="hidden" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-area">
|
<div class="btn-area">
|
||||||
<button class="action-btn" hx-get="/issue/publish" hx-target="#page-content">Ausgabe publizieren</button>
|
<button class="action-btn" hx-post="/issue/publish" hx-target="#page-content">Ausgabe publizieren</button>
|
||||||
<button class="btn" hx-get="/hub" hx-target="#page-content">Abbrechen</button>
|
<button class="btn" hx-get="/hub" hx-target="#page-content">Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var easyMDE = new EasyMDE({
|
||||||
|
element: document.getElementById('easyMDE'),
|
||||||
|
hideIcons: ['image'],
|
||||||
|
imageTexts: {sbInit: ''},
|
||||||
|
showIcons: ["code", "table", "upload-image"],
|
||||||
|
uploadImage: true,
|
||||||
|
|
||||||
|
imageUploadFunction: function (file, onSuccess, onError) {
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('article-image', file);
|
||||||
|
|
||||||
|
fetch('/article/upload-image', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
onSuccess(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
onError(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
easyMDE.codemirror.on("change", () => {
|
||||||
|
document.getElementById('issue-content').value = easyMDE.value();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -6,15 +6,17 @@
|
|||||||
<label for="article-title">Titel</label>
|
<label for="article-title">Titel</label>
|
||||||
<input name="article-title" type="text" value="{{.Title}}" />
|
<input name="article-title" type="text" value="{{.Title}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
|
||||||
|
<div class="flex flex-col gap-y-1">
|
||||||
<label for="article-description">Beschreibung</label>
|
<label for="article-description">Beschreibung</label>
|
||||||
<textarea name="article-description">{{.Description}}</textarea>
|
<textarea name="article-description">{{.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
|
||||||
<label for="article-content">Artikel</label>
|
<div class="flex flex-col gap-y-1">
|
||||||
|
<label for="easyMDE">Artikel</label>
|
||||||
<textarea id="easyMDE">{{.Content}}</textarea>
|
<textarea id="easyMDE">{{.Content}}</textarea>
|
||||||
|
<input id="article-content" name="article-content" type="hidden" />
|
||||||
</div>
|
</div>
|
||||||
<input id="article-content" name="article-content" type="hidden" />
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>Tags</span>
|
<span>Tags</span>
|
||||||
@ -46,7 +48,7 @@
|
|||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append('article-image', file);
|
formData.append('article-image', file);
|
||||||
|
|
||||||
fetch('/image/upload', {
|
fetch('/article/upload-image', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
{{if eq . 0}}
|
{{if eq . 0}}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h2>Administrator</h2>
|
<h2>Administrator</h2>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||||
<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer hinzufügen</button>
|
<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer hinzufügen</button>
|
||||||
<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer bearbeiten</button>
|
<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer bearbeiten</button>
|
||||||
<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer löschen</button>
|
<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer löschen</button>
|
||||||
|
@ -6,15 +6,17 @@
|
|||||||
<label for="article-title">Titel</label>
|
<label for="article-title">Titel</label>
|
||||||
<input name="article-title" type="text" value="{{.Article.Title}}" />
|
<input name="article-title" type="text" value="{{.Article.Title}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
|
||||||
|
<div class="flex flex-col gap-y-1">
|
||||||
<label for="article-description">Beschreibung</label>
|
<label for="article-description">Beschreibung</label>
|
||||||
<textarea name="article-description">{{.Article.Description}}</textarea>
|
<textarea name="article-description">{{.Article.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
|
||||||
<label for="article-content">Artikel</label>
|
<div class="flex flex-col gap-y-1">
|
||||||
<textarea name="article-content" placeholder="Artikel">{{.Article.Content}}</textarea>
|
<label for="easyMDE">Artikel</label>
|
||||||
|
<textarea id="easyMDE">{{.Article.Content}}</textarea>
|
||||||
|
<input id="article-content" name="article-content" type="hidden" />
|
||||||
</div>
|
</div>
|
||||||
<input id="article-content" name="article-content" type="hidden" />
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>Tags</span>
|
<span>Tags</span>
|
||||||
@ -37,6 +39,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.getElementById('article-content').value = easyMDE.value();
|
||||||
|
|
||||||
var easyMDE = new EasyMDE({
|
var easyMDE = new EasyMDE({
|
||||||
element: document.getElementById('easyMDE'),
|
element: document.getElementById('easyMDE'),
|
||||||
hideIcons: ['image'],
|
hideIcons: ['image'],
|
||||||
@ -48,7 +52,7 @@
|
|||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append('article-image', file);
|
formData.append('article-image', file);
|
||||||
|
|
||||||
fetch('/image/upload', {
|
fetch('/article/upload-image', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user