package main import ( "bytes" "encoding/xml" "errors" "fmt" "log" "net/http" "strings" "time" "github.com/gorilla/mux" "riedstra.dev/mitch/go-website/page" ) type Author struct { Name string `xml:"name"` // Required Uri string `xml:"uri,omitempty"` Email string `xml:"email,omitempty"` } type Link struct { Href string `xml:"href,attr,omitempty"` Rel string `xml:"rel,attr,omitempty"` Type string `xml:"type,attr,omitempty"` Title string `xml:"Title,attr,omitempty"` Length string `xml:"Length,attr,omitempty"` } type Content struct { Type string `xml:"type,attr"` Data []byte `xml:"chardata"` } type Entry struct { // Spec requires this, autogenerated from Title and updated if otherwise // left empty Id string `xml:"id"` Title string `xml:"title"` // Required Updated *time.Time `xml:"updated"` // Required Author *Author `xml:"author,omitempty"` Published *time.Time `xml:"published,omitempty"` Links []Link `xml:"link,omitempty"` Content *Content `xml:"content,omitempty"` } func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error { type Alias Entry errs := []string{} if i.Title == "" { errs = append(errs, "Title Cannot be empty") } if i.Updated == nil { errs = append(errs, "Updated cannot be nil") } if len(errs) > 0 { return errors.New(strings.Join(errs, ",")) } if i.Id == "" { i.Id = fmt.Sprintf("%s::%d", i.Title, i.Updated.Unix()) } i2 := (*Alias)(&i) return e.EncodeElement(i2, start) } type Atom struct { Ns string `xml:"xmlns,attr"` Title string `xml:"title"` // Required Id string `xml:"id"` // Required Author Author `xml:"author,omitempty"` // Required Updated *time.Time `xml:"updated"` // Required Published *time.Time `xml:"published,omitempty"` Subtitle string `xml:"subtitle,omitempty"` Entries []Entry `xml:"entry"` } func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error { type Alias Atom a.Ns = "http://www.w3.org/2005/Atom" errs := []string{} if a.Id == "" { errs = append(errs, "ID Cannot be empty") } if a.Author.Name == "" { errs = append(errs, "Author Name cannot be empty") } if a.Updated == nil { errs = append(errs, "Updated cannot be empty") } if len(errs) > 0 { return errors.New(strings.Join(errs, ",")) } start.Name = xml.Name{Local: "feed"} a2 := (*Alias)(&a) return e.EncodeElement(a2, start) } func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) tag, ok := vars["tag"] if !ok { http.Error(w, "Tag not found or supplied", http.StatusNotFound) return } p := page.NewPage("index") index, err := p.Index() if err != nil { log.Println(err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } pages, ok := index[tag] if !ok { http.Error(w, "Invalid tag", http.StatusNotFound) return } feed := &Atom{ Author: a.Author, Title: a.Title, Id: a.FeedId, Updated: &a.Updated.Time, Subtitle: a.Description, } entries := []Entry{} for _, p := range pages { if p.Date == nil { log.Printf("Warning, page %s has no Date field. Skipping inclusion on feed", p) continue } content := &bytes.Buffer{} err := p.Render(content) if err != nil { log.Println(err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } entries = append(entries, Entry{ Title: p.Title, Updated: &p.Date.Time, Links: []Link{Link{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}}, // Content: Content{Type: "html", Data: content.Bytes()}, }) } feed.Entries = entries w.Header().Add("Content-type", "application/xml") w.Write([]byte(xml.Header)) enc := xml.NewEncoder(w) enc.Indent("", " ") err = enc.Encode(feed) if err != nil { log.Println(err) // Headers probably already sent, but we'll try anyway http.Error(w, "Internal server error", http.StatusInternalServerError) } return }