mirror of
				https://github.com/knadh/listmonk.git
				synced 2025-11-01 03:26:32 +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
 | |
| }
 |