| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "strings" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | func entryPublished(date string) string { |
| 10 | if d, err := time.Parse("2006-01-02", date); err == nil { |
| 11 | return time.Date(d.Year(), d.Month(), d.Day(), 12, 0, 0, 0, time.UTC).Format(time.RFC3339) |
| 12 | } |
| 13 | return time.Now().UTC().Format(time.RFC3339) |
| 14 | } |
| 15 | |
| 16 | func escapeXML(s string) string { |
| 17 | r := strings.NewReplacer( |
| 18 | "&", "&", |
| 19 | "<", "<", |
| 20 | ">", ">", |
| 21 | `"`, """, |
| 22 | "'", "'", |
| 23 | ) |
| 24 | return r.Replace(s) |
| 25 | } |
| 26 | |
| 27 | func (a *App) atomFeedHandler(w http.ResponseWriter, r *http.Request) { |
| 28 | items, err := listDaily(a.DB, 100) |
| 29 | if err != nil { |
| 30 | a.Log.Error("atom feed query failed", "err", err) |
| 31 | w.WriteHeader(http.StatusInternalServerError) |
| 32 | return |
| 33 | } |
| 34 | updated := time.Now().UTC().Format(time.RFC3339) |
| 35 | if len(items) > 0 { |
| 36 | updated = entryPublished(items[0].Date) |
| 37 | } |
| 38 | base := strings.TrimRight(a.BaseURL, "/") |
| 39 | selfURL := base + "/feed.xml" |
| 40 | |
| 41 | var b strings.Builder |
| 42 | b.WriteString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") |
| 43 | b.WriteString("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n") |
| 44 | b.WriteString(" <title>Easel — Daily Artwork</title>\n") |
| 45 | b.WriteString(" <subtitle>A daily painting from the Art Institute of Chicago</subtitle>\n") |
| 46 | b.WriteString(` <link href="` + escapeXML(selfURL) + `" rel="self" type="application/atom+xml" />` + "\n") |
| 47 | b.WriteString(` <link href="` + escapeXML(base) + `" />` + "\n") |
| 48 | b.WriteString(" <id>" + escapeXML(selfURL) + "</id>\n") |
| 49 | b.WriteString(" <updated>" + updated + "</updated>\n") |
| 50 | |
| 51 | for _, item := range items { |
| 52 | published := entryPublished(item.Date) |
| 53 | entryURL := base + "/day/" + item.Date |
| 54 | author := item.ArtistTitle.String |
| 55 | if author == "" { |
| 56 | author = item.ArtistDisplay.String |
| 57 | } |
| 58 | if author == "" { |
| 59 | author = "Unknown" |
| 60 | } |
| 61 | summary := item.ShortDescription.String |
| 62 | if summary == "" { |
| 63 | summary = item.Description.String |
| 64 | } |
| 65 | image := iiifURL(item.ImageID) |
| 66 | content := `<p><img src="` + escapeXML(image) + `" alt="` + escapeXML(item.Title) + `" /></p><p>` + escapeXML(summary) + `</p>` |
| 67 | |
| 68 | b.WriteString(" <entry>\n") |
| 69 | b.WriteString(" <title>" + escapeXML(item.Date) + " — " + escapeXML(item.Title) + "</title>\n") |
| 70 | b.WriteString(` <link href="` + escapeXML(entryURL) + `" />` + "\n") |
| 71 | b.WriteString(" <id>" + escapeXML(entryURL) + "</id>\n") |
| 72 | b.WriteString(" <updated>" + published + "</updated>\n") |
| 73 | b.WriteString(" <published>" + published + "</published>\n") |
| 74 | b.WriteString(" <author>\n") |
| 75 | b.WriteString(" <name>" + escapeXML(author) + "</name>\n") |
| 76 | b.WriteString(" </author>\n") |
| 77 | if summary != "" { |
| 78 | b.WriteString(" <summary>" + escapeXML(summary) + "</summary>\n") |
| 79 | } |
| 80 | b.WriteString(` <content type="html">` + escapeXML(content) + `</content>` + "\n") |
| 81 | b.WriteString(" </entry>\n") |
| 82 | } |
| 83 | b.WriteString("</feed>\n") |
| 84 | |
| 85 | w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8") |
| 86 | _, _ = w.Write([]byte(b.String())) |
| 87 | } |