mirror of
https://github.com/knadh/listmonk.git
synced 2024-11-10 17:13:04 +08:00
276 lines
7.2 KiB
Go
276 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"html/template"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/gorilla/feeds"
|
|
"github.com/knadh/listmonk/internal/manager"
|
|
"github.com/knadh/listmonk/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"`
|
|
}
|
|
|
|
// handleGetCampaignArchives renders the public campaign archives page.
|
|
func handleGetCampaignArchives(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
pg = app.paginator.NewFromURL(c.Request().URL.Query())
|
|
)
|
|
|
|
camps, total, err := getCampaignArchives(pg.Offset, pg.Limit, false, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var out models.PageResults
|
|
if len(camps) == 0 {
|
|
out.Results = []campArchive{}
|
|
return c.JSON(http.StatusOK, okResp{out})
|
|
}
|
|
|
|
// Meta.
|
|
out.Results = camps
|
|
out.Total = total
|
|
out.Page = pg.Page
|
|
out.PerPage = pg.PerPage
|
|
|
|
return c.JSON(200, okResp{out})
|
|
}
|
|
|
|
// handleGetCampaignArchivesFeed renders the public campaign archives RSS feed.
|
|
func handleGetCampaignArchivesFeed(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
pg = app.paginator.NewFromURL(c.Request().URL.Query())
|
|
showFullContent = app.constants.EnablePublicArchiveRSSContent
|
|
)
|
|
|
|
camps, _, err := getCampaignArchives(pg.Offset, pg.Limit, showFullContent, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
feed := &feeds.Feed{
|
|
Title: app.constants.SiteName,
|
|
Link: &feeds.Link{Href: app.constants.RootURL},
|
|
Description: app.i18n.T("public.archiveTitle"),
|
|
Items: out,
|
|
}
|
|
|
|
if err := feed.WriteRss(c.Response().Writer); err != nil {
|
|
app.log.Printf("error generating archive RSS feed: %v", err)
|
|
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("public.errorProcessingRequest"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleCampaignArchivesPage renders the public campaign archives page.
|
|
func handleCampaignArchivesPage(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
pg = app.paginator.NewFromURL(c.Request().URL.Query())
|
|
)
|
|
|
|
out, total, err := getCampaignArchives(pg.Offset, pg.Limit, false, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pg.SetTotal(total)
|
|
|
|
title := app.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"))})
|
|
}
|
|
|
|
// handleCampaignArchivePage renders the public campaign archives page.
|
|
func handleCampaignArchivePage(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
id = c.Param("id")
|
|
uuid = ""
|
|
slug = ""
|
|
)
|
|
|
|
// ID can be the UUID or slug.
|
|
if reUUID.MatchString(id) {
|
|
uuid = id
|
|
} else {
|
|
slug = id
|
|
}
|
|
|
|
pubCamp, err := app.core.GetArchivedCampaign(0, uuid, slug)
|
|
if err != nil || pubCamp.Type != models.CampaignTypeRegular {
|
|
notFound := false
|
|
if er, ok := err.(*echo.HTTPError); ok {
|
|
if er.Code == http.StatusBadRequest {
|
|
notFound = true
|
|
}
|
|
} else if pubCamp.Type != models.CampaignTypeRegular {
|
|
notFound = true
|
|
}
|
|
|
|
if notFound {
|
|
return c.Render(http.StatusNotFound, tplMessage,
|
|
makeMsgTpl(app.i18n.T("public.notFoundTitle"), "", app.i18n.T("public.campaignNotFound")))
|
|
}
|
|
|
|
return c.Render(http.StatusInternalServerError, tplMessage,
|
|
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorFetchingCampaign")))
|
|
}
|
|
|
|
out, err := compileArchiveCampaigns([]models.Campaign{pubCamp}, app)
|
|
if err != nil {
|
|
return c.Render(http.StatusInternalServerError, tplMessage,
|
|
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorFetchingCampaign")))
|
|
}
|
|
|
|
// Render the message body.
|
|
camp := out[0].Campaign
|
|
msg, err := app.manager.NewCampaignMessage(camp, out[0].Subscriber)
|
|
if err != nil {
|
|
app.log.Printf("error rendering message: %v", err)
|
|
return c.Render(http.StatusInternalServerError, tplMessage,
|
|
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorFetchingCampaign")))
|
|
}
|
|
|
|
return c.HTML(http.StatusOK, string(msg.Body()))
|
|
}
|
|
|
|
// handleCampaignArchivePageLatest renders the latest public campaign.
|
|
func handleCampaignArchivePageLatest(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
)
|
|
|
|
camps, _, err := getCampaignArchives(0, 1, true, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(camps) == 0 {
|
|
return c.Render(http.StatusNotFound, tplMessage,
|
|
makeMsgTpl(app.i18n.T("public.notFoundTitle"), "", app.i18n.T("public.campaignNotFound")))
|
|
}
|
|
|
|
camp := camps[0]
|
|
|
|
return c.HTML(http.StatusOK, camp.Content)
|
|
}
|
|
|
|
func getCampaignArchives(offset, limit int, renderBody bool, app *App) ([]campArchive, int, error) {
|
|
pubCamps, total, err := app.core.GetArchivedCampaigns(offset, limit)
|
|
if err != nil {
|
|
return []campArchive{}, total, echo.NewHTTPError(http.StatusInternalServerError, app.i18n.T("public.errorFetchingCampaign"))
|
|
}
|
|
|
|
msgs, err := compileArchiveCampaigns(pubCamps, app)
|
|
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,
|
|
}
|
|
|
|
if camp.ArchiveSlug.Valid {
|
|
archive.URL, _ = url.JoinPath(app.constants.ArchiveURL, camp.ArchiveSlug.String)
|
|
} else {
|
|
archive.URL, _ = url.JoinPath(app.constants.ArchiveURL, camp.UUID)
|
|
}
|
|
|
|
if renderBody {
|
|
msg, err := app.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
|
|
}
|
|
|
|
func compileArchiveCampaigns(camps []models.Campaign, app *App) ([]manager.CampaignMessage, error) {
|
|
var (
|
|
b = bytes.Buffer{}
|
|
)
|
|
|
|
out := make([]manager.CampaignMessage, 0, len(camps))
|
|
for _, c := range camps {
|
|
camp := c
|
|
if err := camp.CompileTemplate(app.manager.TemplateFuncs(&camp)); err != nil {
|
|
app.log.Printf("error compiling template: %v", err)
|
|
return nil, echo.NewHTTPError(http.StatusInternalServerError, app.i18n.T("public.errorFetchingCampaign"))
|
|
}
|
|
|
|
// Load the dummy subscriber meta.
|
|
var sub models.Subscriber
|
|
if err := json.Unmarshal([]byte(camp.ArchiveMeta), &sub); err != nil {
|
|
app.log.Printf("error unmarshalling campaign archive meta: %v", err)
|
|
return nil, echo.NewHTTPError(http.StatusInternalServerError, app.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
|
|
}
|