add rss feed #9
This commit is contained in:
@@ -9,12 +9,14 @@ import (
|
||||
type App struct {
|
||||
articles *database.ArticleRepository
|
||||
articleVMs *database.ArticleViewModelRepository
|
||||
rssItems *database.RSSItemRepository
|
||||
}
|
||||
|
||||
func NewApp(db *sql.DB) *App {
|
||||
return &App{
|
||||
articles: &database.ArticleRepository{DB: db},
|
||||
articleVMs: &database.ArticleViewModelRepository{DB: db},
|
||||
rssItems: &database.RSSItemRepository{DB: db},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +24,8 @@ func (app *App) Routes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// dynamic routes
|
||||
mux.Handle("GET /rss.xml", http.HandlerFunc(app.RSS))
|
||||
|
||||
mux.Handle("GET /", http.HandlerFunc(app.Index))
|
||||
mux.Handle("GET /page/{id}", http.HandlerFunc(app.Index))
|
||||
mux.Handle("POST /up/search", http.HandlerFunc(app.UpSearch))
|
||||
|
||||
28
src/internal/app/rss.go
Normal file
28
src/internal/app/rss.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"crowsnest/internal/model"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// List the latest articles using the base template.
|
||||
func (app *App) RSS(w http.ResponseWriter, req *http.Request) {
|
||||
// set response headers
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// get articles
|
||||
feed, err := app.rssItems.All(30)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// write RSS feed to response
|
||||
encoder := xml.NewEncoder(w)
|
||||
encoder.Indent("", " ")
|
||||
if err := encoder.Encode(model.RSSFeedFromItems(feed)); err != nil {
|
||||
http.Error(w, "Error generating RSS feed", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
60
src/internal/model/database/rssitemrepository.go
Normal file
60
src/internal/model/database/rssitemrepository.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"crowsnest/internal/model"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RSSItemRepository struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// Gets all the article as view models objects from the database. This may throw
|
||||
// an error if the connection to the database fails.
|
||||
func (m *RSSItemRepository) All(limit int) ([]model.RSSItem, error) {
|
||||
stmt := `
|
||||
SELECT a.id, a.title, a.sourceUrl, a.publishDate, d.summary
|
||||
FROM articles a JOIN documents d ON a.document_id = d.id
|
||||
ORDER BY a.publishDate DESC
|
||||
LIMIT $1
|
||||
`
|
||||
rows, err := m.DB.Query(stmt, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]model.RSSItem, 0, limit)
|
||||
for rows.Next() {
|
||||
i := model.RSSItem{}
|
||||
|
||||
var id int
|
||||
var sourceUrl string
|
||||
var date time.Time
|
||||
|
||||
err := rows.Scan(&id, &i.Title, &sourceUrl, &date, &i.Description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// description
|
||||
if i.Description == "" {
|
||||
i.Description = "N/A"
|
||||
}
|
||||
// format date
|
||||
i.PubDate = date.Format(time.RFC1123Z)
|
||||
// link
|
||||
i.Link = "https://crowsnest.kohout-dev.de/article/" + strconv.Itoa(id)
|
||||
// identifier
|
||||
i.Guid = model.RSSGuid{IsPermaLink: "false", Value: strconv.Itoa(id)}
|
||||
|
||||
items = append(items, i)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
51
src/internal/model/rss.go
Normal file
51
src/internal/model/rss.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RSS represents the RSS feed structure.
|
||||
type RSSFeed struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Version string `xml:"version,attr"`
|
||||
Channel RSSChannel `xml:"channel"`
|
||||
}
|
||||
|
||||
// Channel represents the channel element in the RSS feed.
|
||||
type RSSChannel struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
Items []RSSItem `xml:"item"`
|
||||
}
|
||||
|
||||
// Item represents an individual item in the RSS feed.
|
||||
type RSSItem struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
Guid RSSGuid `xml:"guid"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
}
|
||||
|
||||
type RSSGuid struct {
|
||||
IsPermaLink string `xml:"isPermaLink,attr,omitempty"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func RSSFeedFromItems(items []RSSItem) *RSSFeed {
|
||||
rssChannel := RSSChannel{
|
||||
Title: "crowsnest",
|
||||
Link: "https://crowsnest.kohout-dev.de",
|
||||
Description: "N/A",
|
||||
PubDate: time.Now().Format(time.RFC1123Z),
|
||||
Items: items,
|
||||
}
|
||||
|
||||
return &RSSFeed{
|
||||
Version: "2.0",
|
||||
Channel: rssChannel,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user