c375916f05
CI / test (push) Successful in 8s
Add SyncChanged (and the ChangedSlugs git-diff helper it's built on) as an opt-in narrower version of SyncDir: pass the push's "before" SHA and only the campaign directories that actually changed get looked at at all, instead of every campaign getting resolved and diffed against listmonk on every push. An empty or unresolvable since (new branch's first push, a shallow clone missing that commit, any git failure) always falls back to a full scan -- this can only ever cause more work than strictly necessary, never a silently-skipped campaign. SyncDir itself is now a thin wrapper (SyncChanged with since=""), so every existing caller and test is unaffected. campaigns sync PATH now accepts an optional trailing SINCE_SHA.
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
// Command campaigns drives listmonk's Campaign API from git-authored
|
|
// Markdown: sync creates/updates draft campaigns from campaigns/<slug>/
|
|
// directories, send transitions one to running (the one real send trigger
|
|
// in this tool), and test previews a campaign's current content without
|
|
// touching its status.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"gitea.reground.org/will/eec-campaigns/internal/campaign"
|
|
"gitea.reground.org/will/eec-campaigns/internal/listmonk"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
}
|
|
|
|
lm := listmonk.New(
|
|
mustEnv("LISTMONK_BASE_URL"),
|
|
mustEnv("LISTMONK_API_USER"),
|
|
mustEnv("LISTMONK_API_TOKEN"),
|
|
)
|
|
|
|
switch os.Args[1] {
|
|
case "sync":
|
|
if len(os.Args) < 3 {
|
|
fmt.Fprintln(os.Stderr, "usage: campaigns sync PATH [SINCE_SHA]")
|
|
os.Exit(1)
|
|
}
|
|
since := ""
|
|
if len(os.Args) >= 4 {
|
|
since = os.Args[3]
|
|
}
|
|
runSync(lm, os.Args[2], since)
|
|
case "send":
|
|
if len(os.Args) < 3 {
|
|
fmt.Fprintln(os.Stderr, "usage: campaigns send SLUG")
|
|
os.Exit(1)
|
|
}
|
|
runSend(lm, os.Args[2])
|
|
case "test":
|
|
if len(os.Args) < 4 {
|
|
fmt.Fprintln(os.Stderr, "usage: campaigns test SLUG EMAIL...")
|
|
os.Exit(1)
|
|
}
|
|
runTest(lm, os.Args[2], os.Args[3:])
|
|
case "template":
|
|
if len(os.Args) < 4 {
|
|
fmt.Fprintln(os.Stderr, "usage: campaigns template NAME PATH")
|
|
os.Exit(1)
|
|
}
|
|
runTemplate(lm, os.Args[2], os.Args[3])
|
|
default:
|
|
usage()
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage: campaigns <sync PATH|send SLUG|test SLUG EMAIL...|template NAME PATH>")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// runSync's default preview address(es) come from CAMPAIGNS_PREVIEW_EMAIL
|
|
// (comma-separated), used for any campaign that doesn't set its own
|
|
// preview_emails in frontmatter. Optional — sync still works with none set,
|
|
// it just won't auto-preview campaigns that don't specify their own.
|
|
//
|
|
// since is optional — the commit to diff against (typically the push
|
|
// event's "before" SHA) to scope this run to just the campaigns that
|
|
// changed, per campaign.SyncChanged. Pass "" to always do a full scan.
|
|
func runSync(lm *listmonk.Client, path, since string) {
|
|
var defaultPreview []string
|
|
if raw := os.Getenv("CAMPAIGNS_PREVIEW_EMAIL"); raw != "" {
|
|
for _, addr := range strings.Split(raw, ",") {
|
|
if addr = strings.TrimSpace(addr); addr != "" {
|
|
defaultPreview = append(defaultPreview, addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
result, err := campaign.SyncChanged(lm, path, defaultPreview, since)
|
|
if err != nil {
|
|
log.Fatalf("sync: %v", err)
|
|
}
|
|
log.Printf("mode: %s", result.Mode)
|
|
log.Printf("synced: %v", result.Synced)
|
|
log.Printf("unchanged: %v", result.Unchanged)
|
|
if len(result.Skipped) > 0 {
|
|
log.Printf("skipped (already sent): %v", result.Skipped)
|
|
}
|
|
if len(result.Rejected) > 0 {
|
|
log.Printf("rejected: %v", result.Rejected)
|
|
}
|
|
if len(result.PreviewFailed) > 0 {
|
|
log.Printf("preview send failed: %v", result.PreviewFailed)
|
|
}
|
|
if len(result.Rejected) > 0 || len(result.PreviewFailed) > 0 {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runSend(lm *listmonk.Client, slug string) {
|
|
camp, err := campaign.Send(lm, slug)
|
|
if err != nil {
|
|
log.Fatalf("send: %v", err)
|
|
}
|
|
log.Printf("sent: campaign %q (id=%d) is now running", slug, camp.ID)
|
|
}
|
|
|
|
func runTest(lm *listmonk.Client, slug string, emails []string) {
|
|
if err := campaign.Test(lm, slug, emails); err != nil {
|
|
log.Fatalf("test: %v", err)
|
|
}
|
|
log.Printf("test: sent preview of %q to %v", slug, emails)
|
|
}
|
|
|
|
func runTemplate(lm *listmonk.Client, name, path string) {
|
|
tpl, err := campaign.SyncTemplate(lm, name, path)
|
|
if err != nil {
|
|
log.Fatalf("template: %v", err)
|
|
}
|
|
log.Printf("template: %q (id=%d) is now the default campaign template", tpl.Name, tpl.ID)
|
|
}
|
|
|
|
func mustEnv(key string) string {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
log.Fatalf("%s environment variable is required", key)
|
|
}
|
|
return v
|
|
}
|