Skip to content

Commit

Permalink
Implement information updating
Browse files Browse the repository at this point in the history
  • Loading branch information
karashiiro committed May 7, 2022
1 parent 6587173 commit 44eab3e
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 56 deletions.
2 changes: 1 addition & 1 deletion cmd/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func main() {
sched.ScheduleJob(&reportJob, reportTrigger)

// Schedule the email-checking job
receiveTrigger := quartz.NewSimpleTrigger(time.Minute)
receiveTrigger := quartz.NewSimpleTrigger(5 * time.Second)
receiveJob := inbox.ReceiveEmailsJob{
Pool: pool,
Policy: bluemonday.UGCPolicy(),
Expand Down
48 changes: 48 additions & 0 deletions pkg/inbox/parse-body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package inbox

import (
"strings"
"time"

"github.com/jprobinson/eazye"
"github.com/microcosm-cc/bluemonday"
)

type ReaderInfo struct {
Email string
GitHub string
GitHubSet bool
ReportInterval time.Duration
}

func ParseBody(email eazye.Email, policy bluemonday.Policy) (*ReaderInfo, error) {
r := &ReaderInfo{
Email: policy.Sanitize(email.From.Address),
}

bodyLines := strings.Split(string(email.Text), "\n")
for _, line := range bodyLines {
lineCleaned := strings.TrimSpace(line)

// Parse their optionally-provided GitHub username
githubMatches := githubPattern.FindStringSubmatch(lineCleaned)
if len(githubMatches) != 0 {
github := githubMatches[githubPattern.SubexpIndex("github")]
r.GitHub = policy.Sanitize(github)
r.GitHubSet = true
continue
}

// Parse their requested reporting interval
intervalMatches := intervalPattern.FindStringSubmatch(lineCleaned)
if len(intervalMatches) != 0 {
interval, err := time.ParseDuration(intervalMatches[intervalPattern.SubexpIndex("interval")])
if err == nil {
r.ReportInterval = interval
continue
}
}
}

return r, nil
}
6 changes: 6 additions & 0 deletions pkg/inbox/patterns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package inbox

import "regexp"

var githubPattern = regexp.MustCompile(`(?i)github:\s*(?P<github>\S*)`)
var intervalPattern = regexp.MustCompile(`(?i)interval:\s*(?P<interval>\S*)`)
34 changes: 24 additions & 10 deletions pkg/inbox/receive-emails.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"log"
"os"
"strings"
"time"

"github.com/jackc/pgx"
"github.com/jprobinson/eazye"
Expand All @@ -17,12 +16,6 @@ type ReceiveEmailsJob struct {
Policy *bluemonday.Policy
}

type newReader struct {
Email string
GitHub string
ReportInterval time.Duration
}

func (j *ReceiveEmailsJob) Execute() {
auth := eazye.MailboxInfo{
Host: os.Getenv("OPERATOR_IMAP_SERVER"),
Expand All @@ -37,25 +30,41 @@ func (j *ReceiveEmailsJob) Execute() {
log.Printf("Failed to get incoming emails: %v\n", err)
}

newReaders := make([]*newReader, 0)
newReaders := make([]*ReaderInfo, 0)
updatedReaders := make([]*ReaderInfo, 0)
unsubscribers := make([]string, 0)
for _, email := range emails {
// Parse out the email information
subjectCleaned := strings.TrimSpace(email.Subject)

if strings.HasPrefix(subjectCleaned, "[op] subscribe") {
r, err := j.subscribe(email)
r, err := ParseBody(email, *j.Policy)
if err != nil {
log.Printf("Failed to parse subscription email: %v\n", err)
continue
}

// Validate reporting interval
if r.ReportInterval.Minutes() <= 0 {
log.Println("User attempted to set a reporting interval of 0 or less")
continue
}

newReaders = append(newReaders, r)
} else if strings.HasPrefix(subjectCleaned, "[op] update") {
r, err := ParseBody(email, *j.Policy)
if err != nil {
log.Printf("Failed to parse update email: %v\n", err)
continue
}

updatedReaders = append(updatedReaders, r)
} else if strings.HasPrefix(subjectCleaned, "[op] unsubscribe") {
unsubscribers = append(unsubscribers, email.From.Address)
}
}

if len(newReaders) == 0 && len(unsubscribers) == 0 {
if len(newReaders) == 0 && len(updatedReaders) == 0 && len(unsubscribers) == 0 {
return
}

Expand All @@ -71,6 +80,11 @@ func (j *ReceiveEmailsJob) Execute() {
saveSubscribers(readerConn, newReaders)
}

// Persist reader updates to the database
if len(updatedReaders) > 0 {
saveUpdatedInfo(readerConn, updatedReaders)
}

// Delete unsubscribing readers from the database
if len(unsubscribers) > 0 {
deleteUnsubscribers(readerConn, unsubscribers)
Expand Down
47 changes: 2 additions & 45 deletions pkg/inbox/subscribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,17 @@ package inbox

import (
"bytes"
"fmt"
"io"
"log"
"regexp"
"strings"
"text/template"
"time"

"github.com/jackc/pgx"
"github.com/jprobinson/eazye"
"github.com/karashiiro/operator/pkg/html"
"github.com/karashiiro/operator/pkg/outlook"
)

var githubPattern = regexp.MustCompile(`(?i)github:\s*(?P<github>\S*)`)
var intervalPattern = regexp.MustCompile(`(?i)interval:\s*(?P<interval>\S*)`)

func (j *ReceiveEmailsJob) subscribe(email eazye.Email) (*newReader, error) {
r := &newReader{
Email: j.Policy.Sanitize(email.From.Address),
}

bodyLines := strings.Split(string(email.Text), "\n")
for _, line := range bodyLines {
lineCleaned := strings.TrimSpace(line)

// Parse their optionally-provided GitHub username
githubMatches := githubPattern.FindStringSubmatch(lineCleaned)
if len(githubMatches) != 0 {
github := githubMatches[githubPattern.SubexpIndex("github")]
r.GitHub = j.Policy.Sanitize(github)
continue
}

// Parse their requested reporting interval
intervalMatches := intervalPattern.FindStringSubmatch(lineCleaned)
if len(intervalMatches) != 0 {
interval, err := time.ParseDuration(intervalMatches[intervalPattern.SubexpIndex("interval")])
if err == nil {
r.ReportInterval = interval
continue
}
}
}

// Validate reporting interval
if r.ReportInterval.Minutes() <= 0 {
return nil, fmt.Errorf("user attempted to set a reporting interval of 0 or less")
}

return r, nil
}

func saveSubscribers(conn *pgx.Conn, readers []*newReader) {
func saveSubscribers(conn *pgx.Conn, readers []*ReaderInfo) {
for _, r := range readers {
_, err := storeReader(conn, r)
if err != nil {
Expand Down Expand Up @@ -99,7 +56,7 @@ func buildSubscribeTemplate(w io.Writer, interval time.Duration) error {
return nil
}

func storeReader(conn *pgx.Conn, r *newReader) (int64, error) {
func storeReader(conn *pgx.Conn, r *ReaderInfo) (int64, error) {
t, err := conn.Exec(`
INSERT INTO Reader (email, github, report_interval, active)
VALUES
Expand Down
94 changes: 94 additions & 0 deletions pkg/inbox/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package inbox

import (
"bytes"
"io"
"log"
"text/template"
"time"

"github.com/jackc/pgx"
"github.com/karashiiro/operator/pkg/html"
"github.com/karashiiro/operator/pkg/outlook"
)

func saveUpdatedInfo(conn *pgx.Conn, readers []*ReaderInfo) {
for _, r := range readers {
if r.GitHubSet {
_, err := updateGitHub(conn, r.GitHub)
if err != nil {
log.Printf("Failed to update reader GitHub: %v\n", err)
continue
}
}

if r.ReportInterval.Minutes() > 0 {
_, err := updateReportInterval(conn, r.ReportInterval)
if err != nil {
log.Printf("Failed to update reader report interval: %v\n", err)
continue
}
}

log.Printf("Sending update confirmation email to %s\n", r.Email)

var updateMessage bytes.Buffer
err := buildUpdateTemplate(&updateMessage, r.ReportInterval)
if err != nil {
log.Printf("Failed to build update template: %v\n", err)
}

err = outlook.SendEmail(r.Email, "Information updated", updateMessage.String())
if err != nil {
log.Printf("Unable to send mail: %v\n", err)
continue
}

log.Printf("Updated reader %s\n", r.Email)
}
}

func buildUpdateTemplate(w io.Writer, interval time.Duration) error {
t, err := template.ParseFS(html.Files, "confirm-update.gohtml")
if err != nil {
return err
}

err = t.Execute(w, struct {
Interval time.Duration
}{
Interval: interval,
})
if err != nil {
return err
}

return nil
}

func updateGitHub(conn *pgx.Conn, gh string) (int64, error) {
var github *string
if gh != "" {
github = &gh
}

t, err := conn.Exec(`
UPDATE Reader SET github = $1;
`, github)
if err != nil {
return 0, err
}

return t.RowsAffected(), nil
}

func updateReportInterval(conn *pgx.Conn, interval time.Duration) (int64, error) {
t, err := conn.Exec(`
UPDATE Reader SET report_interval = $1;
`, interval)
if err != nil {
return 0, err
}

return t.RowsAffected(), nil
}

0 comments on commit 44eab3e

Please sign in to comment.