add rss feed #9
This commit is contained in:
@@ -9,12 +9,14 @@ import (
|
|||||||
type App struct {
|
type App struct {
|
||||||
articles *database.ArticleRepository
|
articles *database.ArticleRepository
|
||||||
articleVMs *database.ArticleViewModelRepository
|
articleVMs *database.ArticleViewModelRepository
|
||||||
|
rssItems *database.RSSItemRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(db *sql.DB) *App {
|
func NewApp(db *sql.DB) *App {
|
||||||
return &App{
|
return &App{
|
||||||
articles: &database.ArticleRepository{DB: db},
|
articles: &database.ArticleRepository{DB: db},
|
||||||
articleVMs: &database.ArticleViewModelRepository{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()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// dynamic routes
|
// dynamic routes
|
||||||
|
mux.Handle("GET /rss.xml", http.HandlerFunc(app.RSS))
|
||||||
|
|
||||||
mux.Handle("GET /", http.HandlerFunc(app.Index))
|
mux.Handle("GET /", http.HandlerFunc(app.Index))
|
||||||
mux.Handle("GET /page/{id}", http.HandlerFunc(app.Index))
|
mux.Handle("GET /page/{id}", http.HandlerFunc(app.Index))
|
||||||
mux.Handle("POST /up/search", http.HandlerFunc(app.UpSearch))
|
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