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