aboutsummaryrefslogtreecommitdiff
path: root/cmd/web/handlers/export.go
blob: 96ba68d7b406818b35a7939ceb88bacf838e71f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package handlers

import (
	"bytes"
	"database/sql"
	"encoding/xml"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/html"

	"paterissa.net/mblog/internal/models"
)

type Item struct {
	XMLName     xml.Name `xml:"item"`
	Title       string   `xml:"title"`
	Link        string   `xml:"link"`
	Guid        string   `xml:"guid"`
	Description string   `xml:"description"`
	PubDate     string   `xml:"pubDate"`
	Content     string   `xml:"content:encoded"`
}

type Channel struct {
	XMLName     xml.Name `xml:"channel"`
	Title       string   `xml:"title"`
	Link        string   `xml:"link"`
	Description string   `xml:"description"`
	Items       []Item   `xml:"item"`
}

type rssExportContext struct {
	err *log.Logger
	db  *sql.DB

	serv string
}

func (ctx *rssExportContext) feed(w http.ResponseWriter, r *http.Request) {
	feed := &Channel{
		Title:       "Paterissa",
		Link:        "https://" + ctx.serv,
		Description: "Blog feed",
	}

	rows, err := ctx.db.Query("SELECT p.id, u.name, p.time, p.brief, p.content FROM posts p INNER JOIN logins u ON p.user_id = u.id ORDER BY p.id DESC LIMIT 20;")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Internal Error"))
		ctx.err.Printf("Could not load posts from DB: %v\n", err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var p models.Post

		if err = rows.Scan(&p.Id, &p.Name, &p.Time, &p.Brief, &p.Content); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Internal Error"))
			ctx.err.Printf("Could not load posts from DB: %v\n", err)
			return
		}

		p.FormattedTime = p.Time.Format(time.RFC1123Z)

		title := ""
		z := html.NewTokenizer(strings.NewReader(string(p.Brief)))

		for {
			tt := z.Next()

			if tt == html.ErrorToken {
				break
			} else if tt == html.StartTagToken {
				tag := z.Token()

				if tag.Data == "h1" {
					if tt = z.Next(); tt == html.TextToken {
						title = z.Token().Data
					}
				}
			}
		}

		feed.Items = append(feed.Items, Item{
			Title:       title,
			Link:        "https://" + ctx.serv + "/post?id=" + strconv.Itoa(p.Id),
			Guid:        "https://" + ctx.serv + "/post?id=" + strconv.Itoa(p.Id),
			Description: "<![CDATA[" + string(p.Brief) + "]]>",
			PubDate:     p.FormattedTime,
			Content:     "<![CDATA[" + string(p.Content) + "]]>",
		})
	}

	out, err := xml.MarshalIndent(feed, "", "  ")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Internal Error"))
		ctx.err.Printf("Could not create RSS feed: %v\n", err)
		return
	}

	// Really stupid workaround.
	// Basically, encoders will escape HTML automatically.
	// This can be configured on the JSON encoder.
	// It cannot on the XML.
	// God only knows why.
	out = []byte(html.UnescapeString(string(out)))

	w.Write([]byte(xml.Header + `<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">` + string(out) + `</rss>`))
}