+277
@@ -0,0 +1,277 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/feeds"
|
||||
"source.offmarket.win/aleagle/eaglecast/internal/manager"
|
||||
"source.offmarket.win/aleagle/eaglecast/models"
|
||||
"github.com/labstack/echo/v4"
|
||||
null "gopkg.in/volatiletech/null.v6"
|
||||
)
|
||||
|
||||
type campArchive struct {
|
||||
UUID string `json:"uuid"`
|
||||
Subject string `json:"subject"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt null.Time `json:"created_at"`
|
||||
SendAt null.Time `json:"send_at"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// GetCampaignArchives renders the public campaign archives page.
|
||||
func (a *App) GetCampaignArchives(c echo.Context) error {
|
||||
// Get archives from the DB.
|
||||
pg := a.pg.NewFromURL(c.Request().URL.Query())
|
||||
camps, total, err := a.getCampaignArchives(pg.Offset, pg.Limit, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(camps) == 0 {
|
||||
return c.JSON(http.StatusOK, okResp{models.PageResults{
|
||||
Results: []campArchive{},
|
||||
}})
|
||||
}
|
||||
|
||||
// Meta.
|
||||
out := models.PageResults{
|
||||
Results: camps,
|
||||
Total: total,
|
||||
Page: pg.Page,
|
||||
PerPage: pg.PerPage,
|
||||
}
|
||||
|
||||
return c.JSON(200, okResp{out})
|
||||
}
|
||||
|
||||
// GetCampaignArchivesFeed renders the public campaign archives RSS feed.
|
||||
func (a *App) GetCampaignArchivesFeed(c echo.Context) error {
|
||||
var (
|
||||
pg = a.pg.NewFromURL(c.Request().URL.Query())
|
||||
showFullContent = a.cfg.EnablePublicArchiveRSSContent
|
||||
)
|
||||
|
||||
// Get archives from the DB.
|
||||
camps, _, err := a.getCampaignArchives(pg.Offset, pg.Limit, showFullContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Format output for the feed.
|
||||
out := make([]*feeds.Item, 0, len(camps))
|
||||
for _, c := range camps {
|
||||
pubDate := c.CreatedAt.Time
|
||||
|
||||
if c.SendAt.Valid {
|
||||
pubDate = c.SendAt.Time
|
||||
}
|
||||
|
||||
out = append(out, &feeds.Item{
|
||||
Title: c.Subject,
|
||||
Link: &feeds.Link{Href: c.URL},
|
||||
Content: c.Content,
|
||||
Created: pubDate,
|
||||
})
|
||||
}
|
||||
|
||||
// Generate the feed.
|
||||
feed := &feeds.Feed{
|
||||
Title: a.cfg.SiteName,
|
||||
Link: &feeds.Link{Href: a.urlCfg.RootURL},
|
||||
Description: a.i18n.T("public.archiveTitle"),
|
||||
Items: out,
|
||||
}
|
||||
|
||||
if err := feed.WriteRss(c.Response().Writer); err != nil {
|
||||
a.log.Printf("error generating archive RSS feed: %v", err)
|
||||
return echo.NewHTTPError(http.StatusBadRequest, a.i18n.T("public.errorProcessingRequest"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CampaignArchivesPage renders the public campaign archives page.
|
||||
func (a *App) CampaignArchivesPage(c echo.Context) error {
|
||||
// Get archives from the DB.
|
||||
pg := a.pg.NewFromURL(c.Request().URL.Query())
|
||||
out, total, err := a.getCampaignArchives(pg.Offset, pg.Limit, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pg.SetTotal(total)
|
||||
|
||||
title := a.i18n.T("public.archiveTitle")
|
||||
return c.Render(http.StatusOK, "archive", struct {
|
||||
Title string
|
||||
Description string
|
||||
Campaigns []campArchive
|
||||
TotalPages int
|
||||
Pagination template.HTML
|
||||
}{title, title, out, pg.TotalPages, template.HTML(pg.HTML("?page=%d"))})
|
||||
}
|
||||
|
||||
// CampaignArchivePage renders the public campaign archives page.
|
||||
func (a *App) CampaignArchivePage(c echo.Context) error {
|
||||
// ID can be the UUID or slug.
|
||||
var (
|
||||
idStr = c.Param("id")
|
||||
uuid, slug string
|
||||
)
|
||||
if reUUID.MatchString(idStr) {
|
||||
uuid = idStr
|
||||
} else {
|
||||
slug = idStr
|
||||
}
|
||||
|
||||
// Get the campaign from the DB.
|
||||
pubCamp, err := a.core.GetArchivedCampaign(0, uuid, slug)
|
||||
if err != nil || pubCamp.Type != models.CampaignTypeRegular {
|
||||
notFound := false
|
||||
|
||||
// Camppaig doesn't exist.
|
||||
if er, ok := err.(*echo.HTTPError); ok {
|
||||
if er.Code == http.StatusBadRequest {
|
||||
notFound = true
|
||||
}
|
||||
} else if pubCamp.Type != models.CampaignTypeRegular {
|
||||
// Campaign isn't of regular type.
|
||||
notFound = true
|
||||
}
|
||||
|
||||
// 404.
|
||||
if notFound {
|
||||
return c.Render(http.StatusNotFound, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.notFoundTitle"), "", a.i18n.T("public.campaignNotFound")))
|
||||
}
|
||||
|
||||
// Some other internal error.
|
||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.errorFetchingCampaign")))
|
||||
}
|
||||
|
||||
// "Compile" the campaign template with appropriate data.
|
||||
out, err := a.compileArchiveCampaigns([]models.Campaign{pubCamp})
|
||||
if err != nil {
|
||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.errorFetchingCampaign")))
|
||||
}
|
||||
|
||||
// Render the campaign body.
|
||||
camp := out[0].Campaign
|
||||
msg, err := a.manager.NewCampaignMessage(camp, out[0].Subscriber)
|
||||
if err != nil {
|
||||
a.log.Printf("error rendering campaign: %v", err)
|
||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.errorFetchingCampaign")))
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, string(msg.Body()))
|
||||
}
|
||||
|
||||
// CampaignArchivePageLatest renders the latest public campaign.
|
||||
func (a *App) CampaignArchivePageLatest(c echo.Context) error {
|
||||
// Get the latest campaign from the DB.
|
||||
camps, _, err := a.getCampaignArchives(0, 1, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(camps) == 0 {
|
||||
return c.Render(http.StatusNotFound, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.notFoundTitle"), "", a.i18n.T("public.campaignNotFound")))
|
||||
}
|
||||
camp := camps[0]
|
||||
|
||||
return c.HTML(http.StatusOK, camp.Content)
|
||||
}
|
||||
|
||||
// getCampaignArchives fetches the public campaign archives from the DB.
|
||||
func (a *App) getCampaignArchives(offset, limit int, renderBody bool) ([]campArchive, int, error) {
|
||||
pubCamps, total, err := a.core.GetArchivedCampaigns(offset, limit)
|
||||
if err != nil {
|
||||
return []campArchive{}, total, echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("public.errorFetchingCampaign"))
|
||||
}
|
||||
|
||||
msgs, err := a.compileArchiveCampaigns(pubCamps)
|
||||
if err != nil {
|
||||
return []campArchive{}, total, err
|
||||
}
|
||||
|
||||
out := make([]campArchive, 0, len(msgs))
|
||||
for _, m := range msgs {
|
||||
camp := m.Campaign
|
||||
|
||||
archive := campArchive{
|
||||
UUID: camp.UUID,
|
||||
Subject: camp.Subject,
|
||||
CreatedAt: camp.CreatedAt,
|
||||
SendAt: camp.SendAt,
|
||||
}
|
||||
|
||||
// The campaign may have a custom slug.
|
||||
if camp.ArchiveSlug.Valid {
|
||||
archive.URL, _ = url.JoinPath(a.urlCfg.ArchiveURL, camp.ArchiveSlug.String)
|
||||
} else {
|
||||
archive.URL, _ = url.JoinPath(a.urlCfg.ArchiveURL, camp.UUID)
|
||||
}
|
||||
|
||||
// Render the full template body if requested.
|
||||
if renderBody {
|
||||
msg, err := a.manager.NewCampaignMessage(camp, m.Subscriber)
|
||||
if err != nil {
|
||||
return []campArchive{}, total, err
|
||||
}
|
||||
archive.Content = string(msg.Body())
|
||||
}
|
||||
|
||||
out = append(out, archive)
|
||||
}
|
||||
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
// compileArchiveCampaigns compiles the campaign template with the subscriber data.
|
||||
func (a *App) compileArchiveCampaigns(camps []models.Campaign) ([]manager.CampaignMessage, error) {
|
||||
|
||||
var (
|
||||
b = bytes.Buffer{}
|
||||
out = make([]manager.CampaignMessage, 0, len(camps))
|
||||
)
|
||||
for _, c := range camps {
|
||||
camp := c
|
||||
if err := camp.CompileTemplate(a.manager.TemplateFuncs(&camp)); err != nil {
|
||||
a.log.Printf("error compiling template: %v", err)
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("public.errorFetchingCampaign"))
|
||||
}
|
||||
|
||||
// Load the dummy subscriber meta.
|
||||
var sub models.Subscriber
|
||||
if err := json.Unmarshal([]byte(camp.ArchiveMeta), &sub); err != nil {
|
||||
a.log.Printf("error unmarshalling campaign archive meta: %v", err)
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("public.errorFetchingCampaign"))
|
||||
}
|
||||
|
||||
m := manager.CampaignMessage{
|
||||
Campaign: &camp,
|
||||
Subscriber: sub,
|
||||
}
|
||||
|
||||
// Render the subject if it's a template.
|
||||
if camp.SubjectTpl != nil {
|
||||
if err := camp.SubjectTpl.ExecuteTemplate(&b, models.ContentTpl, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
camp.Subject = b.String()
|
||||
b.Reset()
|
||||
}
|
||||
|
||||
out = append(out, m)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user