diff options
author | Brian Picciano <mediocregopher@gmail.com> | 2022-05-05 21:20:22 -0600 |
---|---|---|
committer | Brian Picciano <mediocregopher@gmail.com> | 2022-05-05 21:20:22 -0600 |
commit | eed10ce514f28e4acf772f76c92ca05eebec105f (patch) | |
tree | d76820d7a3cd23f09f7dd0e6065bb0cef7ba16dc /srv/mailinglist/mailinglist.go | |
parent | cc8b6289ac3f7d1abb648217949beb89827d7374 (diff) |
Fix various problems with the srv build
Diffstat (limited to 'srv/mailinglist/mailinglist.go')
-rw-r--r-- | srv/mailinglist/mailinglist.go | 272 |
1 files changed, 0 insertions, 272 deletions
diff --git a/srv/mailinglist/mailinglist.go b/srv/mailinglist/mailinglist.go deleted file mode 100644 index fc6e014..0000000 --- a/srv/mailinglist/mailinglist.go +++ /dev/null @@ -1,272 +0,0 @@ -// Package mailinglist manages the list of subscribed emails and allows emailing -// out to them. -package mailinglist - -import ( - "bytes" - "context" - "errors" - "fmt" - "html/template" - "io" - "net/url" - "strings" - - "github.com/google/uuid" - "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" - "github.com/mediocregopher/mediocre-go-lib/v2/mctx" - "github.com/tilinna/clock" -) - -var ( - // ErrAlreadyVerified is used when the email is already fully subscribed. - ErrAlreadyVerified = errors.New("email is already subscribed") -) - -// MailingList is able to subscribe, unsubscribe, and iterate through emails. -type MailingList interface { - - // May return ErrAlreadyVerified. - BeginSubscription(email string) error - - // May return ErrNotFound or ErrAlreadyVerified. - FinalizeSubscription(subToken string) error - - // May return ErrNotFound. - Unsubscribe(unsubToken string) error - - // Publish blasts the mailing list with an update about a new blog post. - Publish(postTitle, postURL string) error -} - -// Params are parameters used to initialize a new MailingList. All fields are -// required unless otherwise noted. -type Params struct { - Store Store - Mailer Mailer - Clock clock.Clock - - // PublicURL is the base URL which site visitors can navigate to. - // MailingList will generate links based on this value. - PublicURL *url.URL -} - -// SetupCfg implement the cfg.Cfger interface. -func (p *Params) SetupCfg(cfg *cfg.Cfg) { - publicURLStr := cfg.String("ml-public-url", "http://localhost:4000", "URL this service is accessible at") - - cfg.OnInit(func(ctx context.Context) error { - var err error - if p.PublicURL, err = url.Parse(*publicURLStr); err != nil { - return fmt.Errorf("parsing -public-url: %w", err) - } - - return nil - }) -} - -// Annotate implements mctx.Annotator interface. -func (p *Params) Annotate(a mctx.Annotations) { - a["publicURL"] = p.PublicURL -} - -// New initializes and returns a MailingList instance using the given Params. -func New(params Params) MailingList { - return &mailingList{params: params} -} - -type mailingList struct { - params Params -} - -var beginSubTpl = template.Must(template.New("beginSub").Parse(` -Welcome to the Mediocre Blog mailing list! By subscribing to this mailing list -you are signing up to receive an email everytime a new blog post is published. - -In order to complete your subscription please navigate to the following link: - -{{ .SubLink }} - -This mailing list is built and run using my own hardware and software, and I -solemnly swear that you'll never receive an email from it unless there's a new -blog post. - -If you did not initiate this email, and/or do not wish to subscribe to the -mailing list, then simply delete this email and pretend that nothing ever -happened. - -- Brian -`)) - -func (m *mailingList) BeginSubscription(email string) error { - - emailRecord, err := m.params.Store.Get(email) - - if errors.Is(err, ErrNotFound) { - emailRecord = Email{ - Email: email, - SubToken: uuid.New().String(), - CreatedAt: m.params.Clock.Now(), - } - - if err := m.params.Store.Set(emailRecord); err != nil { - return fmt.Errorf("storing pending email: %w", err) - } - - } else if err != nil { - return fmt.Errorf("finding existing email record: %w", err) - - } else if !emailRecord.VerifiedAt.IsZero() { - return ErrAlreadyVerified - } - - body := new(bytes.Buffer) - err = beginSubTpl.Execute(body, struct { - SubLink string - }{ - SubLink: fmt.Sprintf( - "%s/mailinglist/finalize.html?subToken=%s", - m.params.PublicURL.String(), - emailRecord.SubToken, - ), - }) - - if err != nil { - return fmt.Errorf("executing beginSubTpl: %w", err) - } - - err = m.params.Mailer.Send( - email, - "Mediocre Blog - Please verify your email address", - body.String(), - ) - - if err != nil { - return fmt.Errorf("sending email: %w", err) - } - - return nil -} - -func (m *mailingList) FinalizeSubscription(subToken string) error { - emailRecord, err := m.params.Store.GetBySubToken(subToken) - - if err != nil { - return fmt.Errorf("retrieving email record: %w", err) - - } else if !emailRecord.VerifiedAt.IsZero() { - return ErrAlreadyVerified - } - - emailRecord.VerifiedAt = m.params.Clock.Now() - emailRecord.UnsubToken = uuid.New().String() - - if err := m.params.Store.Set(emailRecord); err != nil { - return fmt.Errorf("storing verified email: %w", err) - } - - return nil -} - -func (m *mailingList) Unsubscribe(unsubToken string) error { - emailRecord, err := m.params.Store.GetByUnsubToken(unsubToken) - - if err != nil { - return fmt.Errorf("retrieving email record: %w", err) - } - - if err := m.params.Store.Delete(emailRecord.Email); err != nil { - return fmt.Errorf("deleting email record: %w", err) - } - - return nil -} - -var publishTpl = template.Must(template.New("publish").Parse(` -A new post has been published to the Mediocre Blog! - -{{ .PostTitle }} -{{ .PostURL }} - -If you're interested then please check it out! - -If you'd like to unsubscribe from this mailing list then visit the following -link instead: - -{{ .UnsubURL }} - -- Brian -`)) - -type multiErr []error - -func (m multiErr) Error() string { - if len(m) == 0 { - panic("multiErr with no members") - } - - b := new(strings.Builder) - fmt.Fprintln(b, "The following errors were encountered:") - for _, err := range m { - fmt.Fprintf(b, "\t- %s\n", err.Error()) - } - - return b.String() -} - -func (m *mailingList) Publish(postTitle, postURL string) error { - - var mErr multiErr - - iter := m.params.Store.GetAll() - for { - emailRecord, err := iter() - if errors.Is(err, io.EOF) { - break - - } else if err != nil { - mErr = append(mErr, fmt.Errorf("iterating through email records: %w", err)) - break - - } else if emailRecord.VerifiedAt.IsZero() { - continue - } - - body := new(bytes.Buffer) - err = publishTpl.Execute(body, struct { - PostTitle string - PostURL string - UnsubURL string - }{ - PostTitle: postTitle, - PostURL: postURL, - UnsubURL: fmt.Sprintf( - "%s/mailinglist/unsubscribe.html?unsubToken=%s", - m.params.PublicURL.String(), - emailRecord.UnsubToken, - ), - }) - - if err != nil { - mErr = append(mErr, fmt.Errorf("rendering publish email template for %q: %w", emailRecord.Email, err)) - continue - } - - err = m.params.Mailer.Send( - emailRecord.Email, - fmt.Sprintf("Mediocre Blog - New Post! - %s", postTitle), - body.String(), - ) - - if err != nil { - mErr = append(mErr, fmt.Errorf("sending email to %q: %w", emailRecord.Email, err)) - continue - } - } - - if len(mErr) > 0 { - return mErr - } - - return nil -} |