Switch to atom feed

This commit is contained in:
2024-10-27 07:20:23 +01:00
parent 2751a8c972
commit 19b390cbbb
18 changed files with 358 additions and 190 deletions

View File

@ -11,15 +11,13 @@ import (
type Article struct {
Created time.Time
Title string
Description string
Link string
EncURL string
EncType string
BannerLink string
Summary string
ContentLink string
ID int64
AuthorID int64
IssueID int64
EditedID int64
EncLength int
Published bool
Rejected bool
IsInIssue bool
@ -32,9 +30,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
selectQuery := "SELECT id FROM issues WHERE published = false"
insertQuery := `
INSERT INTO articles
(title, description, link, enc_url, enc_length, enc_type, published,
rejected, author_id, issue_id, edited_id, is_in_issue, auto_generated)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(title, banner_link, summary, content_link, published, rejected, author_id, issue_id, edited_id, is_in_issue, auto_generated)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
for i := 0; i < TxMaxRetries; i++ {
@ -51,9 +48,7 @@ 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.Link,
a.EncURL, a.EncLength, a.EncType, a.Published, a.Rejected, a.AuthorID, id,
a.EditedID, a.IsInIssue, a.AutoGenerated)
result, err := tx.Exec(insertQuery, a.Title, a.BannerLink, a.Summary, a.ContentLink, a.Published, a.Rejected, a.AuthorID, id, a.EditedID, a.IsInIssue, a.AutoGenerated)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
@ -87,9 +82,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
func (db *DB) GetArticle(id int64) (*Article, error) {
query := `
SELECT
title, created, description, link, enc_url, enc_length, enc_type,
published, author_id, issue_id, edited_id, is_in_issue, auto_generated
SELECT title, created, banner_link, summary, content_link, published, author_id, issue_id, edited_id, is_in_issue, auto_generated
FROM articles
WHERE id = ?
`
@ -99,10 +92,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
var created []byte
var err error
if err := row.Scan(&article.Title, &created, &article.Description,
&article.Link, &article.EncURL, &article.EncLength, &article.EncType,
&article.Published, &article.AuthorID, &article.IssueID, &article.EditedID,
&article.IsInIssue, &article.AutoGenerated); err != nil {
if err := row.Scan(&article.Title, &created, &article.BannerLink, &article.Summary, &article.ContentLink, &article.Published, &article.AuthorID, &article.IssueID, &article.EditedID, &article.IsInIssue, &article.AutoGenerated); err != nil {
return nil, fmt.Errorf("error scanning article row: %v", err)
}
@ -117,9 +107,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, error) {
query := fmt.Sprintf(`
SELECT
id, title, created, description, link, enc_url, enc_length, enc_type,
author_id, issue_id, published, rejected, is_in_issue, auto_generated
SELECT id, title, created, banner_link, summary, content_link, author_id, issue_id, published, rejected, is_in_issue, auto_generated
FROM articles
WHERE %s = ?
`, attribute)
@ -133,10 +121,7 @@ func (db *DB) GetCertainArticles(attribute string, value bool) ([]*Article, erro
article := new(Article)
var created []byte
if err = rows.Scan(&article.ID, &article.Title, &created,
&article.Description, &article.Link, &article.EncURL, &article.EncLength,
&article.EncType, &article.AuthorID, &article.IssueID, &article.Published,
&article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil {
if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.ContentLink, &article.AuthorID, &article.IssueID, &article.Published, &article.Rejected, &article.IsInIssue, &article.AutoGenerated); err != nil {
return nil, fmt.Errorf("error scanning article row: %v", err)
}
@ -156,9 +141,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, link, enc_url, enc_length, enc_type,
author_id, auto_generated
SELECT id, title, created, banner_link, summary, content_link, author_id, auto_generated
FROM articles
WHERE issue_id = ? AND published = true AND is_in_issue = true
`
@ -191,9 +174,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
article := new(Article)
var created []byte
if err = rows.Scan(&article.ID, &article.Title, &created,
&article.Description, &article.Link, &article.EncURL, &article.EncLength,
&article.EncType, &article.AuthorID, &article.AutoGenerated); err != nil {
if err = rows.Scan(&article.ID, &article.Title, &created, &article.BannerLink, &article.Summary, &article.ContentLink, &article.AuthorID, &article.AutoGenerated); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}

103
cmd/backend/atom.go Normal file
View File

@ -0,0 +1,103 @@
package backend
import (
"fmt"
"io"
"os"
"git.streifling.com/jason/atom"
)
type Feed struct{ *atom.Feed }
func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
feed := atom.NewFeed(c.Title)
feed.ID = atom.NewID("urn:feed:1")
feed.Subtitle = atom.NewText("text", c.Description)
feed.AddLink(atom.NewLink(c.Link))
feed.Generator = atom.NewGenerator("cpolis")
feed.Generator.URI = "https://git.streifling.com/jason/cpolis"
feed.Generator.Version = "0.13"
articles, err := db.GetCertainArticles("published", true)
if err != nil {
return nil, fmt.Errorf("error getting published articles for RSS feed: %v", err)
}
for _, article := range articles {
articleTitle, err := ConvertToPlain(article.Title)
if err != nil {
return nil, fmt.Errorf("error converting title to plain text for RSS feed: %v", err)
}
entry := atom.NewEntry(articleTitle)
entry.ID = atom.NewID(fmt.Sprint("urn:entry:", article.ID))
entry.Content = atom.NewContent(atom.OutOfLine, "text/hmtl", article.ContentLink)
entry.Published = atom.NewDate(article.Created)
linkID := entry.AddLink(atom.NewLink(article.BannerLink))
entry.Links[linkID].Rel = "enclosure"
entry.Links[linkID].Type = "image/webp"
user, err := db.GetUser(article.AuthorID)
if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err)
}
entry.AddAuthor(atom.NewPerson(user.FirstName + " " + user.LastName))
articleSummary, err := ConvertToPlain(article.Summary)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
}
if article.AutoGenerated {
articleSummary = "auto generated"
}
entry.Summary = atom.NewText("text", articleSummary)
tags, err := db.GetArticleTags(article.ID)
if err != nil {
return nil, fmt.Errorf("error getting tags for articles for RSS feed: %v", err)
}
for _, tag := range tags {
entry.AddCategory(atom.NewCategory(tag.Name))
}
if article.IsInIssue || article.AutoGenerated {
entry.AddCategory(atom.NewCategory(fmt.Sprint("Orient Express ", article.IssueID)))
}
if article.AutoGenerated {
entry.AddCategory(atom.NewCategory("autogenerated"))
}
entry.Updated = atom.NewDate(article.Created)
feed.AddEntry(entry)
}
if err = feed.Check(); err != nil {
return nil, fmt.Errorf("error checking Atom feed: %v", err)
}
atom, err := feed.ToXML("UTF-8")
if err != nil {
return nil, fmt.Errorf("error converting RSS feed to XML: %v", err)
}
return &atom, nil
}
func SaveAtomFeed(filename string, feed *string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating file for Atom feed: %v", err)
}
defer file.Close()
if err = file.Chmod(0644); err != nil {
return fmt.Errorf("error setting permissions for Atom file: %v", err)
}
if _, err = io.WriteString(file, *feed); err != nil {
return fmt.Errorf("error writing to Atom file: %v", err)
}
return nil
}

