From 4e16f6fd486ef9fe79abfddde72ad4b31868a6a9 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sat, 1 Aug 2020 15:22:30 +0100 Subject: [PATCH] make checkInvite check only one invite, invite daemon checkInvite no longer loops over all invites and checks for expiry, that functionality has moved to checkInvites. Couple more rogue print statements removed aswell. --- api.go | 84 +++++++++++++++++++++++++++++++++++------------------- auth.go | 3 -- daemon.go | 50 ++++++++++++++++++++++++++++++++ email.go | 40 +++----------------------- main.go | 4 +++ storage.go | 3 -- 6 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 daemon.go diff --git a/api.go b/api.go index edd004f..198a4b8 100644 --- a/api.go +++ b/api.go @@ -76,12 +76,11 @@ func timeDiff(a, b time.Time) (year, month, day, hour, min, sec int) { return } -func (ctx *appContext) checkInvite(code string, used bool, username string) bool { +func (ctx *appContext) checkInvites() { current_time := time.Now() ctx.storage.loadInvites() - match := false changed := false - for invCode, data := range ctx.storage.invites { + for code, data := range ctx.storage.invites { expiry := data.ValidTill if current_time.After(expiry) { ctx.debug.Printf("Housekeeping: Deleting old invite %s", code) @@ -90,7 +89,7 @@ func (ctx *appContext) checkInvite(code string, used bool, username string) bool ctx.debug.Printf("%s: Expiry notification", code) for address, settings := range notify { if settings["notify-expiry"] { - if ctx.email.constructExpiry(invCode, data, ctx) != nil { + if ctx.email.constructExpiry(code, data, ctx) != nil { ctx.err.Printf("%s: Failed to construct expiry notification", code) } else if ctx.email.send(address, ctx) != nil { ctx.err.Printf("%s: Failed to send expiry notification", code) @@ -101,31 +100,62 @@ func (ctx *appContext) checkInvite(code string, used bool, username string) bool } } changed = true - delete(ctx.storage.invites, invCode) - } else if invCode == code { - match = true - if used { - changed = true - del := false - newInv := data - if newInv.RemainingUses == 1 { - del = true - delete(ctx.storage.invites, invCode) - } else if newInv.RemainingUses != 0 { - // 0 means infinite i guess? - newInv.RemainingUses -= 1 - } - newInv.UsedBy = append(newInv.UsedBy, []string{username, ctx.formatDatetime(current_time)}) - if !del { - ctx.storage.invites[invCode] = newInv - } - } + delete(ctx.storage.invites, code) } } if changed { ctx.storage.storeInvites() } - return match +} + +func (ctx *appContext) checkInvite(code string, used bool, username string) bool { + current_time := time.Now() + ctx.storage.loadInvites() + changed := false + if inv, match := ctx.storage.invites[code]; match { + expiry := inv.ValidTill + if current_time.After(expiry) { + ctx.debug.Printf("Housekeeping: Deleting old invite %s", code) + notify := inv.Notify + if ctx.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { + ctx.debug.Printf("%s: Expiry notification", code) + for address, settings := range notify { + if settings["notify-expiry"] { + if ctx.email.constructExpiry(code, inv, ctx) != nil { + ctx.err.Printf("%s: Failed to construct expiry notification", code) + } else if ctx.email.send(address, ctx) != nil { + ctx.err.Printf("%s: Failed to send expiry notification", code) + } else { + ctx.info.Printf("Sent expiry notification to %s", address) + } + } + } + } + changed = true + match = false + delete(ctx.storage.invites, code) + } else if used { + changed = true + del := false + newInv := inv + if newInv.RemainingUses == 1 { + del = true + delete(ctx.storage.invites, code) + } else if newInv.RemainingUses != 0 { + // 0 means infinite i guess? + newInv.RemainingUses -= 1 + } + newInv.UsedBy = append(newInv.UsedBy, []string{username, ctx.formatDatetime(current_time)}) + if !del { + ctx.storage.invites[code] = newInv + } + } + if changed { + ctx.storage.storeInvites() + } + return match + } + return false } // Routes from now on! @@ -270,12 +300,8 @@ func (ctx *appContext) GenerateInvite(gc *gin.Context) { func (ctx *appContext) GetInvites(gc *gin.Context) { ctx.debug.Println("Invites requested") current_time := time.Now() - // checking one checks all of them ctx.storage.loadInvites() - for key := range ctx.storage.invites { - ctx.checkInvite(key, false, "") - break - } + ctx.checkInvites() var invites []map[string]interface{} for code, inv := range ctx.storage.invites { _, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, current_time) diff --git a/auth.go b/auth.go index 8b752ef..8658abb 100644 --- a/auth.go +++ b/auth.go @@ -16,9 +16,6 @@ func (ctx *appContext) webAuth() gin.HandlerFunc { } func (ctx *appContext) authenticate(gc *gin.Context) { - for _, val := range ctx.users { - fmt.Println("userid", val.UserID) - } header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2) if header[0] != "Basic" { ctx.debug.Println("Invalid authentication header") diff --git a/daemon.go b/daemon.go new file mode 100644 index 0000000..c194bd6 --- /dev/null +++ b/daemon.go @@ -0,0 +1,50 @@ +package main + +import "time" + +// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS + +type Repeater struct { + Stopped bool + ShutdownChannel chan string + Interval time.Duration + period time.Duration + ctx *appContext +} + +func NewRepeater(interval time.Duration, ctx *appContext) *Repeater { + return &Repeater{ + Stopped: false, + ShutdownChannel: make(chan string), + Interval: interval, + period: interval, + ctx: ctx, + } +} + +func (rt *Repeater) Run() { + rt.ctx.info.Println("Invite daemon started") + for { + select { + case <-rt.ShutdownChannel: + rt.ShutdownChannel <- "Down" + return + case <-time.After(rt.period): + break + } + started := time.Now() + rt.ctx.storage.loadInvites() + rt.ctx.debug.Println("Daemon: Checking invites") + rt.ctx.checkInvites() + finished := time.Now() + duration := finished.Sub(started) + rt.period = rt.Interval - duration + } +} + +func (rt *Repeater) Shutdown() { + rt.Stopped = true + rt.ShutdownChannel <- "Down" + <-rt.ShutdownChannel + close(rt.ShutdownChannel) +} diff --git a/email.go b/email.go index 5b54d83..3a8f208 100644 --- a/email.go +++ b/email.go @@ -73,7 +73,6 @@ func (email *Emailer) constructInvite(code string, invite Invite, ctx *appContex fpath := ctx.config.Section("invite_emails").Key("email_" + key).String() tpl, err := template.ParseFiles(fpath) if err != nil { - fmt.Println("failed email", err) return err } var tplData bytes.Buffer @@ -85,7 +84,6 @@ func (email *Emailer) constructInvite(code string, invite Invite, ctx *appContex "message": message, }) if err != nil { - fmt.Println("failed email", err) return err } if key == "html" { @@ -105,7 +103,6 @@ func (email *Emailer) constructExpiry(code string, invite Invite, ctx *appContex fpath := ctx.config.Section("notifications").Key("expiry_" + key).String() tpl, err := template.ParseFiles(fpath) if err != nil { - fmt.Println("failed email", err) return err } var tplData bytes.Buffer @@ -114,7 +111,6 @@ func (email *Emailer) constructExpiry(code string, invite Invite, ctx *appContex "expiry": expiry, }) if err != nil { - fmt.Println("failed email", err) return err } if key == "html" { @@ -140,7 +136,6 @@ func (email *Emailer) constructCreated(code, username, address string, invite In fpath := ctx.config.Section("notifications").Key("created_" + key).String() tpl, err := template.ParseFiles(fpath) if err != nil { - fmt.Println("failed email", err) return err } var tplData bytes.Buffer @@ -151,7 +146,6 @@ func (email *Emailer) constructCreated(code, username, address string, invite In "time": created, }) if err != nil { - fmt.Println("failed email", err) return err } if key == "html" { @@ -166,33 +160,6 @@ func (email *Emailer) constructCreated(code, username, address string, invite In func (email *Emailer) send(address string, ctx *appContext) error { if email.sendMethod == "mailgun" { - // reqData := map[string]string{ - // "to": fmt.Sprintf("%s <%s>", "test", email.to), - // "from": email.fromAddr, - // "subject": email.subject, - // } - // if email.sendType == "invite" { - // reqData["text"] = email.invite.text - // reqData["html"] = email.invite.html - // } - // data := &bytes.Buffer{} - // encoder := json.NewEncoder(data) - // encoder.SetEscapeHTML(false) - // err := encoder.Encode(reqData) - // fmt.Println("marshaled:", data) - // if err != nil { - // fmt.Println("Failed marshal:", err, ">", data) - // return err - // } - // var req *http.Request - // req, err = http.NewRequest("POST", ctx.config.Section("mailgun").Key("api_url").String(), data) - // req.SetBasicAuth("api", ctx.config.Section("mailgun").Key("api_key").String()) - // var resp *http.Response - // resp, err = email.httpClient.Do(req) - // if err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 204) { - // fmt.Println("failed send:", err, resp.StatusCode) - // fmt.Println("resp:", resp.Header.Get("Content-Encoding"), resp.Body) - // } message := email.mg.NewMessage( fmt.Sprintf("%s <%s>", email.fromName, email.fromAddr), email.content.subject, @@ -201,9 +168,10 @@ func (email *Emailer) send(address string, ctx *appContext) error { message.SetHtml(email.content.html) mgctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() - _, id, err := email.mg.Send(mgctx, message) - fmt.Println("mailgun:", id, err) - + _, _, err := email.mg.Send(mgctx, message) + if err != nil { + return err + } } return nil } diff --git a/main.go b/main.go index 6d813a3..e884be6 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "log" "os" "path/filepath" + "time" ) // Username is JWT! @@ -193,6 +194,9 @@ func main() { ctx.email.init(ctx) + inviteDaemon := NewRepeater(time.Duration(60*time.Second), ctx) + go inviteDaemon.Run() + ctx.info.Println("Loading routes") router := gin.New() diff --git a/storage.go b/storage.go index 7be2e64..ef39f74 100644 --- a/storage.go +++ b/storage.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "io/ioutil" - "os" "time" ) @@ -78,8 +77,6 @@ func loadJSON(path string, obj interface{}) error { } func storeJSON(path string, obj interface{}) error { - test := json.NewEncoder(os.Stdout) - test.Encode(obj) data, err := json.Marshal(obj) if err != nil { return err