diff --git a/README.md b/README.md index df5cfdb..80fa84e 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ MB_LOGO=" ▄▄▄·▪ ▐ ▄ ▄▄▄ .\n▐█ ▄███ •█▌▐ MB_RIGHTS=pine32.be MB_LINK=https://pine32.be MB_MESSAGE="A funny little cycle." +MB_HOST=https://mb.pine32.be ``` ### Compose file @@ -87,6 +88,7 @@ docker compose up -d | `MB_RIGHTS` | Name of the right holder thingy | `mb.dev` just a placeholder | | `MB_LINK` | Link displayed at the top fo the page | `https://mb.dev` link does not exist | | `MB_MESSAGE` | Message displayed at the top of the page | `Created without any JS.` | +| `MB_HOST` | The full host url where the blog is hosted. For ex. `https://mb.pine32.be` | `http://localhost:3000` | # Performance diff --git a/config/config.go b/config/config.go index 2621205..c8cafd7 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ const ( defaultLink = "https://mb.dev" defaultRights = "mb.dev" defaultMessage = "Created without any JS." + defaultHost = "http://localhost:3000" DataDir = "./data" ) @@ -24,6 +25,7 @@ var ( HomepageLink string HomepageRights string HomepageMessage string + Host string ) func Load() { @@ -45,6 +47,7 @@ func Load() { initLink() initRights() initMessage() + initHost() } func initTimezone() { @@ -98,3 +101,12 @@ func initMessage() { } HomepageMessage = message } + +func initHost() { + host, isSet := os.LookupEnv("MB_HOST") + if !isSet { + Host = defaultHost + return + } + Host = host +} diff --git a/main.go b/main.go index 569124e..6a36a40 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/Pineapple217/mb/handler" "github.com/Pineapple217/mb/media" "github.com/Pineapple217/mb/middleware" + "github.com/Pineapple217/mb/rss" "github.com/labstack/echo/v4" echoMw "github.com/labstack/echo/v4/middleware" @@ -88,7 +89,7 @@ func main() { // e.Static("/static", "./static/public") s.StaticFS("/", echo.MustSubFS(publicFS, "static/public")) - //TODO RSS + e.GET("/index.xml", rss.RSSFeed) //TODO better caching with http headers diff --git a/rss/rss.go b/rss/rss.go new file mode 100644 index 0000000..434d7ae --- /dev/null +++ b/rss/rss.go @@ -0,0 +1,86 @@ +package rss + +import ( + "encoding/xml" + "strconv" + "time" + + "github.com/Pineapple217/mb/config" + "github.com/Pineapple217/mb/database" + "github.com/labstack/echo/v4" +) + +type RSS struct { + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + Channel Channel +} + +type Channel struct { + Title string `xml:"title"` + Description string `xml:"description"` + Link string `xml:"link"` + Generator string `xml:"generator"` + Language string `xml:"language"` + Copyright string `xml:"copyright"` + Items []Item `xml:"item"` +} + +type Item struct { + Title string `xml:"title"` + Description string `xml:"description"` + Link string `xml:"link"` + PubDate string `xml:"pubDate"` + Category string `xml:"category"` +} + +func RSSFeed(c echo.Context) error { + c.Response().Header().Add("Content-Type", "application/rss+xml") + + q := database.GetQueries() + posts, err := q.ListPosts(c.Request().Context()) + if err != nil { + return err + } + rssPosts := make([]Item, 0) + for _, post := range posts { + unixTime := strconv.FormatInt(post.CreatedAt, 10) + p := Item{ + Title: unixTime, + Link: config.Host + "/post/" + unixTime, + PubDate: time.Unix(post.CreatedAt, 0).Format(time.RFC1123Z), + Category: post.Tags.String, + Description: truncateString(post.Content), + } + rssPosts = append(rssPosts, p) + } + + feed := RSS{ + Version: "2.0", + Channel: Channel{ + Title: "Micro Blog of " + config.HomepageRights, + Description: config.HomepageMessage, + Link: config.Host + "/index.xml", + Items: rssPosts, + Generator: "Golang", + Language: "en-uk", + Copyright: "Copyright " + strconv.Itoa(time.Now().Year()) + ", " + config.HomepageRights, + }, + } + xmlData, err := xml.MarshalIndent(feed, "", " ") + if err != nil { + return err + } + c.Response().Write([]byte(xml.Header)) + c.Response().Write(xmlData) + return nil +} + +const descriptionMaxLength = 500 + +func truncateString(s string) string { + if len(s) <= descriptionMaxLength { + return s + } + return s[:descriptionMaxLength] + "..." +}