View File

@ -17,6 +17,7 @@ type Config struct {
DBName string
Description string
Domain string
AtomFeed string
FirebaseKey string
KeyFile string
Link string
@ -36,6 +37,7 @@ func newConfig() *Config {
ArticleDir: "/var/www/cpolis/articles",
ConfigFile: "/etc/cpolis/config.toml",
DBName: "cpolis",
AtomFeed: "/var/www/cpolis/cpolis.atom",
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
KeyFile: "/var/www/cpolis/cpolis.key",
LogFile: "/var/log/cpolis.log",
@ -101,6 +103,7 @@ func (c *Config) handleCliArgs() error {
var err error
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file")
flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
flag.StringVar(&c.Description, "desc", c.Description, "channel description")
@ -163,6 +166,18 @@ func (c *Config) setupConfig(cliConfig *Config) error {
if cliConfig.Domain != defaultConfig.Domain {
c.Domain = cliConfig.Domain
}
domainStrings := strings.Split(c.Domain, "/")
if domainStrings[0] != "http:" && domainStrings[0] != "https:" {
c.Domain = "https://" + c.Domain
}
if cliConfig.AtomFeed != defaultConfig.AtomFeed {
c.AtomFeed = cliConfig.AtomFeed
}
c.AtomFeed, err = mkFile(c.AtomFeed, 0644, 0744)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.FirebaseKey != defaultConfig.FirebaseKey {
c.FirebaseKey = cliConfig.FirebaseKey

View File

@ -49,7 +49,7 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
return nil, fmt.Errorf("error converting title to plain text for RSS feed: %v", err)
}
articleDescription, err := ConvertToPlain(article.Description)
articleDescription, err := ConvertToPlain(article.Summary)
if err != nil {
return nil, fmt.Errorf("error converting description to plain text for RSS feed: %v", err)
}
@ -59,19 +59,19 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
Categories: tagNames,
Description: articleDescription,
Guid: string(article.ID),
Link: article.Link,
Link: article.ContentLink,
PubDate: article.Created.Format(time.RFC1123Z),
Title: articleTitle,
}
if article.AutoGenerated {
item.Enclosure = &rss.Enclosure{
Url: article.EncURL,
Lenght: article.EncLength,
Type: article.EncType,
}
}
// if article.AutoGenerated {
// item.Enclosure = &rss.Enclosure{
// Url: article.EncURL,
// Lenght: article.EncLength,
// Type: article.EncType,
// }
// }
//
channel.Items = append(channel.Items, item)
}

View File

@ -45,6 +45,7 @@ func LoadKey(filename string) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("error opening key file: %v", err)
}
defer file.Close()
key := make([]byte, 32)
if err = gob.NewDecoder(file).Decode(&key); err != nil {