From e2ef54152d51e26dcda3af5cfb3c1addde86ab3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sat, 19 Nov 2022 22:21:51 +0100 Subject: [PATCH 01/13] refactor(wip): introduce job processing system refactor: adapt report generation scheduling --- config/config.go | 119 +++++++++++++++++++++++++++++++++++++++---- config/jobqueue.go | 44 ++++++++++++++++ go.mod | 1 + go.sum | 2 + services/report.go | 112 ++++++++++++++-------------------------- services/services.go | 3 +- utils/cron.go | 11 ++++ 7 files changed, 204 insertions(+), 88 deletions(-) create mode 100644 config/jobqueue.go create mode 100644 utils/cron.go diff --git a/config/config.go b/config/config.go index 9754e1d..1302010 100644 --- a/config/config.go +++ b/config/config.go @@ -4,10 +4,12 @@ import ( "encoding/json" "flag" "fmt" + "github.com/robfig/cron/v3" "io/ioutil" "net/http" "os" "regexp" + "strconv" "strings" "time" @@ -242,13 +244,81 @@ func (c *appConfig) GetOSColors() map[string]string { return cloneStringMap(c.Colors["operating_systems"], true) } -func (c *appConfig) GetWeeklyReportDay() time.Weekday { - s := strings.Split(c.ReportTimeWeekly, ",")[0] - return parseWeekday(s) +func (c *appConfig) GetAggregationTimeCron() string { + if strings.Contains(c.AggregationTime, ":") { + // old format, e.g. "15:04" + timeParts := strings.Split(c.AggregationTime, ":") + h, err := strconv.Atoi(timeParts[0]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + m, err := strconv.Atoi(timeParts[1]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + return fmt.Sprintf("0 %d %d * * *", m, h) + } + + return cronPadToSecondly(c.AggregationTime) } -func (c *appConfig) GetWeeklyReportTime() string { - return strings.Split(c.ReportTimeWeekly, ",")[1] +func (c *appConfig) GetWeeklyReportCron() string { + if strings.Contains(c.ReportTimeWeekly, ",") { + // old format, e.g. "fri,18:00" + split := strings.Split(c.ReportTimeWeekly, ",") + weekday := parseWeekday(split[0]) + timeParts := strings.Split(split[1], ":") + + h, err := strconv.Atoi(timeParts[0]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + m, err := strconv.Atoi(timeParts[1]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + return fmt.Sprintf("0 %d %d * * %d", m, h, weekday) + } + + return cronPadToSecondly(c.ReportTimeWeekly) +} + +func (c *appConfig) GetLeaderboardGenerationTimeCron() []string { + crons := []string{} + + var parse func(string) string + + if strings.Contains(c.LeaderboardGenerationTime, ":") { + // old format, e.g. "15:04" + parse = func(s string) string { + timeParts := strings.Split(s, ":") + h, err := strconv.Atoi(timeParts[0]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + m, err := strconv.Atoi(timeParts[1]) + if err != nil { + logbuch.Fatal(err.Error()) + } + + return fmt.Sprintf("0 %d %d * * *", m, h) + } + } else { + parse = func(s string) string { + return cronPadToSecondly(s) + } + } + + for _, s := range strings.Split(c.LeaderboardGenerationTime, ";") { + crons = append(crons, parse(strings.TrimSpace(s))) + } + + return crons } func (c *appConfig) HeartbeatsMaxAge() time.Duration { @@ -352,6 +422,14 @@ func parseWeekday(s string) time.Weekday { return time.Monday } +func cronPadToSecondly(expr string) string { + parts := strings.Split(expr, " ") + if len(parts) == 6 { + return expr + } + return "0 " + expr +} + func Set(config *Config) { cfg = config } @@ -414,16 +492,35 @@ func Load(version string) *Config { if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" { logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider) } - if _, err := time.Parse("15:04", config.App.GetWeeklyReportTime()); err != nil { - logbuch.Fatal("invalid interval set for report_time_weekly") - } - if _, err := time.Parse("15:04", config.App.AggregationTime); err != nil { - logbuch.Fatal("invalid interval set for aggregation_time") - } if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil { logbuch.Fatal("invalid duration set for heartbeat_max_age") } + cronParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + + if _, err := cronParser.Parse(config.App.GetWeeklyReportCron()); err != nil { + logbuch.Fatal("invalid cron expression for report_time_weekly") + } + if _, err := cronParser.Parse(config.App.GetAggregationTimeCron()); err != nil { + logbuch.Fatal("invalid cron expression for aggregation_time") + } + for _, c := range config.App.GetLeaderboardGenerationTimeCron() { + if _, err := cronParser.Parse(c); err != nil { + logbuch.Fatal("invalid cron expression for leaderboard_generation_time") + } + } + + // deprecation notices + if strings.Contains(config.App.AggregationTime, ":") { + logbuch.Warn("you're using deprecated syntax for 'aggregation_time', please change it to a valid cron expression") + } + if strings.Contains(config.App.ReportTimeWeekly, ":") { + logbuch.Warn("you're using deprecated syntax for 'report_time_weekly', please change it to a valid cron expression") + } + if strings.Contains(config.App.LeaderboardGenerationTime, ":") { + logbuch.Warn("you're using deprecated syntax for 'leaderboard_generation_time', please change it to a semicolon-separated list if valid cron expressions") + } + Set(config) return Get() } diff --git a/config/jobqueue.go b/config/jobqueue.go new file mode 100644 index 0000000..baf3c0d --- /dev/null +++ b/config/jobqueue.go @@ -0,0 +1,44 @@ +package config + +import ( + "fmt" + "github.com/muety/artifex" +) + +var jobqueues map[string]*artifex.Dispatcher + +const ( + QueueDefault = "" + QueueProcessing = "processing" + QueueMails = "mails" +) + +func init() { + jobqueues = make(map[string]*artifex.Dispatcher) +} + +func InitQueue(name string, workers int) error { + if _, ok := jobqueues[name]; ok { + return fmt.Errorf("queue '%s' already existing", name) + } + jobqueues[name] = artifex.NewDispatcher(workers, 4096) + jobqueues[name].Start() + return nil +} + +func GetDefaultQueue() *artifex.Dispatcher { + return GetQueue("") +} + +func GetQueue(name string) *artifex.Dispatcher { + if _, ok := jobqueues[name]; !ok { + InitQueue(name, 1) + } + return jobqueues[name] +} + +func CloseQueues() { + for _, q := range jobqueues { + q.Stop() + } +} diff --git a/go.mod b/go.mod index 0a118cf..311afb6 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect github.com/robfig/cron/v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 937577b..a552912 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae h1:f9HA6uXm/MuI1zqsLTUioA2awahxdHv6LtKH8P9njKM= +github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae/go.mod h1:ohgA8vHxRH0ErHcJejxICnAp49PK/6ZezEJ69zrx0/A= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e h1:bR8DQ4ZfItytLJwRlrLOPUHd5z18V6tECwYQFy8W+8g= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e/go.mod h1:m9BzkaxwU4IfPQi9ko23cmuFltayFe8iS0dlRlnEWiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= diff --git a/services/report.go b/services/report.go index c332c29..b2d473c 100644 --- a/services/report.go +++ b/services/report.go @@ -1,21 +1,20 @@ package services import ( + "github.com/duke-git/lancet/v2/slice" "github.com/emvi/logbuch" - "github.com/go-co-op/gocron" "github.com/leandro-lugaresi/hub" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" "math/rand" - "sync" "time" ) -var reportLock = sync.Mutex{} +// delay between evey report generation task (to throttle email sending frequency) +const reportDelay = 15 * time.Second -// range for random offset to add / subtract when scheduling a new job -// to avoid all mails being sent at once, but distributed over 2*offsetIntervalMin minutes -const offsetIntervalMin = 15 +// past time range to cover in the report +const reportRange = 7 * 24 * time.Hour type ReportService struct { config *config.Config @@ -23,7 +22,6 @@ type ReportService struct { summaryService ISummaryService userService IUserService mailService IMailService - scheduler *gocron.Scheduler rand *rand.Rand } @@ -34,80 +32,55 @@ func NewReportService(summaryService ISummaryService, userService IUserService, summaryService: summaryService, userService: userService, mailService: mailService, - scheduler: gocron.NewScheduler(time.Local), rand: rand.New(rand.NewSource(time.Now().Unix())), } - srv.scheduler.StartAsync() - - sub := srv.eventBus.Subscribe(0, config.EventUserUpdate) - go func(sub *hub.Subscription) { - for m := range sub.Receiver { - srv.SyncSchedule(m.Fields[config.FieldPayload].(*models.User)) - } - }(&sub) - return srv } func (srv *ReportService) Schedule() { logbuch.Info("initializing report service") - users, err := srv.userService.GetAllByReports(true) - if err != nil { - config.Log().Fatal("%v", err) - } - - logbuch.Info("scheduling reports for %d users", len(users)) - for _, u := range users { - srv.SyncSchedule(u) - } -} - -// SyncSchedule syncs the currently active schedulers with the user's wish about whether or not to receive reports. -// Returns whether a scheduler is active after this operation has run. -func (srv *ReportService) SyncSchedule(u *models.User) bool { - reportLock.Lock() - defer reportLock.Unlock() - - // unschedule - if !u.ReportsWeekly { - _ = srv.scheduler.RemoveByTag(u.ID) - logbuch.Info("disabled scheduled reports for user %s", u.ID) - return false - } - - // schedule - if job := srv.getJobByTag(u.ID); job == nil && u.ReportsWeekly { - t, _ := time.ParseInLocation("15:04", srv.config.App.GetWeeklyReportTime(), u.TZ()) - t = t.Add(time.Duration(srv.rand.Intn(offsetIntervalMin*60)) * time.Second) - if job, err := srv.scheduler. - SingletonMode(). - Every(1). - Week(). - Weekday(srv.config.App.GetWeeklyReportDay()). - At(t). - Tag(u.ID). - Do(srv.Run, u, 7*24*time.Hour); err != nil { - config.Log().Error("failed to schedule report job for user '%s' - %v", u.ID, err) - } else { - logbuch.Info("next report for user %s is scheduled for %v", u.ID, job.NextRun()) + _, err := config.GetDefaultQueue().DispatchCron(func() { + // fetch all users with reports enabled + users, err := srv.userService.GetAllByReports(true) + if err != nil { + config.Log().Error("failed to get users for report generation, %v", err) + return } - } - return u.ReportsWeekly + // filter users who have their email set + users = slice.Filter[*models.User](users, func(i int, u *models.User) bool { + return u.Email != "" + }) + + // schedule jobs, throttled by one job per 15 seconds + logbuch.Info("scheduling report generation for %d users", len(users)) + for i, u := range users { + err := config.GetQueue(config.QueueMails).DispatchIn(func() { + if err := srv.SendReport(u, reportRange); err != nil { + config.Log().Error("failed to generate report for '%s', %v", u.ID, err) + } + }, time.Duration(i)*reportDelay) + + if err != nil { + config.Log().Error("failed to dispatch report generation job for user '%s', %v", u.ID, err) + } + } + }, srv.config.App.GetWeeklyReportCron()) + + if err != nil { + config.Log().Error("failed to dispatch report generation jobs, %v", err) + } } -func (srv *ReportService) Run(user *models.User, duration time.Duration) error { +func (srv *ReportService) SendReport(user *models.User, duration time.Duration) error { if user.Email == "" { logbuch.Warn("not generating report for '%s' as no e-mail address is set") return nil } - if !srv.SyncSchedule(user) { - logbuch.Info("reports for user '%s' were turned off in the meanwhile since last report job ran") - return nil - } + logbuch.Info("generating report for '%s'", user.ID) end := time.Now().In(user.TZ()) start := time.Now().Add(-1 * duration) @@ -126,21 +99,10 @@ func (srv *ReportService) Run(user *models.User, duration time.Duration) error { } if err := srv.mailService.SendReport(user, report); err != nil { - config.Log().Error("failed to send report for '%s' - %v", user.ID, err) + config.Log().Error("failed to send report for '%s', %v", user.ID, err) return err } logbuch.Info("sent report to user '%s'", user.ID) return nil } - -func (srv *ReportService) getJobByTag(tag string) *gocron.Job { - for _, j := range srv.scheduler.Jobs() { - for _, t := range j.Tags() { - if t == tag { - return j - } - } - } - return nil -} diff --git a/services/services.go b/services/services.go index 867e7bf..4920e30 100644 --- a/services/services.go +++ b/services/services.go @@ -93,8 +93,7 @@ type ISummaryService interface { type IReportService interface { Schedule() - SyncSchedule(user *models.User) bool - Run(*models.User, time.Duration) error + SendReport(*models.User, time.Duration) error } type ILeaderboardService interface { diff --git a/utils/cron.go b/utils/cron.go new file mode 100644 index 0000000..d2444f3 --- /dev/null +++ b/utils/cron.go @@ -0,0 +1,11 @@ +package utils + +import "strings" + +func CronPadToSecondly(expr string) string { + parts := strings.Split(expr, " ") + if len(parts) == 6 { + return expr + } + return "0 " + expr +} From fcca881cfcad480b5335d9d0ddde6311f7127b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 20 Nov 2022 10:10:24 +0100 Subject: [PATCH 02/13] refactor: move more background jobs to using job queue --- config/jobqueue.go | 14 ++++- main.go | 4 +- routes/settings.go | 2 +- services/aggregation.go | 118 +++++++++++++++++++--------------------- services/leaderboard.go | 27 +++++---- services/misc.go | 108 ++++++++++++++++-------------------- services/report.go | 33 ++++++----- services/services.go | 6 +- utils/sync.go | 23 ++++++++ 9 files changed, 179 insertions(+), 156 deletions(-) create mode 100644 utils/sync.go diff --git a/config/jobqueue.go b/config/jobqueue.go index baf3c0d..6db17c7 100644 --- a/config/jobqueue.go +++ b/config/jobqueue.go @@ -2,25 +2,33 @@ package config import ( "fmt" + "github.com/emvi/logbuch" "github.com/muety/artifex" + "math" + "runtime" ) var jobqueues map[string]*artifex.Dispatcher const ( - QueueDefault = "" - QueueProcessing = "processing" - QueueMails = "mails" + QueueDefault = "wakapi.default" + QueueProcessing = "wakapi.processing" + QueueReports = "wakapi.reports" ) func init() { jobqueues = make(map[string]*artifex.Dispatcher) + + InitQueue(QueueDefault, 1) + InitQueue(QueueProcessing, int(math.Ceil(float64(runtime.NumCPU())/2.0))) + InitQueue(QueueReports, 1) } func InitQueue(name string, workers int) error { if _, ok := jobqueues[name]; ok { return fmt.Errorf("queue '%s' already existing", name) } + logbuch.Info("creating job queue '%s' (%d workers)", name, workers) jobqueues[name] = artifex.NewDispatcher(workers, 4096) jobqueues[name].Start() return nil diff --git a/main.go b/main.go index 51fc4bd..237a135 100644 --- a/main.go +++ b/main.go @@ -186,9 +186,9 @@ func main() { // Schedule background tasks go aggregationService.Schedule() - go leaderboardService.ScheduleDefault() - go miscService.ScheduleCountTotalTime() + go leaderboardService.Schedule() go reportService.Schedule() + go miscService.ScheduleCountTotalTime() routes.Init() diff --git a/routes/settings.go b/routes/settings.go index bf2066f..5e8ad9c 100644 --- a/routes/settings.go +++ b/routes/settings.go @@ -661,7 +661,7 @@ func (h *SettingsHandler) regenerateSummaries(user *models.User) error { return err } - if err := h.aggregationSrvc.Run(datastructure.NewSet(user.ID)); err != nil { + if err := h.aggregationSrvc.AggregateSummaries(datastructure.NewSet(user.ID)); err != nil { logbuch.Error("failed to regenerate summaries: %v", err) return err } diff --git a/services/aggregation.go b/services/aggregation.go index f11653a..d2276a4 100644 --- a/services/aggregation.go +++ b/services/aggregation.go @@ -4,12 +4,11 @@ import ( "errors" datastructure "github.com/duke-git/lancet/v2/datastructure/set" "github.com/emvi/logbuch" + "github.com/muety/artifex" "github.com/muety/wakapi/config" - "runtime" "sync" "time" - "github.com/go-co-op/gocron" "github.com/muety/wakapi/models" ) @@ -25,6 +24,8 @@ type AggregationService struct { summaryService ISummaryService heartbeatService IHeartbeatService inProgress datastructure.Set[string] + queueDefault *artifex.Dispatcher + queueWorkers *artifex.Dispatcher } func NewAggregationService(userService IUserService, summaryService ISummaryService, heartbeatService IHeartbeatService) *AggregationService { @@ -34,6 +35,8 @@ func NewAggregationService(userService IUserService, summaryService ISummaryServ summaryService: summaryService, heartbeatService: heartbeatService, inProgress: datastructure.NewSet[string](), + queueDefault: config.GetDefaultQueue(), + queueWorkers: config.GetQueue(config.QueueProcessing), } } @@ -45,58 +48,23 @@ type AggregationJob struct { // Schedule a job to (re-)generate summaries every day shortly after midnight func (srv *AggregationService) Schedule() { - s := gocron.NewScheduler(time.Local) - s.Every(1).Day().At(srv.config.App.AggregationTime).WaitForSchedule().Do(srv.Run, datastructure.NewSet[string]()) - s.StartBlocking() + logbuch.Info("scheduling summary aggregation") + + if _, err := srv.queueDefault.DispatchCron(func() { + if err := srv.AggregateSummaries(datastructure.NewSet[string]()); err != nil { + config.Log().Error("failed to generate summaries, %v", err) + } + }, srv.config.App.GetAggregationTimeCron()); err != nil { + config.Log().Error("failed to schedule summary generation, %v", err) + } } -func (srv *AggregationService) Run(userIds datastructure.Set[string]) error { +func (srv *AggregationService) AggregateSummaries(userIds datastructure.Set[string]) error { if err := srv.lockUsers(userIds); err != nil { return err } defer srv.unlockUsers(userIds) - jobs := make(chan *AggregationJob) - summaries := make(chan *models.Summary) - - for i := 0; i < runtime.NumCPU(); i++ { - go srv.summaryWorker(jobs, summaries) - } - - for i := 0; i < int(srv.config.Db.MaxConn); i++ { - go srv.persistWorker(summaries) - } - - // don't leak open channels - go func(c1 chan *AggregationJob, c2 chan *models.Summary) { - defer close(c1) - defer close(c2) - time.Sleep(1 * time.Hour) - }(jobs, summaries) - - return srv.trigger(jobs, userIds) -} - -func (srv *AggregationService) summaryWorker(jobs <-chan *AggregationJob, summaries chan<- *models.Summary) { - for job := range jobs { - if summary, err := srv.summaryService.Summarize(job.From, job.To, &models.User{ID: job.UserID}, nil); err != nil { - config.Log().Error("failed to generate summary (%v, %v, %s) - %v", job.From, job.To, job.UserID, err) - } else { - logbuch.Info("successfully generated summary (%v, %v, %s)", job.From, job.To, job.UserID) - summaries <- summary - } - } -} - -func (srv *AggregationService) persistWorker(summaries <-chan *models.Summary) { - for summary := range summaries { - if err := srv.summaryService.Insert(summary); err != nil { - config.Log().Error("failed to save summary (%v, %v, %s) - %v", summary.UserID, summary.FromTime, summary.ToTime, err) - } - } -} - -func (srv *AggregationService) trigger(jobs chan<- *AggregationJob, userIds datastructure.Set[string]) error { logbuch.Info("generating summaries") // Get a map from user ids to the time of their latest summary or nil if none exists yet @@ -119,6 +87,19 @@ func (srv *AggregationService) trigger(jobs chan<- *AggregationJob, userIds data firstUserHeartbeatLookup[e.User] = e.Time } + // Dispatch summary generation jobs + jobs := make(chan *AggregationJob) + defer close(jobs) + go func() { + for job := range jobs { + if err := srv.queueWorkers.Dispatch(func() { + srv.process(job) + }); err != nil { + config.Log().Error("failed to dispatch summary generation job for user '%s'", job.UserID) + } + } + }() + // Generate summary aggregation jobs for _, e := range lastUserSummaryTimes { if userIds != nil && !userIds.IsEmpty() && !userIds.Contain(e.User) { @@ -141,24 +122,15 @@ func (srv *AggregationService) trigger(jobs chan<- *AggregationJob, userIds data return nil } -func (srv *AggregationService) lockUsers(userIds datastructure.Set[string]) error { - aggregationLock.Lock() - defer aggregationLock.Unlock() - for uid := range userIds { - if srv.inProgress.Contain(uid) { - return errors.New("aggregation already in progress for at least of the request users") +func (srv *AggregationService) process(job *AggregationJob) { + if summary, err := srv.summaryService.Summarize(job.From, job.To, &models.User{ID: job.UserID}, nil); err != nil { + config.Log().Error("failed to generate summary (%v, %v, %s) - %v", job.From, job.To, job.UserID, err) + } else { + logbuch.Info("successfully generated summary (%v, %v, %s)", job.From, job.To, job.UserID) + if err := srv.summaryService.Insert(summary); err != nil { + config.Log().Error("failed to save summary (%v, %v, %s) - %v", summary.UserID, summary.FromTime, summary.ToTime, err) } } - srv.inProgress = srv.inProgress.Union(userIds) - return nil -} - -func (srv *AggregationService) unlockUsers(userIds datastructure.Set[string]) { - aggregationLock.Lock() - defer aggregationLock.Unlock() - for uid := range userIds { - srv.inProgress.Delete(uid) - } } func generateUserJobs(userId string, from time.Time, jobs chan<- *AggregationJob) { @@ -189,6 +161,26 @@ func generateUserJobs(userId string, from time.Time, jobs chan<- *AggregationJob } } +func (srv *AggregationService) lockUsers(userIds datastructure.Set[string]) error { + aggregationLock.Lock() + defer aggregationLock.Unlock() + for uid := range userIds { + if srv.inProgress.Contain(uid) { + return errors.New("aggregation already in progress for at least of the request users") + } + } + srv.inProgress = srv.inProgress.Union(userIds) + return nil +} + +func (srv *AggregationService) unlockUsers(userIds datastructure.Set[string]) { + aggregationLock.Lock() + defer aggregationLock.Unlock() + for uid := range userIds { + srv.inProgress.Delete(uid) + } +} + func getStartOfToday() time.Time { now := time.Now() return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 1, now.Location()) diff --git a/services/leaderboard.go b/services/leaderboard.go index 0dd2cb1..8bea809 100644 --- a/services/leaderboard.go +++ b/services/leaderboard.go @@ -2,8 +2,8 @@ package services import ( "github.com/emvi/logbuch" - "github.com/go-co-op/gocron" "github.com/leandro-lugaresi/hub" + "github.com/muety/artifex" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" "github.com/muety/wakapi/repositories" @@ -22,6 +22,8 @@ type LeaderboardService struct { repository repositories.ILeaderboardRepository summaryService ISummaryService userService IUserService + queueDefault *artifex.Dispatcher + queueWorkers *artifex.Dispatcher } func NewLeaderboardService(leaderboardRepo repositories.ILeaderboardRepository, summaryService ISummaryService, userService IUserService) *LeaderboardService { @@ -32,6 +34,8 @@ func NewLeaderboardService(leaderboardRepo repositories.ILeaderboardRepository, repository: leaderboardRepo, summaryService: summaryService, userService: userService, + queueDefault: config.GetDefaultQueue(), + queueWorkers: config.GetQueue(config.QueueProcessing), } onUserUpdate := srv.eventBus.Subscribe(0, config.EventUserUpdate) @@ -48,7 +52,7 @@ func NewLeaderboardService(leaderboardRepo repositories.ILeaderboardRepository, if user.PublicLeaderboard && !exists { logbuch.Info("generating leaderboard for '%s' after settings update", user.ID) - srv.Run([]*models.User{user}, models.IntervalPast7Days, []uint8{models.SummaryLanguage}) + srv.ComputeLeaderboard([]*models.User{user}, models.IntervalPast7Days, []uint8{models.SummaryLanguage}) } else if !user.PublicLeaderboard && exists { logbuch.Info("clearing leaderboard for '%s' after settings update", user.ID) if err := srv.repository.DeleteByUser(user.ID); err != nil { @@ -62,23 +66,26 @@ func NewLeaderboardService(leaderboardRepo repositories.ILeaderboardRepository, return srv } -func (srv *LeaderboardService) ScheduleDefault() { - runAllUsers := func(interval *models.IntervalKey, by []uint8) { +func (srv *LeaderboardService) Schedule() { + logbuch.Info("scheduling leaderboard generation") + + generate := func() { users, err := srv.userService.GetAllByLeaderboard(true) if err != nil { config.Log().Error("failed to get users for leaderboard generation - %v", err) return } - - srv.Run(users, interval, by) + srv.ComputeLeaderboard(users, models.IntervalPast7Days, []uint8{models.SummaryLanguage}) } - s := gocron.NewScheduler(time.Local) - s.Every(1).Day().At(srv.config.App.LeaderboardGenerationTime).Do(runAllUsers, models.IntervalPast7Days, []uint8{models.SummaryLanguage}) - s.StartBlocking() + for _, cronExp := range srv.config.App.GetLeaderboardGenerationTimeCron() { + if _, err := srv.queueDefault.DispatchCron(generate, cronExp); err != nil { + config.Log().Error("failed to schedule leaderboard generation (%s), %v", cronExp, err) + } + } } -func (srv *LeaderboardService) Run(users []*models.User, interval *models.IntervalKey, by []uint8) error { +func (srv *LeaderboardService) ComputeLeaderboard(users []*models.User, interval *models.IntervalKey, by []uint8) error { logbuch.Info("generating leaderboard (%s) for %d users (%d aggregations)", (*interval)[0], len(users), len(by)) for _, user := range users { diff --git a/services/misc.go b/services/misc.go index f493209..5d2f72f 100644 --- a/services/misc.go +++ b/services/misc.go @@ -2,12 +2,13 @@ package services import ( "github.com/emvi/logbuch" + "github.com/muety/artifex" "github.com/muety/wakapi/config" - "runtime" + "github.com/muety/wakapi/utils" "strconv" + "sync" "time" - "github.com/go-co-op/gocron" "github.com/muety/wakapi/models" ) @@ -16,6 +17,8 @@ type MiscService struct { userService IUserService summaryService ISummaryService keyValueService IKeyValueService + queueDefault *artifex.Dispatcher + queueWorkers *artifex.Dispatcher } func NewMiscService(userService IUserService, summaryService ISummaryService, keyValueService IKeyValueService) *MiscService { @@ -24,81 +27,64 @@ func NewMiscService(userService IUserService, summaryService ISummaryService, ke userService: userService, summaryService: summaryService, keyValueService: keyValueService, + queueDefault: config.GetDefaultQueue(), + queueWorkers: config.GetQueue(config.QueueProcessing), } } -type CountTotalTimeJob struct { - UserID string - NumJobs int -} - -type CountTotalTimeResult struct { - UserId string - Total time.Duration -} - func (srv *MiscService) ScheduleCountTotalTime() { - s := gocron.NewScheduler(time.Local) - s.Every(1).Hour().WaitForSchedule().Do(srv.runCountTotalTime) - s.StartBlocking() + if _, err := srv.queueDefault.DispatchEvery(srv.CountTotalTime, 1*time.Hour); err != nil { + config.Log().Error("failed to schedule user counting jobs, %v", err) + } } -func (srv *MiscService) runCountTotalTime() error { +func (srv *MiscService) CountTotalTime() { users, err := srv.userService.GetAll() if err != nil { - return err + logbuch.Error("failed to fetch users for time counting, %v", err) } - jobs := make(chan *CountTotalTimeJob, len(users)) - results := make(chan *CountTotalTimeResult, len(users)) + var totalTime time.Duration = 0 + var pendingJobs sync.WaitGroup + pendingJobs.Add(len(users)) for _, u := range users { - jobs <- &CountTotalTimeJob{ - UserID: u.ID, - NumJobs: len(users), + if err := srv.queueWorkers.Dispatch(func() { + defer pendingJobs.Done() + totalTime += srv.countUserTotalTime(u.ID) + }); err != nil { + config.Log().Error("failed to enqueue counting job for user '%s'", u.ID) + pendingJobs.Done() } } - close(jobs) - - for i := 0; i < runtime.NumCPU(); i++ { - go srv.countTotalTimeWorker(jobs, results) - } // persist - var i int - var total time.Duration - for i = 0; i < len(users); i++ { - result := <-results - total += result.Total - } - close(results) - - if err := srv.keyValueService.PutString(&models.KeyStringValue{ - Key: config.KeyLatestTotalTime, - Value: total.String(), - }); err != nil { - logbuch.Error("failed to save total time count: %v", err) - } - - if err := srv.keyValueService.PutString(&models.KeyStringValue{ - Key: config.KeyLatestTotalUsers, - Value: strconv.Itoa(i), - }); err != nil { - logbuch.Error("failed to save total users count: %v", err) - } - - return nil -} - -func (srv *MiscService) countTotalTimeWorker(jobs <-chan *CountTotalTimeJob, results chan<- *CountTotalTimeResult) { - for job := range jobs { - if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve, nil, false); err != nil { - config.Log().Error("failed to count total for user %s: %v", job.UserID, err) - } else { - results <- &CountTotalTimeResult{ - UserId: job.UserID, - Total: result.TotalTime(), + go func(wg *sync.WaitGroup) { + if utils.WaitTimeout(&pendingJobs, 10*time.Minute) { + if err := srv.keyValueService.PutString(&models.KeyStringValue{ + Key: config.KeyLatestTotalTime, + Value: totalTime.String(), + }); err != nil { + logbuch.Error("failed to save total time count: %v", err) } + + if err := srv.keyValueService.PutString(&models.KeyStringValue{ + Key: config.KeyLatestTotalUsers, + Value: strconv.Itoa(len(users)), + }); err != nil { + logbuch.Error("failed to save total users count: %v", err) + } + } else { + config.Log().Error("waiting for user counting jobs timed out") } - } + }(&pendingJobs) +} + +func (srv *MiscService) countUserTotalTime(userId string) time.Duration { + result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: userId}, srv.summaryService.Retrieve, nil, false) + if err != nil { + config.Log().Error("failed to count total for user %s: %v", userId, err) + return 0 + } + return result.TotalTime() } diff --git a/services/report.go b/services/report.go index b2d473c..ce4ca40 100644 --- a/services/report.go +++ b/services/report.go @@ -4,6 +4,7 @@ import ( "github.com/duke-git/lancet/v2/slice" "github.com/emvi/logbuch" "github.com/leandro-lugaresi/hub" + "github.com/muety/artifex" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" "math/rand" @@ -11,7 +12,7 @@ import ( ) // delay between evey report generation task (to throttle email sending frequency) -const reportDelay = 15 * time.Second +const reportDelay = 5 * time.Second // past time range to cover in the report const reportRange = 7 * 24 * time.Hour @@ -23,6 +24,8 @@ type ReportService struct { userService IUserService mailService IMailService rand *rand.Rand + queueDefault *artifex.Dispatcher + queueWorkers *artifex.Dispatcher } func NewReportService(summaryService ISummaryService, userService IUserService, mailService IMailService) *ReportService { @@ -33,15 +36,27 @@ func NewReportService(summaryService ISummaryService, userService IUserService, userService: userService, mailService: mailService, rand: rand.New(rand.NewSource(time.Now().Unix())), + queueDefault: config.GetDefaultQueue(), + queueWorkers: config.GetQueue(config.QueueReports), } return srv } func (srv *ReportService) Schedule() { - logbuch.Info("initializing report service") + logbuch.Info("scheduling report generation") - _, err := config.GetDefaultQueue().DispatchCron(func() { + scheduleUserReport := func(u *models.User, index int) { + if err := srv.queueWorkers.DispatchIn(func() { + if err := srv.SendReport(u, reportRange); err != nil { + config.Log().Error("failed to generate report for '%s', %v", u.ID, err) + } + }, time.Duration(index)*reportDelay); err != nil { + config.Log().Error("failed to dispatch report generation job for user '%s', %v", u.ID, err) + } + } + + _, err := srv.queueDefault.DispatchCron(func() { // fetch all users with reports enabled users, err := srv.userService.GetAllByReports(true) if err != nil { @@ -54,18 +69,10 @@ func (srv *ReportService) Schedule() { return u.Email != "" }) - // schedule jobs, throttled by one job per 15 seconds + // schedule jobs, throttled by one job per x seconds logbuch.Info("scheduling report generation for %d users", len(users)) for i, u := range users { - err := config.GetQueue(config.QueueMails).DispatchIn(func() { - if err := srv.SendReport(u, reportRange); err != nil { - config.Log().Error("failed to generate report for '%s', %v", u.ID, err) - } - }, time.Duration(i)*reportDelay) - - if err != nil { - config.Log().Error("failed to dispatch report generation job for user '%s', %v", u.ID, err) - } + scheduleUserReport(u, i) } }, srv.config.App.GetWeeklyReportCron()) diff --git a/services/services.go b/services/services.go index 4920e30..cbee61a 100644 --- a/services/services.go +++ b/services/services.go @@ -8,7 +8,7 @@ import ( type IAggregationService interface { Schedule() - Run(set datastructure.Set[string]) error + AggregateSummaries(set datastructure.Set[string]) error } type IMiscService interface { @@ -97,8 +97,8 @@ type IReportService interface { } type ILeaderboardService interface { - ScheduleDefault() - Run([]*models.User, *models.IntervalKey, []uint8) error + Schedule() + ComputeLeaderboard([]*models.User, *models.IntervalKey, []uint8) error ExistsAnyByUser(string) (bool, error) CountUsers() (int64, error) GetByInterval(*models.IntervalKey, *models.PageParams, bool) (models.Leaderboard, error) diff --git a/utils/sync.go b/utils/sync.go new file mode 100644 index 0000000..0536970 --- /dev/null +++ b/utils/sync.go @@ -0,0 +1,23 @@ +package utils + +import ( + "sync" + "time" +) + +// WaitTimeout waits for the waitgroup for the specified max timeout. +// Returns true if waiting timed out. +// See // https://stackoverflow.com/a/32843750/3112139. +func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { + c := make(chan struct{}) + go func() { + defer close(c) + wg.Wait() + }() + select { + case <-c: + return false // completed normally + case <-time.After(timeout): + return true // timed out + } +} From 4ce75c2acb255e8a16452af0923f52eaa429c134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 20 Nov 2022 10:11:23 +0100 Subject: [PATCH 03/13] chore: clean up dependencies --- config/config.go | 6 +++--- go.mod | 5 ++--- go.sum | 48 +----------------------------------------------- 3 files changed, 6 insertions(+), 53 deletions(-) diff --git a/config/config.go b/config/config.go index 1302010..4d412ec 100644 --- a/config/config.go +++ b/config/config.go @@ -246,7 +246,7 @@ func (c *appConfig) GetOSColors() map[string]string { func (c *appConfig) GetAggregationTimeCron() string { if strings.Contains(c.AggregationTime, ":") { - // old format, e.g. "15:04" + // old gocron format, e.g. "15:04" timeParts := strings.Split(c.AggregationTime, ":") h, err := strconv.Atoi(timeParts[0]) if err != nil { @@ -266,7 +266,7 @@ func (c *appConfig) GetAggregationTimeCron() string { func (c *appConfig) GetWeeklyReportCron() string { if strings.Contains(c.ReportTimeWeekly, ",") { - // old format, e.g. "fri,18:00" + // old gocron format, e.g. "fri,18:00" split := strings.Split(c.ReportTimeWeekly, ",") weekday := parseWeekday(split[0]) timeParts := strings.Split(split[1], ":") @@ -293,7 +293,7 @@ func (c *appConfig) GetLeaderboardGenerationTimeCron() []string { var parse func(string) string if strings.Contains(c.LeaderboardGenerationTime, ":") { - // old format, e.g. "15:04" + // old gocron format, e.g. "15:04" parse = func(s string) string { timeParts := strings.Split(s, ":") h, err := strconv.Atoi(timeParts[0]) diff --git a/go.mod b/go.mod index 311afb6..808dbfb 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/emvi/logbuch v1.2.0 github.com/getsentry/sentry-go v0.14.0 github.com/glebarez/sqlite v1.5.0 - github.com/go-co-op/gocron v1.17.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 @@ -20,8 +19,10 @@ require ( github.com/leandro-lugaresi/hub v1.1.1 github.com/lpar/gzipped/v2 v2.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 + github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/robfig/cron/v3 v3.0.1 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.8.0 github.com/swaggo/http-swagger v1.3.3 @@ -63,10 +64,8 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect - github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect - github.com/robfig/cron/v3 v3.0.1 // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect diff --git a/go.sum b/go.sum index a552912..7ff1055 100644 --- a/go.sum +++ b/go.sum @@ -29,21 +29,12 @@ github.com/emvi/logbuch v1.2.0/go.mod h1:hFxe0XQOFl76SkE/f0Pt5oQbXRZtyGa8EroBrrb github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo= -github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= -github.com/glebarez/go-sqlite v1.18.2 h1:ck3PQVaEzzzapP0g7pfhzbB3Jw4rNk+IldLMy/lgdeQ= -github.com/glebarez/go-sqlite v1.18.2/go.mod h1:/kOdnnt5T0ztYXqBPdjRVM8JwMpFtyAQp1mtRoNxziM= github.com/glebarez/go-sqlite v1.19.1 h1:o2XhjyR8CQ2m84+bVz10G0cabmG0tY4sIMiCbrcUTrY= github.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M= -github.com/glebarez/sqlite v1.4.7 h1:tIBxEWLJOPkekuQcwfenNfh13itj9GoVJYxp7GidJAo= -github.com/glebarez/sqlite v1.4.7/go.mod h1:UY1smw9rBTSGnJE0He8pVRPvlxCP1C8hlB8Z24K8fG4= github.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk= github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY= -github.com/go-co-op/gocron v1.17.0 h1:IixLXsti+Qo0wMvmn6Kmjp2csk2ykpkcL+EmHmST18w= -github.com/go-co-op/gocron v1.17.0/go.mod h1:IpDBSaJOVfFw7hXZuTag3SCSkqazXBBUkbQ1m1aesBs= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -66,7 +57,6 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -81,7 +71,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -102,7 +91,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -173,7 +161,6 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -187,8 +174,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -252,8 +239,6 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= @@ -271,8 +256,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ= -golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -295,8 +278,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -325,7 +306,6 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -340,73 +320,47 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.3.6 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM= -gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic= gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/postgres v1.3.10 h1:Fsd+pQpFMGlGxxVMUPJhNo8gG8B1lKtk8QQ4/VZZAJw= -gorm.io/driver/postgres v1.3.10/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw= gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= -gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ= -gorm.io/driver/sqlite v1.3.6/go.mod h1:Sg1/pvnKtbQ7jLXxfZa+jSHvoX8hoZA8cn4xllOMTgE= gorm.io/driver/sqlite v1.4.2 h1:F6vYJcmR4Cnh0ErLyoY8JSfabBGyR0epIGuhgHJuNws= gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.23.10 h1:4Ne9ZbzID9GUxRkllxN4WjJKpsHx8YbKvekVdgyWh24= -gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.20.0 h1:MEbCfCKpuDC/LRb3HOCM9fZOqnPx8le3kzTJVmUGDbU= -modernc.org/libc v1.20.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/libc v1.20.3 h1:BodaDPuUse7taQchAClMmbE/yZp3T2ZBiwCDFyBLEXw= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4= modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0= modernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= From 99e50b1062305668b18efb967f698b3b974e7d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 20 Nov 2022 10:12:34 +0100 Subject: [PATCH 04/13] chore: logging --- services/misc.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/misc.go b/services/misc.go index 5d2f72f..7f178b7 100644 --- a/services/misc.go +++ b/services/misc.go @@ -33,6 +33,7 @@ func NewMiscService(userService IUserService, summaryService ISummaryService, ke } func (srv *MiscService) ScheduleCountTotalTime() { + logbuch.Info("scheduling total time counting") if _, err := srv.queueDefault.DispatchEvery(srv.CountTotalTime, 1*time.Hour); err != nil { config.Log().Error("failed to schedule user counting jobs, %v", err) } @@ -41,7 +42,7 @@ func (srv *MiscService) ScheduleCountTotalTime() { func (srv *MiscService) CountTotalTime() { users, err := srv.userService.GetAll() if err != nil { - logbuch.Error("failed to fetch users for time counting, %v", err) + config.Log().Error("failed to fetch users for time counting, %v", err) } var totalTime time.Duration = 0 @@ -65,14 +66,14 @@ func (srv *MiscService) CountTotalTime() { Key: config.KeyLatestTotalTime, Value: totalTime.String(), }); err != nil { - logbuch.Error("failed to save total time count: %v", err) + config.Log().Error("failed to save total time count: %v", err) } if err := srv.keyValueService.PutString(&models.KeyStringValue{ Key: config.KeyLatestTotalUsers, Value: strconv.Itoa(len(users)), }); err != nil { - logbuch.Error("failed to save total users count: %v", err) + config.Log().Error("failed to save total users count: %v", err) } } else { config.Log().Error("waiting for user counting jobs timed out") From 61f13fce200193ec883b7a458b84413863a4dc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 20 Nov 2022 10:59:06 +0100 Subject: [PATCH 05/13] fix: prometheus metrics types --- config/jobqueue.go | 37 +++++++++++++++------ go.mod | 38 +++++++++++----------- go.sum | 59 ++++++++++++++++++++++++++++++++-- models/metrics/gauge_metric.go | 22 +++++++++++++ routes/api/metrics.go | 56 +++++++++++++++++++++----------- 5 files changed, 164 insertions(+), 48 deletions(-) create mode 100644 models/metrics/gauge_metric.go diff --git a/config/jobqueue.go b/config/jobqueue.go index 6db17c7..b96a39c 100644 --- a/config/jobqueue.go +++ b/config/jobqueue.go @@ -8,7 +8,8 @@ import ( "runtime" ) -var jobqueues map[string]*artifex.Dispatcher +var jobQueues map[string]*artifex.Dispatcher +var jobCounts map[string]int const ( QueueDefault = "wakapi.default" @@ -16,8 +17,14 @@ const ( QueueReports = "wakapi.reports" ) +type JobQueueMetrics struct { + Queue string + EnqueuedJobs int + FinishedJobs int +} + func init() { - jobqueues = make(map[string]*artifex.Dispatcher) + jobQueues = make(map[string]*artifex.Dispatcher) InitQueue(QueueDefault, 1) InitQueue(QueueProcessing, int(math.Ceil(float64(runtime.NumCPU())/2.0))) @@ -25,28 +32,40 @@ func init() { } func InitQueue(name string, workers int) error { - if _, ok := jobqueues[name]; ok { + if _, ok := jobQueues[name]; ok { return fmt.Errorf("queue '%s' already existing", name) } logbuch.Info("creating job queue '%s' (%d workers)", name, workers) - jobqueues[name] = artifex.NewDispatcher(workers, 4096) - jobqueues[name].Start() + jobQueues[name] = artifex.NewDispatcher(workers, 4096) + jobQueues[name].Start() return nil } func GetDefaultQueue() *artifex.Dispatcher { - return GetQueue("") + return GetQueue(QueueDefault) } func GetQueue(name string) *artifex.Dispatcher { - if _, ok := jobqueues[name]; !ok { + if _, ok := jobQueues[name]; !ok { InitQueue(name, 1) } - return jobqueues[name] + return jobQueues[name] +} + +func GetQueueMetrics() []*JobQueueMetrics { + metrics := make([]*JobQueueMetrics, 0, len(jobQueues)) + for name, queue := range jobQueues { + metrics = append(metrics, &JobQueueMetrics{ + Queue: name, + EnqueuedJobs: queue.CountEnqueued(), + FinishedJobs: queue.CountDispatched(), + }) + } + return metrics } func CloseQueues() { - for _, q := range jobqueues { + for _, q := range jobQueues { q.Stop() } } diff --git a/go.mod b/go.mod index 808dbfb..4e0e256 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.18 require ( codeberg.org/Codeberg/avatars v1.0.0 - github.com/duke-git/lancet/v2 v2.1.6 + github.com/duke-git/lancet/v2 v2.1.10 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead github.com/emersion/go-smtp v0.15.0 github.com/emvi/logbuch v1.2.0 - github.com/getsentry/sentry-go v0.14.0 + github.com/getsentry/sentry-go v0.15.0 github.com/glebarez/sqlite v1.5.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 @@ -19,29 +19,29 @@ require ( github.com/leandro-lugaresi/hub v1.1.1 github.com/lpar/gzipped/v2 v2.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 - github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae + github.com/muety/artifex v0.0.0-20221120093027-024690fdd028 github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e github.com/patrickmn/go-cache v2.1.0+incompatible github.com/robfig/cron/v3 v3.0.1 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.8.0 github.com/swaggo/http-swagger v1.3.3 - github.com/swaggo/swag v1.8.6 + github.com/swaggo/swag v1.8.7 go.uber.org/atomic v1.10.0 - golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b - golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - gorm.io/driver/mysql v1.4.1 - gorm.io/driver/postgres v1.4.4 - gorm.io/driver/sqlite v1.4.2 - gorm.io/gorm v1.24.0 + golang.org/x/crypto v0.3.0 + golang.org/x/sync v0.1.0 + gorm.io/driver/mysql v1.4.4 + gorm.io/driver/postgres v1.4.5 + gorm.io/driver/sqlite v1.4.3 + gorm.io/gorm v1.24.1 ) require ( - github.com/BurntSushi/toml v1.2.0 // indirect + github.com/BurntSushi/toml v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/glebarez/go-sqlite v1.19.1 // indirect + github.com/glebarez/go-sqlite v1.19.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.7 // indirect @@ -68,15 +68,15 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect - golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect - golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect - golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/image v0.1.0 // indirect + golang.org/x/net v0.2.0 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/text v0.4.0 // indirect + golang.org/x/tools v0.3.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.20.3 // indirect + modernc.org/libc v1.21.4 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.4.0 // indirect - modernc.org/sqlite v1.19.1 // indirect + modernc.org/sqlite v1.19.4 // indirect ) diff --git a/go.sum b/go.sum index 7ff1055..7a85ae6 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ codeberg.org/Codeberg/avatars v1.0.0/go.mod h1:ML/htpPRb3+owhkm4+qG2ZrXnk5WXaQLA github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -18,6 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/duke-git/lancet/v2 v2.1.6 h1:zRWZkK3IAoGnzEonbrkmUP2NyHqtH9qIlW0AaSQrzmY= github.com/duke-git/lancet/v2 v2.1.6/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA= +github.com/duke-git/lancet/v2 v2.1.10 h1:q6YKhbYg6KChBS+T41e/IhK+sTDPVk2wRhWLTevCeuY= +github.com/duke-git/lancet/v2 v2.1.10/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y= @@ -31,8 +35,12 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= +github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA= +github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= github.com/glebarez/go-sqlite v1.19.1 h1:o2XhjyR8CQ2m84+bVz10G0cabmG0tY4sIMiCbrcUTrY= github.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M= +github.com/glebarez/go-sqlite v1.19.2 h1:mTtntWN3wk9UNjIf6F7Upqnfq96p+cjhfgCsupUd1hY= +github.com/glebarez/go-sqlite v1.19.2/go.mod h1:DoubC3Kn5X6EBvDa2iaxAdIJqPNmY7M/sOCpfa8fus0= github.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk= github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -58,6 +66,7 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -71,6 +80,7 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -91,6 +101,7 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -166,8 +177,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae h1:f9HA6uXm/MuI1zqsLTUioA2awahxdHv6LtKH8P9njKM= -github.com/muety/artifex v0.0.0-20221119195407-0ccdcf919cae/go.mod h1:ohgA8vHxRH0ErHcJejxICnAp49PK/6ZezEJ69zrx0/A= +github.com/muety/artifex v0.0.0-20221120093027-024690fdd028 h1:kUGQw7rRtg7OkkjXqnUvz0Eh9pOVMv9n8B2oKhthdTI= +github.com/muety/artifex v0.0.0-20221120093027-024690fdd028/go.mod h1:ohgA8vHxRH0ErHcJejxICnAp49PK/6ZezEJ69zrx0/A= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e h1:bR8DQ4ZfItytLJwRlrLOPUHd5z18V6tECwYQFy8W+8g= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e/go.mod h1:m9BzkaxwU4IfPQi9ko23cmuFltayFe8iS0dlRlnEWiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -214,7 +225,10 @@ github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCG github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo= github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= +github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU= +github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -238,16 +252,23 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= +golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -256,12 +277,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -277,11 +304,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -289,6 +321,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -301,6 +335,8 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -322,22 +358,34 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic= gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ= +gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM= gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw= gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= +gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= +gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= gorm.io/driver/sqlite v1.4.2 h1:F6vYJcmR4Cnh0ErLyoY8JSfabBGyR0epIGuhgHJuNws= gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= +gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v3 v3.16.12/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= @@ -346,6 +394,8 @@ modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/libc v1.20.3 h1:BodaDPuUse7taQchAClMmbE/yZp3T2ZBiwCDFyBLEXw= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8= +modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= @@ -358,9 +408,14 @@ modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4= modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms= +modernc.org/sqlite v1.19.2/go.mod h1:fEgebDYAGTFJj2c/ukKmnaq/0ZQZg0PSYxRa/bHyCDs= +modernc.org/sqlite v1.19.4 h1:nlPIDqumn6/mSvs7T5C8MNYEuN73sISzPdKtMdURpUI= +modernc.org/sqlite v1.19.4/go.mod h1:x/yZNb3h5+I3zGQSlwIv4REL5eJhiRkUH5MReogAeIc= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM= +modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= +modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= diff --git a/models/metrics/gauge_metric.go b/models/metrics/gauge_metric.go new file mode 100644 index 0000000..651281b --- /dev/null +++ b/models/metrics/gauge_metric.go @@ -0,0 +1,22 @@ +package metrics + +import "fmt" + +type GaugeMetric struct { + Name string + Value int64 + Desc string + Labels Labels +} + +func (c GaugeMetric) Key() string { + return c.Name +} + +func (c GaugeMetric) Print() string { + return fmt.Sprintf("%s%s %d", c.Name, c.Labels.Print(), c.Value) +} + +func (c GaugeMetric) Header() string { + return fmt.Sprintf("# HELP %s %s\n# TYPE %s gauge", c.Name, c.Desc, c.Name) +} diff --git a/routes/api/metrics.go b/routes/api/metrics.go index 7fe98bc..fcab86e 100644 --- a/routes/api/metrics.go +++ b/routes/api/metrics.go @@ -37,6 +37,9 @@ const ( DescAdminTotalUsers = "Total number of registered users." DescAdminActiveUsers = "Number of active users." + DescJobQueueEnqueued = "Number of jobs currently enqueued" + DescJobQueueTotalFinished = "Total number of processed jobs" + DescMemAllocTotal = "Total number of bytes allocated for heap" DescMemSysTotal = "Total number of bytes obtained from the OS" DescGoroutines = "Total number of running goroutines" @@ -142,21 +145,21 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) // User Metrics - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_cumulative_seconds_total", Desc: DescAllTime, Value: int64(v1.NewAllTimeFrom(summaryAllTime).Data.TotalSeconds), Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_seconds_total", Desc: DescTotal, Value: int64(summaryToday.TotalTime().Seconds()), Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_heartbeats_total", Desc: DescHeartbeats, Value: int64(heartbeatCount), @@ -164,7 +167,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) }) for _, p := range summaryToday.Projects { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_project_seconds_total", Desc: DescProjects, Value: int64(summaryToday.TotalTimeByKey(models.SummaryProject, p.Key).Seconds()), @@ -173,7 +176,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) } for _, l := range summaryToday.Languages { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_language_seconds_total", Desc: DescLanguages, Value: int64(summaryToday.TotalTimeByKey(models.SummaryLanguage, l.Key).Seconds()), @@ -182,7 +185,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) } for _, e := range summaryToday.Editors { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_editor_seconds_total", Desc: DescEditors, Value: int64(summaryToday.TotalTimeByKey(models.SummaryEditor, e.Key).Seconds()), @@ -191,7 +194,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) } for _, o := range summaryToday.OperatingSystems { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_operating_system_seconds_total", Desc: DescOperatingSystems, Value: int64(summaryToday.TotalTimeByKey(models.SummaryOS, o.Key).Seconds()), @@ -200,7 +203,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) } for _, m := range summaryToday.Machines { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_machine_seconds_total", Desc: DescMachines, Value: int64(summaryToday.TotalTimeByKey(models.SummaryMachine, m.Key).Seconds()), @@ -209,7 +212,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) } for _, m := range summaryToday.Labels { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_label_seconds_total", Desc: DescLabels, Value: int64(summaryToday.TotalTimeByKey(models.SummaryLabel, m.Key).Seconds()), @@ -221,21 +224,21 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) var memStats runtime.MemStats runtime.ReadMemStats(&memStats) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_goroutines_total", Desc: DescGoroutines, Value: int64(runtime.NumGoroutine()), Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_mem_alloc_total", Desc: DescMemAllocTotal, Value: int64(memStats.Alloc), Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_mem_sys_total", Desc: DescMemSysTotal, Value: int64(memStats.Sys), @@ -248,13 +251,30 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) logbuch.Warn("failed to get database size (%v)", err) } - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_db_total_bytes", Desc: DescDatabaseSize, Value: dbSize, Labels: []mm.Label{}, }) + // Miscellaneous + for _, qm := range conf.GetQueueMetrics() { + metrics = append(metrics, &mm.GaugeMetric{ + Name: MetricsPrefix + "_queue_jobs_enqueued", + Value: int64(qm.EnqueuedJobs), + Desc: DescJobQueueEnqueued, + Labels: []mm.Label{{Key: "queue", Value: qm.Queue}}, + }) + + metrics = append(metrics, &mm.CounterMetric{ + Name: MetricsPrefix + "_queue_jobs_total_finished", + Value: int64(qm.FinishedJobs), + Desc: DescJobQueueTotalFinished, + Labels: []mm.Label{{Key: "queue", Value: qm.Queue}}, + }) + } + return &metrics, nil } @@ -281,28 +301,28 @@ func (h *MetricsHandler) getAdminMetrics(user *models.User) (*mm.Metrics, error) return nil, err } - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_admin_seconds_total", Desc: DescAdminTotalTime, Value: int64(totalSeconds), Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_admin_heartbeats_total", Desc: DescAdminTotalHeartbeats, Value: totalHeartbeats, Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_admin_users_total", Desc: DescAdminTotalUsers, Value: totalUsers, Labels: []mm.Label{}, }) - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_admin_users_active_total", Desc: DescAdminActiveUsers, Value: int64(len(activeUsers)), @@ -318,7 +338,7 @@ func (h *MetricsHandler) getAdminMetrics(user *models.User) (*mm.Metrics, error) } for _, uc := range userCounts { - metrics = append(metrics, &mm.CounterMetric{ + metrics = append(metrics, &mm.GaugeMetric{ Name: MetricsPrefix + "_admin_user_heartbeats_total", Desc: DescAdminUserHeartbeats, Value: uc.Count, From c13fc96a169838dc1463e2d54d5e2af88350f213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 20 Nov 2022 11:09:51 +0100 Subject: [PATCH 06/13] refactor: use job queue for data imports --- config/jobqueue.go | 2 ++ services/imports/wakatime.go | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config/jobqueue.go b/config/jobqueue.go index b96a39c..2e82741 100644 --- a/config/jobqueue.go +++ b/config/jobqueue.go @@ -15,6 +15,7 @@ const ( QueueDefault = "wakapi.default" QueueProcessing = "wakapi.processing" QueueReports = "wakapi.reports" + QueueImports = "wakapi.imports" ) type JobQueueMetrics struct { @@ -29,6 +30,7 @@ func init() { InitQueue(QueueDefault, 1) InitQueue(QueueProcessing, int(math.Ceil(float64(runtime.NumCPU())/2.0))) InitQueue(QueueReports, 1) + InitQueue(QueueImports, 1) } func InitQueue(name string, workers int) error { diff --git a/services/imports/wakatime.go b/services/imports/wakatime.go index 94dd354..50a318b 100644 --- a/services/imports/wakatime.go +++ b/services/imports/wakatime.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "github.com/duke-git/lancet/v2/datetime" + "github.com/muety/artifex" "github.com/muety/wakapi/utils" "net/http" "strings" @@ -32,19 +33,23 @@ const ( type WakatimeHeartbeatImporter struct { ApiKey string httpClient *http.Client + queue *artifex.Dispatcher } func NewWakatimeHeartbeatImporter(apiKey string) *WakatimeHeartbeatImporter { return &WakatimeHeartbeatImporter{ ApiKey: apiKey, httpClient: &http.Client{Timeout: 10 * time.Second}, + queue: config.GetQueue(config.QueueImports), } } func (w *WakatimeHeartbeatImporter) Import(user *models.User, minFrom time.Time, maxTo time.Time) <-chan *models.Heartbeat { out := make(chan *models.Heartbeat) - go func(user *models.User, out chan *models.Heartbeat) { + process := func(user *models.User, minFrom time.Time, maxTo time.Time, out chan *models.Heartbeat) { + logbuch.Info("running wakatime import for user '%s'", user.ID) + baseUrl := user.WakaTimeURL(config.WakatimeApiUrl) startDate, endDate, err := w.fetchRange(baseUrl) @@ -109,7 +114,14 @@ func (w *WakatimeHeartbeatImporter) Import(user *models.User, minFrom time.Time, } }(d) } - }(user, out) + } + + logbuch.Info("scheduling wakatime import for user '%s'", user.ID) + if err := w.queue.Dispatch(func() { + process(user, minFrom, maxTo, out) + }); err != nil { + config.Log().Error("failed to dispatch wakatime import job for user '%s', %v", user.ID, err) + } return out } From c5fda029003bed7f948c181c5dc240c84d863578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 10:10:39 +0100 Subject: [PATCH 07/13] docs: update default cron expressions --- README.md | 5 +++-- config.default.yml | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 996ca95..6869e84 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,9 @@ You can specify configuration options either via a config file (default: `config | YAML key / Env. variable | Default | Description | |------------------------------------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `env` /
`ENVIRONMENT` | `dev` | Whether to use development- or production settings | -| `app.aggregation_time` /
`WAKAPI_AGGREGATION_TIME` | `02:15` | Time of day at which to periodically run summary generation for all users | -| `app.report_time_weekly` /
`WAKAPI_REPORT_TIME_WEEKLY` | `fri,18:00` | Week day and time at which to send e-mail reports | +| `app.aggregation_time` /
`WAKAPI_AGGREGATION_TIME` | `0 15 2 * * *` | Time of day at which to periodically run summary generation for all users | +| `app.report_time_weekly` /
`WAKAPI_REPORT_TIME_WEEKLY` | `0 0 18 * * 5` | Week day and time at which to send e-mail reports | +| `app.leaderboard_generation_time` /
`WAKAPI_LEADERBOARD_GENERATION_TIME` | `0 0 6 * * *,0 0 18 * * *` | One or multiple times of day at which to re-calculate the leaderboard | | `app.import_batch_size` /
`WAKAPI_IMPORT_BATCH_SIZE` | `50` | Size of batches of heartbeats to insert to the database during importing from external services | | `app.inactive_days` /
`WAKAPI_INACTIVE_DAYS` | `7` | Number of days after which to consider a user inactive (only for metrics) | | `app.heartbeat_max_age /`
`WAKAPI_HEARTBEAT_MAX_AGE` | `4320h` | Maximum acceptable age of a heartbeat (see [`ParseDuration`](https://pkg.go.dev/time#ParseDuration)) | diff --git a/config.default.yml b/config.default.yml index 7a7c9c4..b3b48fe 100644 --- a/config.default.yml +++ b/config.default.yml @@ -12,12 +12,12 @@ server: public_url: http://localhost:3000 # required for links (e.g. password reset) in e-mail app: - aggregation_time: '02:15' # time at which to run daily aggregation batch jobs - leaderboard_generation_time: '06:00;18:00' # time at which to run daily aggregation batch jobs - report_time_weekly: 'fri,18:00' # time at which to fan out weekly reports (format: ',') - inactive_days: 7 # time of previous days within a user must have logged in to be considered active - import_batch_size: 50 # maximum number of heartbeats to insert into the database within one transaction - heartbeat_max_age: '4320h' # maximum acceptable age of a heartbeat (see https://pkg.go.dev/time#ParseDuration) + aggregation_time: '0 15 2 * * *' # time at which to run daily aggregation batch jobs + leaderboard_generation_time: '0 0 6 * * *,0 0 18 * * *' # times at which to re-calculate the leaderboard + report_time_weekly: '0 0 18 * * 5' # time at which to fan out weekly reports (extended cron) + inactive_days: 7 # time of previous days within a user must have logged in to be considered active + import_batch_size: 50 # maximum number of heartbeats to insert into the database within one transaction + heartbeat_max_age: '4320h' # maximum acceptable age of a heartbeat (see https://pkg.go.dev/time#ParseDuration) custom_languages: vue: Vue jsx: JSX From 21f6809f052d5d5110369ff75d64ddca8b07d47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 10:57:07 +0100 Subject: [PATCH 08/13] refactor: split utility functions into utils and helpers --- config/config.go | 58 ++------ config/utils.go | 14 -- utils/common.go => helpers/date.go | 29 ++-- helpers/helpers.go | 4 + helpers/http.go | 20 +++ {utils => helpers}/summary.go | 135 ++++++++++--------- middlewares/authenticate.go | 3 +- models/compat/shields/v1/badge.go | 4 +- models/compat/wakatime/v1/all_time.go | 8 +- models/compat/wakatime/v1/summaries.go | 8 +- routes/api/diagnostics.go | 7 +- routes/api/heartbeat.go | 3 +- routes/api/metrics.go | 4 +- routes/api/summary.go | 4 +- routes/compat/shields/v1/badge.go | 8 +- routes/compat/wakatime/v1/all_time.go | 6 +- routes/compat/wakatime/v1/heartbeat.go | 4 +- routes/compat/wakatime/v1/projects.go | 4 +- routes/compat/wakatime/v1/stats.go | 8 +- routes/compat/wakatime/v1/statusbar.go | 6 +- routes/compat/wakatime/v1/summaries.go | 13 +- routes/compat/wakatime/v1/users.go | 4 +- routes/routes.go | 17 ++- routes/summary.go | 3 +- routes/utils/badge_utils.go | 6 +- routes/utils/summary_utils.go | 4 +- services/leaderboard.go | 6 +- services/mail/mail.go | 3 +- utils/auth.go | 6 +- utils/collection.go | 21 +++ utils/{common_test.go => collection_test.go} | 0 utils/date.go | 30 +++-- utils/date_test.go | 5 +- utils/http.go | 20 +-- utils/strings.go | 20 +++ 35 files changed, 259 insertions(+), 236 deletions(-) delete mode 100644 config/utils.go rename utils/common.go => helpers/date.go (71%) create mode 100644 helpers/helpers.go create mode 100644 helpers/http.go rename {utils => helpers}/summary.go (86%) create mode 100644 utils/collection.go rename utils/{common_test.go => collection_test.go} (100%) diff --git a/config/config.go b/config/config.go index 4d412ec..4d18c64 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/muety/wakapi/utils" "github.com/robfig/cron/v3" "io/ioutil" "net/http" @@ -229,19 +230,19 @@ func (c *Config) GetMigrationFunc(dbDialect string) models.MigrationFunc { } func (c *appConfig) GetCustomLanguages() map[string]string { - return cloneStringMap(c.CustomLanguages, false) + return utils.CloneStringMap(c.CustomLanguages, false) } func (c *appConfig) GetLanguageColors() map[string]string { - return cloneStringMap(c.Colors["languages"], true) + return utils.CloneStringMap(c.Colors["languages"], true) } func (c *appConfig) GetEditorColors() map[string]string { - return cloneStringMap(c.Colors["editors"], true) + return utils.CloneStringMap(c.Colors["editors"], true) } func (c *appConfig) GetOSColors() map[string]string { - return cloneStringMap(c.Colors["operating_systems"], true) + return utils.CloneStringMap(c.Colors["operating_systems"], true) } func (c *appConfig) GetAggregationTimeCron() string { @@ -261,14 +262,14 @@ func (c *appConfig) GetAggregationTimeCron() string { return fmt.Sprintf("0 %d %d * * *", m, h) } - return cronPadToSecondly(c.AggregationTime) + return utils.CronPadToSecondly(c.AggregationTime) } func (c *appConfig) GetWeeklyReportCron() string { if strings.Contains(c.ReportTimeWeekly, ",") { // old gocron format, e.g. "fri,18:00" split := strings.Split(c.ReportTimeWeekly, ",") - weekday := parseWeekday(split[0]) + weekday := utils.ParseWeekday(split[0]) timeParts := strings.Split(split[1], ":") h, err := strconv.Atoi(timeParts[0]) @@ -284,7 +285,7 @@ func (c *appConfig) GetWeeklyReportCron() string { return fmt.Sprintf("0 %d %d * * %d", m, h, weekday) } - return cronPadToSecondly(c.ReportTimeWeekly) + return utils.CronPadToSecondly(c.ReportTimeWeekly) } func (c *appConfig) GetLeaderboardGenerationTimeCron() []string { @@ -310,11 +311,11 @@ func (c *appConfig) GetLeaderboardGenerationTimeCron() []string { } } else { parse = func(s string) string { - return cronPadToSecondly(s) + return utils.CronPadToSecondly(s) } } - for _, s := range strings.Split(c.LeaderboardGenerationTime, ";") { + for _, s := range utils.SplitMulti(c.LeaderboardGenerationTime, ",", ";") { crons = append(crons, parse(strings.TrimSpace(s))) } @@ -393,43 +394,6 @@ func resolveDbDialect(dbType string) string { return dbType } -func findString(needle string, haystack []string, defaultVal string) string { - for _, s := range haystack { - if s == needle { - return s - } - } - return defaultVal -} - -func parseWeekday(s string) time.Weekday { - switch strings.ToLower(s) { - case "mon", strings.ToLower(time.Monday.String()): - return time.Monday - case "tue", strings.ToLower(time.Tuesday.String()): - return time.Tuesday - case "wed", strings.ToLower(time.Wednesday.String()): - return time.Wednesday - case "thu", strings.ToLower(time.Thursday.String()): - return time.Thursday - case "fri", strings.ToLower(time.Friday.String()): - return time.Friday - case "sat", strings.ToLower(time.Saturday.String()): - return time.Saturday - case "sun", strings.ToLower(time.Sunday.String()): - return time.Sunday - } - return time.Monday -} - -func cronPadToSecondly(expr string) string { - parts := strings.Split(expr, " ") - if len(parts) == 6 { - return expr - } - return "0 " + expr -} - func Set(config *Config) { cfg = config } @@ -489,7 +453,7 @@ func Load(version string) *Config { logbuch.Warn("with sqlite, only a single connection is supported") // otherwise 'PRAGMA foreign_keys=ON' would somehow have to be set for every connection in the pool config.Db.MaxConn = 1 } - if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" { + if config.Mail.Provider != "" && utils.FindString(config.Mail.Provider, emailProviders, "") == "" { logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider) } if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil { diff --git a/config/utils.go b/config/utils.go deleted file mode 100644 index 942c66d..0000000 --- a/config/utils.go +++ /dev/null @@ -1,14 +0,0 @@ -package config - -import "strings" - -func cloneStringMap(m map[string]string, keysToLower bool) map[string]string { - m2 := make(map[string]string) - for k, v := range m { - if keysToLower { - k = strings.ToLower(k) - } - m2[k] = v - } - return m2 -} diff --git a/utils/common.go b/helpers/date.go similarity index 71% rename from utils/common.go rename to helpers/date.go index 69f5b20..c099d40 100644 --- a/utils/common.go +++ b/helpers/date.go @@ -1,9 +1,8 @@ -package utils +package helpers import ( - "errors" + "fmt" "github.com/muety/wakapi/config" - "regexp" "time" ) @@ -41,22 +40,10 @@ func FormatDateHuman(date time.Time) string { return date.Format("Mon, 02 Jan 2006") } -func Add(i, j int) int { - return i + j -} - -func ParseUserAgent(ua string) (string, string, error) { - re := regexp.MustCompile(`(?iU)^wakatime\/(?:v?[\d+.]+|unset)\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`) - groups := re.FindAllStringSubmatch(ua, -1) - if len(groups) == 0 || len(groups[0]) != 3 { - return "", "", errors.New("failed to parse user agent string") - } - return groups[0][1], groups[0][2], nil -} - -func SubSlice[T any](slice []T, from, to uint) []T { - if int(to) > len(slice) { - to = uint(len(slice)) - } - return slice[from:int(to)] +func FmtWakatimeDuration(d time.Duration) string { + d = d.Round(time.Minute) + h := d / time.Hour + d -= h * time.Hour + m := d / time.Minute + return fmt.Sprintf("%d hrs %d mins", h, m) } diff --git a/helpers/helpers.go b/helpers/helpers.go new file mode 100644 index 0000000..f6c2dbf --- /dev/null +++ b/helpers/helpers.go @@ -0,0 +1,4 @@ +package helpers + +// helpers are different from utils in that they contain wakapi-specific utility functions +// also, helpers may depend on the config package, while utils must be entirely static diff --git a/helpers/http.go b/helpers/http.go new file mode 100644 index 0000000..4318832 --- /dev/null +++ b/helpers/http.go @@ -0,0 +1,20 @@ +package helpers + +import ( + "encoding/json" + "github.com/muety/wakapi/config" + "github.com/muety/wakapi/utils" + "net/http" +) + +func ExtractCookieAuth(r *http.Request) (username *string, err error) { + return utils.ExtractCookieAuth(r, config.Get().Security.SecureCookie) +} + +func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if err := json.NewEncoder(w).Encode(object); err != nil { + config.Log().Request(r).Error("error while writing json response: %v", err) + } +} diff --git a/utils/summary.go b/helpers/summary.go similarity index 86% rename from utils/summary.go rename to helpers/summary.go index e2f62e9..842c6d2 100644 --- a/utils/summary.go +++ b/helpers/summary.go @@ -1,78 +1,13 @@ -package utils +package helpers import ( "errors" "github.com/muety/wakapi/models" + "github.com/muety/wakapi/utils" "net/http" "time" ) -func ParseInterval(interval string) (*models.IntervalKey, error) { - for _, i := range models.AllIntervals { - if i.HasAlias(interval) { - return i, nil - } - } - return nil, errors.New("not a valid interval") -} - -func MustResolveIntervalRawTZ(interval string, tz *time.Location) (from, to time.Time) { - _, from, to = ResolveIntervalRawTZ(interval, tz) - return from, to -} - -func ResolveIntervalRawTZ(interval string, tz *time.Location) (err error, from, to time.Time) { - parsed, err := ParseInterval(interval) - if err != nil { - return err, time.Time{}, time.Time{} - } - return ResolveIntervalTZ(parsed, tz) -} - -func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err error, from, to time.Time) { - now := time.Now().In(tz) - to = now - - switch interval { - case models.IntervalToday: - from = BeginOfToday(tz) - case models.IntervalYesterday: - from = BeginOfToday(tz).Add(-24 * time.Hour) - to = BeginOfToday(tz) - case models.IntervalThisWeek: - from = BeginOfThisWeek(tz) - case models.IntervalLastWeek: - from = BeginOfThisWeek(tz).AddDate(0, 0, -7) - to = BeginOfThisWeek(tz) - case models.IntervalThisMonth: - from = BeginOfThisMonth(tz) - case models.IntervalLastMonth: - from = BeginOfThisMonth(tz).AddDate(0, -1, 0) - to = BeginOfThisMonth(tz) - case models.IntervalThisYear: - from = BeginOfThisYear(tz) - case models.IntervalPast7Days: - from = now.AddDate(0, 0, -7) - case models.IntervalPast7DaysYesterday: - from = BeginOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7) - to = BeginOfToday(tz).AddDate(0, 0, -1) - case models.IntervalPast14Days: - from = now.AddDate(0, 0, -14) - case models.IntervalPast30Days: - from = now.AddDate(0, 0, -30) - case models.IntervalPast6Months: - from = now.AddDate(0, -6, 0) - case models.IntervalPast12Months: - from = now.AddDate(0, -12, 0) - case models.IntervalAny: - from = time.Time{} - default: - err = errors.New("invalid interval") - } - - return err, from, to -} - func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) { user := extractUser(r) params := r.URL.Query() @@ -144,3 +79,69 @@ func extractUser(r *http.Request) *models.User { } return nil } + +func ParseInterval(interval string) (*models.IntervalKey, error) { + for _, i := range models.AllIntervals { + if i.HasAlias(interval) { + return i, nil + } + } + return nil, errors.New("not a valid interval") +} + +func MustResolveIntervalRawTZ(interval string, tz *time.Location) (from, to time.Time) { + _, from, to = ResolveIntervalRawTZ(interval, tz) + return from, to +} + +func ResolveIntervalRawTZ(interval string, tz *time.Location) (err error, from, to time.Time) { + parsed, err := ParseInterval(interval) + if err != nil { + return err, time.Time{}, time.Time{} + } + return ResolveIntervalTZ(parsed, tz) +} + +func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err error, from, to time.Time) { + now := time.Now().In(tz) + to = now + + switch interval { + case models.IntervalToday: + from = utils.BeginOfToday(tz) + case models.IntervalYesterday: + from = utils.BeginOfToday(tz).Add(-24 * time.Hour) + to = utils.BeginOfToday(tz) + case models.IntervalThisWeek: + from = utils.BeginOfThisWeek(tz) + case models.IntervalLastWeek: + from = utils.BeginOfThisWeek(tz).AddDate(0, 0, -7) + to = utils.BeginOfThisWeek(tz) + case models.IntervalThisMonth: + from = utils.BeginOfThisMonth(tz) + case models.IntervalLastMonth: + from = utils.BeginOfThisMonth(tz).AddDate(0, -1, 0) + to = utils.BeginOfThisMonth(tz) + case models.IntervalThisYear: + from = utils.BeginOfThisYear(tz) + case models.IntervalPast7Days: + from = now.AddDate(0, 0, -7) + case models.IntervalPast7DaysYesterday: + from = utils.BeginOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7) + to = utils.BeginOfToday(tz).AddDate(0, 0, -1) + case models.IntervalPast14Days: + from = now.AddDate(0, 0, -14) + case models.IntervalPast30Days: + from = now.AddDate(0, 0, -30) + case models.IntervalPast6Months: + from = now.AddDate(0, -6, 0) + case models.IntervalPast12Months: + from = now.AddDate(0, -12, 0) + case models.IntervalAny: + from = time.Time{} + default: + err = errors.New("invalid interval") + } + + return err, from, to +} diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go index 82417b4..3972b28 100644 --- a/middlewares/authenticate.go +++ b/middlewares/authenticate.go @@ -2,6 +2,7 @@ package middlewares import ( "fmt" + "github.com/muety/wakapi/helpers" "net/http" "strings" @@ -121,7 +122,7 @@ func (m *AuthenticateMiddleware) tryGetUserByApiKeyQuery(r *http.Request) (*mode } func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.User, error) { - username, err := utils.ExtractCookieAuth(r, m.config) + username, err := helpers.ExtractCookieAuth(r) if err != nil { return nil, err } diff --git a/models/compat/shields/v1/badge.go b/models/compat/shields/v1/badge.go index 39f6dc1..6ef754d 100644 --- a/models/compat/shields/v1/badge.go +++ b/models/compat/shields/v1/badge.go @@ -1,8 +1,8 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" - "github.com/muety/wakapi/utils" ) // https://shields.io/endpoint @@ -23,7 +23,7 @@ func NewBadgeDataFrom(summary *models.Summary) *BadgeData { return &BadgeData{ SchemaVersion: 1, Label: defaultLabel, - Message: utils.FmtWakatimeDuration(summary.TotalTime()), + Message: helpers.FmtWakatimeDuration(summary.TotalTime()), Color: defaultColor, } } diff --git a/models/compat/wakatime/v1/all_time.go b/models/compat/wakatime/v1/all_time.go index 8d200d4..55dc68f 100644 --- a/models/compat/wakatime/v1/all_time.go +++ b/models/compat/wakatime/v1/all_time.go @@ -1,8 +1,8 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" - "github.com/muety/wakapi/utils" "time" ) @@ -33,13 +33,13 @@ func NewAllTimeFrom(summary *models.Summary) *AllTimeViewModel { return &AllTimeViewModel{ Data: &AllTimeData{ TotalSeconds: float32(total.Seconds()), - Text: utils.FmtWakatimeDuration(total), + Text: helpers.FmtWakatimeDuration(total), IsUpToDate: true, Range: &AllTimeRange{ End: summary.ToTime.T().Format(time.RFC3339), - EndDate: utils.FormatDate(summary.ToTime.T()), + EndDate: helpers.FormatDate(summary.ToTime.T()), Start: summary.FromTime.T().Format(time.RFC3339), - StartDate: utils.FormatDate(summary.FromTime.T()), + StartDate: helpers.FormatDate(summary.FromTime.T()), Timezone: tzName, }, }, diff --git a/models/compat/wakatime/v1/summaries.go b/models/compat/wakatime/v1/summaries.go index 7716e3a..cbbffa4 100644 --- a/models/compat/wakatime/v1/summaries.go +++ b/models/compat/wakatime/v1/summaries.go @@ -2,8 +2,8 @@ package v1 import ( "fmt" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" - "github.com/muety/wakapi/utils" "math" "sync" "time" @@ -96,7 +96,7 @@ func NewSummariesFrom(summaries []*models.Summary) *SummariesViewModel { Decimal: fmt.Sprintf("%.2f", totalHrs), Digital: fmt.Sprintf("%d:%d", int(totalHrs), int(totalMins)), Seconds: totalSecs, - Text: utils.FmtWakatimeDuration(totalTime), + Text: helpers.FmtWakatimeDuration(totalTime), }, } } @@ -119,7 +119,7 @@ func newDataFrom(s *models.Summary) *SummariesData { Digital: fmt.Sprintf("%d:%d", totalHrs, totalMins), Hours: totalHrs, Minutes: totalMins, - Text: utils.FmtWakatimeDuration(total), + Text: helpers.FmtWakatimeDuration(total), TotalSeconds: total.Seconds(), }, Range: &SummariesRange{ @@ -201,7 +201,7 @@ func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *SummariesEn Name: e.Key, Percent: percentage, Seconds: secs, - Text: utils.FmtWakatimeDuration(total), + Text: helpers.FmtWakatimeDuration(total), TotalSeconds: total.Seconds(), } } diff --git a/routes/api/diagnostics.go b/routes/api/diagnostics.go index 219204d..5b3b8de 100644 --- a/routes/api/diagnostics.go +++ b/routes/api/diagnostics.go @@ -2,14 +2,13 @@ package api import ( "encoding/json" + "github.com/muety/wakapi/helpers" "net/http" "github.com/gorilla/mux" conf "github.com/muety/wakapi/config" - "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" - "github.com/muety/wakapi/models" + "github.com/muety/wakapi/services" ) type DiagnosticsApiHandler struct { @@ -55,5 +54,5 @@ func (h *DiagnosticsApiHandler) Post(w http.ResponseWriter, r *http.Request) { return } - utils.RespondJSON(w, r, http.StatusCreated, struct{}{}) + helpers.RespondJSON(w, r, http.StatusCreated, struct{}{}) } diff --git a/routes/api/heartbeat.go b/routes/api/heartbeat.go index 58574d8..c923d2c 100644 --- a/routes/api/heartbeat.go +++ b/routes/api/heartbeat.go @@ -1,6 +1,7 @@ package api import ( + "github.com/muety/wakapi/helpers" "net/http" "github.com/gorilla/mux" @@ -120,7 +121,7 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) { defer func() {}() - utils.RespondJSON(w, r, http.StatusCreated, constructSuccessResponse(len(heartbeats))) + helpers.RespondJSON(w, r, http.StatusCreated, constructSuccessResponse(len(heartbeats))) } // construct weird response format (see https://github.com/wakatime/wakatime/blob/2e636d389bf5da4e998e05d5285a96ce2c181e3d/wakatime/api.py#L288) diff --git a/routes/api/metrics.go b/routes/api/metrics.go index fcab86e..404c1bd 100644 --- a/routes/api/metrics.go +++ b/routes/api/metrics.go @@ -5,13 +5,13 @@ import ( "github.com/emvi/logbuch" "github.com/gorilla/mux" conf "github.com/muety/wakapi/config" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/wakatime/v1" mm "github.com/muety/wakapi/models/metrics" "github.com/muety/wakapi/repositories" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" "net/http" "runtime" "sort" @@ -129,7 +129,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) return nil, err } - from, to := utils.MustResolveIntervalRawTZ("today", user.TZ()) + from, to := helpers.MustResolveIntervalRawTZ("today", user.TZ()) summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, nil, false) if err != nil { diff --git a/routes/api/summary.go b/routes/api/summary.go index ac013e8..a5926e8 100644 --- a/routes/api/summary.go +++ b/routes/api/summary.go @@ -1,6 +1,7 @@ package api import ( + "github.com/muety/wakapi/helpers" routeutils "github.com/muety/wakapi/routes/utils" "net/http" @@ -8,7 +9,6 @@ import ( conf "github.com/muety/wakapi/config" "github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type SummaryApiHandler struct { @@ -58,5 +58,5 @@ func (h *SummaryApiHandler) Get(w http.ResponseWriter, r *http.Request) { return } - utils.RespondJSON(w, r, http.StatusOK, summary) + helpers.RespondJSON(w, r, http.StatusOK, summary) } diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go index 854d9d2..600cb4b 100644 --- a/routes/compat/shields/v1/badge.go +++ b/routes/compat/shields/v1/badge.go @@ -2,6 +2,7 @@ package v1 import ( "fmt" + "github.com/muety/wakapi/helpers" routeutils "github.com/muety/wakapi/routes/utils" "net/http" "time" @@ -11,7 +12,6 @@ import ( "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/shields/v1" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" "github.com/patrickmn/go-cache" ) @@ -63,7 +63,7 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) { cacheKey := fmt.Sprintf("%s_%v_%s", user.ID, *interval.Key, filters.Hash()) if cacheResult, ok := h.cache.Get(cacheKey); ok { - utils.RespondJSON(w, r, http.StatusOK, cacheResult.(*v1.BadgeData)) + helpers.RespondJSON(w, r, http.StatusOK, cacheResult.(*v1.BadgeData)) return } @@ -83,11 +83,11 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) { vm := v1.NewBadgeDataFrom(summary) h.cache.SetDefault(cacheKey, vm) - utils.RespondJSON(w, r, http.StatusOK, vm) + helpers.RespondJSON(w, r, http.StatusOK, vm) } func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey, filters *models.Filters) (*models.Summary, error, int) { - err, from, to := utils.ResolveIntervalTZ(interval, user.TZ()) + err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ()) if err != nil { return nil, err, http.StatusBadRequest } diff --git a/routes/compat/wakatime/v1/all_time.go b/routes/compat/wakatime/v1/all_time.go index 8da804e..431eaec 100644 --- a/routes/compat/wakatime/v1/all_time.go +++ b/routes/compat/wakatime/v1/all_time.go @@ -3,12 +3,12 @@ package v1 import ( "github.com/gorilla/mux" conf "github.com/muety/wakapi/config" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/wakatime/v1" routeutils "github.com/muety/wakapi/routes/utils" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" "net/http" "time" ) @@ -50,7 +50,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) { return // response was already sent by util function } - summary, err, status := h.loadUserSummary(user, utils.ParseSummaryFilters(r)) + summary, err, status := h.loadUserSummary(user, helpers.ParseSummaryFilters(r)) if err != nil { w.WriteHeader(status) w.Write([]byte(err.Error())) @@ -58,7 +58,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) { } vm := v1.NewAllTimeFrom(summary) - utils.RespondJSON(w, r, http.StatusOK, vm) + helpers.RespondJSON(w, r, http.StatusOK, vm) } func (h *AllTimeHandler) loadUserSummary(user *models.User, filters *models.Filters) (*models.Summary, error, int) { diff --git a/routes/compat/wakatime/v1/heartbeat.go b/routes/compat/wakatime/v1/heartbeat.go index c21f797..1a30bad 100644 --- a/routes/compat/wakatime/v1/heartbeat.go +++ b/routes/compat/wakatime/v1/heartbeat.go @@ -2,6 +2,7 @@ package v1 import ( "github.com/duke-git/lancet/v2/datetime" + "github.com/muety/wakapi/helpers" "net/http" "time" @@ -11,7 +12,6 @@ import ( wakatime "github.com/muety/wakapi/models/compat/wakatime/v1" routeutils "github.com/muety/wakapi/routes/utils" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type HeartbeatsResult struct { @@ -82,5 +82,5 @@ func (h *HeartbeatHandler) Get(w http.ResponseWriter, r *http.Request) { End: rangeTo.UTC().Format(time.RFC3339), Timezone: timezone.String(), } - utils.RespondJSON(w, r, http.StatusOK, res) + helpers.RespondJSON(w, r, http.StatusOK, res) } diff --git a/routes/compat/wakatime/v1/projects.go b/routes/compat/wakatime/v1/projects.go index 5e2ebb6..acdc12e 100644 --- a/routes/compat/wakatime/v1/projects.go +++ b/routes/compat/wakatime/v1/projects.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "net/http" "strings" @@ -11,7 +12,6 @@ import ( v1 "github.com/muety/wakapi/models/compat/wakatime/v1" routeutils "github.com/muety/wakapi/routes/utils" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type ProjectsHandler struct { @@ -70,5 +70,5 @@ func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) { } vm := &v1.ProjectsViewModel{Data: projects} - utils.RespondJSON(w, r, http.StatusOK, vm) + helpers.RespondJSON(w, r, http.StatusOK, vm) } diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go index b625eaa..fa03c1e 100644 --- a/routes/compat/wakatime/v1/stats.go +++ b/routes/compat/wakatime/v1/stats.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "net/http" "time" @@ -10,7 +11,6 @@ import ( "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/wakatime/v1" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type StatsHandler struct { @@ -79,7 +79,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) { rangeParam = (*models.IntervalPast7Days)[0] } - err, rangeFrom, rangeTo := utils.ResolveIntervalRawTZ(rangeParam, requestedUser.TZ()) + err, rangeFrom, rangeTo := helpers.ResolveIntervalRawTZ(rangeParam, requestedUser.TZ()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("invalid range")) @@ -94,7 +94,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) { return } - summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo, utils.ParseSummaryFilters(r)) + summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo, helpers.ParseSummaryFilters(r)) if err != nil { w.WriteHeader(status) w.Write([]byte(err.Error())) @@ -120,7 +120,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) { stats.Data.Machines = nil } - utils.RespondJSON(w, r, http.StatusOK, stats) + helpers.RespondJSON(w, r, http.StatusOK, stats) } func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time, filters *models.Filters) (*models.Summary, error, int) { diff --git a/routes/compat/wakatime/v1/statusbar.go b/routes/compat/wakatime/v1/statusbar.go index 9fade55..a71a791 100644 --- a/routes/compat/wakatime/v1/statusbar.go +++ b/routes/compat/wakatime/v1/statusbar.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "net/http" "time" @@ -11,7 +12,6 @@ import ( v1 "github.com/muety/wakapi/models/compat/wakatime/v1" routeutils "github.com/muety/wakapi/routes/utils" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type StatusBarViewModel struct { @@ -65,7 +65,7 @@ func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) { rangeParam = (*models.IntervalToday)[0] } - err, rangeFrom, rangeTo := utils.ResolveIntervalRawTZ(rangeParam, user.TZ()) + err, rangeFrom, rangeTo := helpers.ResolveIntervalRawTZ(rangeParam, user.TZ()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("invalid range")) @@ -79,7 +79,7 @@ func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) { return } summariesView := v1.NewSummariesFrom([]*models.Summary{summary}) - utils.RespondJSON(w, r, http.StatusOK, StatusBarViewModel{ + helpers.RespondJSON(w, r, http.StatusOK, StatusBarViewModel{ CachedAt: time.Now(), Data: *summariesView.Data[0], }) diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go index 62be34c..5a45b7b 100644 --- a/routes/compat/wakatime/v1/summaries.go +++ b/routes/compat/wakatime/v1/summaries.go @@ -3,6 +3,7 @@ package v1 import ( "errors" "github.com/duke-git/lancet/v2/datetime" + "github.com/muety/wakapi/helpers" "net/http" "strings" "time" @@ -76,7 +77,7 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) { } vm := v1.NewSummariesFrom(summaries) - utils.RespondJSON(w, r, http.StatusOK, vm) + helpers.RespondJSON(w, r, http.StatusOK, vm) } func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) { @@ -94,24 +95,24 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary var start, end time.Time if rangeParam != "" { // range param takes precedence - if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(rangeParam, timezone); err == nil { + if err, parsedFrom, parsedTo := helpers.ResolveIntervalRawTZ(rangeParam, timezone); err == nil { start, end = parsedFrom, parsedTo } else { return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest } - } else if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(startParam, timezone); err == nil && startParam == endParam { + } else if err, parsedFrom, parsedTo := helpers.ResolveIntervalRawTZ(startParam, timezone); err == nil && startParam == endParam { // also accept start param to be a range param start, end = parsedFrom, parsedTo } else { // eventually, consider start and end params a date var err error - start, err = utils.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), timezone) + start, err = helpers.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), timezone) if err != nil { return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest } - end, err = utils.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), timezone) + end, err = helpers.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), timezone) if err != nil { return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest } @@ -133,7 +134,7 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary summaries := make([]*models.Summary, len(intervals)) // filtering - filters := utils.ParseSummaryFilters(r) + filters := helpers.ParseSummaryFilters(r) for i, interval := range intervals { summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, filters, end.After(time.Now())) diff --git a/routes/compat/wakatime/v1/users.go b/routes/compat/wakatime/v1/users.go index 72601bc..b1ae7ab 100644 --- a/routes/compat/wakatime/v1/users.go +++ b/routes/compat/wakatime/v1/users.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/muety/wakapi/helpers" "net/http" "github.com/gorilla/mux" @@ -9,7 +10,6 @@ import ( v1 "github.com/muety/wakapi/models/compat/wakatime/v1" routeutils "github.com/muety/wakapi/routes/utils" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" ) type UsersHandler struct { @@ -56,5 +56,5 @@ func (h *UsersHandler) Get(w http.ResponseWriter, r *http.Request) { conf.Log().Request(r).Error("%v", err) } - utils.RespondJSON(w, r, http.StatusOK, v1.UserViewModel{Data: user}) + helpers.RespondJSON(w, r, http.StatusOK, v1.UserViewModel{Data: user}) } diff --git a/routes/routes.go b/routes/routes.go index 1a8d508..7b281b0 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -2,6 +2,7 @@ package routes import ( "fmt" + "github.com/muety/wakapi/helpers" "html/template" "net/http" "strings" @@ -24,16 +25,16 @@ func Init() { func DefaultTemplateFuncs() template.FuncMap { return template.FuncMap{ "json": utils.Json, - "date": utils.FormatDateHuman, - "datetime": utils.FormatDateTimeHuman, - "simpledate": utils.FormatDate, - "simpledatetime": utils.FormatDateTime, - "duration": utils.FmtWakatimeDuration, + "date": helpers.FormatDateHuman, + "datetime": helpers.FormatDateTimeHuman, + "simpledate": helpers.FormatDate, + "simpledatetime": helpers.FormatDateTime, + "duration": helpers.FmtWakatimeDuration, "floordate": datetime.BeginOfDay, "ceildate": utils.CeilDate, "title": strings.Title, "join": strings.Join, - "add": utils.Add, + "add": add, "capitalize": utils.Capitalize, "lower": strings.ToLower, "toRunes": utils.ToRunes, @@ -106,3 +107,7 @@ func loadTemplates() { func defaultErrorRedirectTarget() string { return fmt.Sprintf("%s/?error=unauthorized", config.Get().Server.BasePath) } + +func add(i, j int) int { + return i + j +} diff --git a/routes/summary.go b/routes/summary.go index 268da1d..709c4c7 100644 --- a/routes/summary.go +++ b/routes/summary.go @@ -3,6 +3,7 @@ package routes import ( "github.com/gorilla/mux" conf "github.com/muety/wakapi/config" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/models/view" su "github.com/muety/wakapi/routes/utils" @@ -47,7 +48,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) { r.URL.RawQuery = q.Encode() } - summaryParams, _ := utils.ParseSummaryParams(r) + summaryParams, _ := helpers.ParseSummaryParams(r) summary, err, status := su.LoadUserSummary(h.summarySrvc, r) if err != nil { w.WriteHeader(status) diff --git a/routes/utils/badge_utils.go b/routes/utils/badge_utils.go index 327296b..5b012b6 100644 --- a/routes/utils/badge_utils.go +++ b/routes/utils/badge_utils.go @@ -2,8 +2,8 @@ package utils import ( "errors" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" - "github.com/muety/wakapi/utils" "net/http" "regexp" ) @@ -31,12 +31,12 @@ func GetBadgeParams(r *http.Request, requestedUser *models.User) (*models.KeyedI var intervalKey = models.IntervalPast30Days if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 { - if i, err := utils.ParseInterval(groups[1]); err == nil { + if i, err := helpers.ParseInterval(groups[1]); err == nil { intervalKey = i } } - _, rangeFrom, rangeTo := utils.ResolveIntervalTZ(intervalKey, requestedUser.TZ()) + _, rangeFrom, rangeTo := helpers.ResolveIntervalTZ(intervalKey, requestedUser.TZ()) interval := &models.KeyedInterval{ Interval: models.Interval{Start: rangeFrom, End: rangeTo}, Key: intervalKey, diff --git a/routes/utils/summary_utils.go b/routes/utils/summary_utils.go index d07304c..dfee8bf 100644 --- a/routes/utils/summary_utils.go +++ b/routes/utils/summary_utils.go @@ -1,14 +1,14 @@ package utils import ( + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" "github.com/muety/wakapi/services" - "github.com/muety/wakapi/utils" "net/http" ) func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summary, error, int) { - summaryParams, err := utils.ParseSummaryParams(r) + summaryParams, err := helpers.ParseSummaryParams(r) if err != nil { return nil, err, http.StatusBadRequest } diff --git a/services/leaderboard.go b/services/leaderboard.go index 8bea809..a2e1015 100644 --- a/services/leaderboard.go +++ b/services/leaderboard.go @@ -5,9 +5,9 @@ import ( "github.com/leandro-lugaresi/hub" "github.com/muety/artifex" "github.com/muety/wakapi/config" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" "github.com/muety/wakapi/repositories" - "github.com/muety/wakapi/utils" "github.com/patrickmn/go-cache" "reflect" "strconv" @@ -212,7 +212,7 @@ func (srv *LeaderboardService) GetAggregatedByIntervalAndUser(interval *models.I } func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *models.IntervalKey) (*models.LeaderboardItem, error) { - err, from, to := utils.ResolveIntervalTZ(interval, user.TZ()) + err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ()) if err != nil { return nil, err } @@ -231,7 +231,7 @@ func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *model } func (srv *LeaderboardService) GenerateAggregatedByUser(user *models.User, interval *models.IntervalKey, by uint8) ([]*models.LeaderboardItem, error) { - err, from, to := utils.ResolveIntervalTZ(interval, user.TZ()) + err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ()) if err != nil { return nil, err } diff --git a/services/mail/mail.go b/services/mail/mail.go index 6f1c638..33c917c 100644 --- a/services/mail/mail.go +++ b/services/mail/mail.go @@ -3,6 +3,7 @@ package mail import ( "bytes" "fmt" + "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" "github.com/muety/wakapi/routes" "github.com/muety/wakapi/services" @@ -115,7 +116,7 @@ func (m *MailService) SendReport(recipient *models.User, report *models.Report) mail := &models.Mail{ From: models.MailAddress(m.config.Mail.Sender), To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}), - Subject: fmt.Sprintf(subjectReport, utils.FormatDateHuman(time.Now().In(recipient.TZ()))), + Subject: fmt.Sprintf(subjectReport, helpers.FormatDateHuman(time.Now().In(recipient.TZ()))), } mail.WithHTML(tpl.String()) return m.sendingService.Send(mail) diff --git a/utils/auth.go b/utils/auth.go index ab96242..28ae791 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -3,7 +3,7 @@ package utils import ( "encoding/base64" "errors" - "github.com/muety/wakapi/config" + "github.com/gorilla/securecookie" "github.com/muety/wakapi/models" "golang.org/x/crypto/bcrypt" "net/http" @@ -44,13 +44,13 @@ func ExtractBearerAuth(r *http.Request) (key string, err error) { return string(keyBytes), err } -func ExtractCookieAuth(r *http.Request, config *config.Config) (username *string, err error) { +func ExtractCookieAuth(r *http.Request, secureCookie *securecookie.SecureCookie) (username *string, err error) { cookie, err := r.Cookie(models.AuthCookieKey) if err != nil { return nil, errors.New("missing authentication") } - if err := config.Security.SecureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil { + if err := secureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil { return nil, errors.New("cookie is invalid") } diff --git a/utils/collection.go b/utils/collection.go new file mode 100644 index 0000000..b079e4a --- /dev/null +++ b/utils/collection.go @@ -0,0 +1,21 @@ +package utils + +import "strings" + +func SubSlice[T any](slice []T, from, to uint) []T { + if int(to) > len(slice) { + to = uint(len(slice)) + } + return slice[from:int(to)] +} + +func CloneStringMap(m map[string]string, keysToLower bool) map[string]string { + m2 := make(map[string]string) + for k, v := range m { + if keysToLower { + k = strings.ToLower(k) + } + m2[k] = v + } + return m2 +} diff --git a/utils/common_test.go b/utils/collection_test.go similarity index 100% rename from utils/common_test.go rename to utils/collection_test.go diff --git a/utils/date.go b/utils/date.go index 4a0a64e..2fc1717 100644 --- a/utils/date.go +++ b/utils/date.go @@ -1,8 +1,8 @@ package utils import ( - "fmt" "github.com/duke-git/lancet/v2/datetime" + "strings" "time" ) @@ -47,16 +47,28 @@ func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time { return intervals } -func FmtWakatimeDuration(d time.Duration) string { - d = d.Round(time.Minute) - h := d / time.Hour - d -= h * time.Hour - m := d / time.Minute - return fmt.Sprintf("%d hrs %d mins", h, m) -} - // LocalTZOffset returns the time difference between server local time and UTC func LocalTZOffset() time.Duration { _, offset := time.Now().Zone() return time.Duration(offset * int(time.Second)) } + +func ParseWeekday(s string) time.Weekday { + switch strings.ToLower(s) { + case "mon", strings.ToLower(time.Monday.String()): + return time.Monday + case "tue", strings.ToLower(time.Tuesday.String()): + return time.Tuesday + case "wed", strings.ToLower(time.Wednesday.String()): + return time.Wednesday + case "thu", strings.ToLower(time.Thursday.String()): + return time.Thursday + case "fri", strings.ToLower(time.Friday.String()): + return time.Friday + case "sat", strings.ToLower(time.Saturday.String()): + return time.Saturday + case "sun", strings.ToLower(time.Sunday.String()): + return time.Sunday + } + return time.Monday +} diff --git a/utils/date_test.go b/utils/date_test.go index 02d85db..a5d676c 100644 --- a/utils/date_test.go +++ b/utils/date_test.go @@ -2,7 +2,6 @@ package utils import ( "github.com/duke-git/lancet/v2/datetime" - "github.com/muety/wakapi/config" "github.com/stretchr/testify/assert" "testing" "time" @@ -23,8 +22,8 @@ func init() { } func TestDate_SplitRangeByDays(t *testing.T) { - df1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-25 20:25:00") - dt1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-28 06:45:00") + df1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-25 20:25:00") + dt1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-28 06:45:00") df2 := df1 dt2 := datetime.EndOfDay(df1) df3 := df1 diff --git a/utils/http.go b/utils/http.go index faebb7e..5ee9b29 100644 --- a/utils/http.go +++ b/utils/http.go @@ -1,8 +1,7 @@ package utils import ( - "encoding/json" - "github.com/muety/wakapi/config" + "errors" "github.com/muety/wakapi/models" "net/http" "regexp" @@ -23,14 +22,6 @@ func init() { cacheMaxAgeRe = regexp.MustCompile(cacheMaxAgePattern) } -func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if err := json.NewEncoder(w).Encode(object); err != nil { - config.Log().Request(r).Error("error while writing json response: %v", err) - } -} - func IsNoCache(r *http.Request, cacheTtl time.Duration) bool { cacheControl := r.Header.Get("cache-control") if strings.Contains(cacheControl, "no-cache") { @@ -67,3 +58,12 @@ func ParsePageParamsWithDefault(r *http.Request, page, size int) *models.PagePar } return pageParams } + +func ParseUserAgent(ua string) (string, string, error) { + re := regexp.MustCompile(`(?iU)^wakatime\/(?:v?[\d+.]+|unset)\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`) + groups := re.FindAllStringSubmatch(ua, -1) + if len(groups) == 0 || len(groups[0]) != 3 { + return "", "", errors.New("failed to parse user agent string") + } + return groups[0][1], groups[0][2], nil +} diff --git a/utils/strings.go b/utils/strings.go index 3175d79..ef78ffc 100644 --- a/utils/strings.go +++ b/utils/strings.go @@ -8,3 +8,23 @@ import ( func Capitalize(s string) string { return fmt.Sprintf("%s%s", strings.ToUpper(s[:1]), s[1:]) } + +func SplitMulti(s string, delimiters ...string) []string { + return strings.FieldsFunc(s, func(r rune) bool { + for _, d := range delimiters { + if string(r) == d { + return true + } + } + return false + }) +} + +func FindString(needle string, haystack []string, defaultVal string) string { + for _, s := range haystack { + if s == needle { + return s + } + } + return defaultVal +} From 964405f3492fcaa132b13fc3ee757b9ce8fed5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 10:57:51 +0100 Subject: [PATCH 09/13] chore: refine report scheduling --- services/report.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/services/report.go b/services/report.go index ce4ca40..fbc21d3 100644 --- a/services/report.go +++ b/services/report.go @@ -12,7 +12,7 @@ import ( ) // delay between evey report generation task (to throttle email sending frequency) -const reportDelay = 5 * time.Second +const reportDelay = 10 * time.Second // past time range to cover in the report const reportRange = 7 * 24 * time.Hour @@ -46,12 +46,20 @@ func NewReportService(summaryService ISummaryService, userService IUserService, func (srv *ReportService) Schedule() { logbuch.Info("scheduling report generation") - scheduleUserReport := func(u *models.User, index int) { - if err := srv.queueWorkers.DispatchIn(func() { + scheduleUserReport := func(u *models.User) { + if err := srv.queueWorkers.Dispatch(func() { + t0 := time.Now() + if err := srv.SendReport(u, reportRange); err != nil { config.Log().Error("failed to generate report for '%s', %v", u.ID, err) } - }, time.Duration(index)*reportDelay); err != nil { + + // make the job take at least reportDelay seconds + if diff := reportDelay - time.Now().Sub(t0); diff > 0 { + logbuch.Debug("waiting for %v before sending next report", diff) + time.Sleep(diff) + } + }); err != nil { config.Log().Error("failed to dispatch report generation job for user '%s', %v", u.ID, err) } } @@ -71,8 +79,8 @@ func (srv *ReportService) Schedule() { // schedule jobs, throttled by one job per x seconds logbuch.Info("scheduling report generation for %d users", len(users)) - for i, u := range users { - scheduleUserReport(u, i) + for _, u := range users { + scheduleUserReport(u) } }, srv.config.App.GetWeeklyReportCron()) From d4945c982f7aa77fbad9a289ad74ffca8fdaae0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 11:11:45 +0100 Subject: [PATCH 10/13] fix: tests --- coverage/coverage.out | 1826 +++++++++++++++++------------------ helpers/http.go | 16 +- middlewares/authenticate.go | 2 +- utils/auth.go | 15 - 4 files changed, 919 insertions(+), 940 deletions(-) diff --git a/coverage/coverage.out b/coverage/coverage.out index e8313cd..640cf7d 100644 --- a/coverage/coverage.out +++ b/coverage/coverage.out @@ -1,64 +1,78 @@ mode: set -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:25.112,32.2 1 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:34.59,38.2 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:50.68,52.16 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:57.2,58.16 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:64.2,65.50 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:70.2,78.16 3 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:84.2,86.44 3 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:52.16,55.3 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:58.16,62.3 3 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:65.50,68.3 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:78.16,82.3 3 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:89.144,91.16 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:95.2,102.29 3 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:106.2,114.16 2 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:118.2,118.36 1 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:91.16,93.3 1 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:102.29,104.3 1 0 -github.com/muety/wakapi/routes/compat/shields/v1/badge.go:114.16,116.3 1 0 -github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0 -github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0 -github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0 -github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0 -github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0 -github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0 -github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0 -github.com/muety/wakapi/utils/auth.go:37.65,39.85 2 0 -github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0 -github.com/muety/wakapi/utils/auth.go:39.85,41.3 1 0 -github.com/muety/wakapi/utils/auth.go:47.94,49.16 2 0 -github.com/muety/wakapi/utils/auth.go:53.2,53.107 1 0 -github.com/muety/wakapi/utils/auth.go:57.2,57.22 1 0 -github.com/muety/wakapi/utils/auth.go:49.16,51.3 1 0 -github.com/muety/wakapi/utils/auth.go:53.107,55.3 1 0 -github.com/muety/wakapi/utils/auth.go:60.56,64.2 3 0 -github.com/muety/wakapi/utils/auth.go:66.55,69.16 3 0 -github.com/muety/wakapi/utils/auth.go:72.2,72.16 1 0 -github.com/muety/wakapi/utils/auth.go:69.16,71.3 1 0 +github.com/muety/wakapi/utils/collection.go:5.52,6.26 1 0 +github.com/muety/wakapi/utils/collection.go:9.2,9.28 1 0 +github.com/muety/wakapi/utils/collection.go:6.26,8.3 1 0 +github.com/muety/wakapi/utils/collection.go:12.78,14.22 2 0 +github.com/muety/wakapi/utils/collection.go:20.2,20.11 1 0 +github.com/muety/wakapi/utils/collection.go:14.22,15.18 1 0 +github.com/muety/wakapi/utils/collection.go:18.3,18.12 1 0 +github.com/muety/wakapi/utils/collection.go:15.18,17.4 1 0 github.com/muety/wakapi/utils/color.go:8.90,10.32 2 0 github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0 github.com/muety/wakapi/utils/color.go:10.32,11.50 1 0 github.com/muety/wakapi/utils/color.go:11.50,13.4 1 0 -github.com/muety/wakapi/utils/http.go:22.13,24.2 1 1 -github.com/muety/wakapi/utils/http.go:26.90,29.58 3 0 -github.com/muety/wakapi/utils/http.go:29.58,31.3 1 0 -github.com/muety/wakapi/utils/http.go:34.62,36.48 2 0 -github.com/muety/wakapi/utils/http.go:39.2,39.93 1 0 -github.com/muety/wakapi/utils/http.go:44.2,44.14 1 0 -github.com/muety/wakapi/utils/http.go:36.48,38.3 1 0 -github.com/muety/wakapi/utils/http.go:39.93,40.89 1 0 -github.com/muety/wakapi/utils/http.go:40.89,42.4 1 0 -github.com/muety/wakapi/utils/http.go:47.58,51.46 4 0 -github.com/muety/wakapi/utils/http.go:54.2,54.73 1 0 -github.com/muety/wakapi/utils/http.go:57.2,57.19 1 0 -github.com/muety/wakapi/utils/http.go:51.46,53.3 1 0 -github.com/muety/wakapi/utils/http.go:54.73,56.3 1 0 -github.com/muety/wakapi/utils/http.go:60.85,62.26 2 0 -github.com/muety/wakapi/utils/http.go:65.2,65.30 1 0 -github.com/muety/wakapi/utils/http.go:68.2,68.19 1 0 -github.com/muety/wakapi/utils/http.go:62.26,64.3 1 0 -github.com/muety/wakapi/utils/http.go:65.30,67.3 1 0 +github.com/muety/wakapi/utils/cron.go:5.44,7.21 2 0 +github.com/muety/wakapi/utils/cron.go:10.2,10.20 1 0 +github.com/muety/wakapi/utils/cron.go:7.21,9.3 1 0 +github.com/muety/wakapi/utils/date.go:9.48,11.2 1 0 +github.com/muety/wakapi/utils/date.go:13.51,15.2 1 0 +github.com/muety/wakapi/utils/date.go:17.52,19.2 1 0 +github.com/muety/wakapi/utils/date.go:21.51,23.2 1 0 +github.com/muety/wakapi/utils/date.go:26.41,28.21 2 0 +github.com/muety/wakapi/utils/date.go:31.2,31.33 1 0 +github.com/muety/wakapi/utils/date.go:28.21,30.3 1 0 +github.com/muety/wakapi/utils/date.go:35.67,38.33 2 1 +github.com/muety/wakapi/utils/date.go:47.2,47.18 1 1 +github.com/muety/wakapi/utils/date.go:38.33,40.19 2 1 +github.com/muety/wakapi/utils/date.go:43.3,44.10 2 1 +github.com/muety/wakapi/utils/date.go:40.19,42.4 1 1 +github.com/muety/wakapi/utils/date.go:51.36,54.2 2 0 +github.com/muety/wakapi/utils/date.go:56.42,57.28 1 0 +github.com/muety/wakapi/utils/date.go:73.2,73.20 1 0 +github.com/muety/wakapi/utils/date.go:58.52,59.21 1 0 +github.com/muety/wakapi/utils/date.go:60.53,61.22 1 0 +github.com/muety/wakapi/utils/date.go:62.55,63.24 1 0 +github.com/muety/wakapi/utils/date.go:64.54,65.23 1 0 +github.com/muety/wakapi/utils/date.go:66.52,67.21 1 0 +github.com/muety/wakapi/utils/date.go:68.54,69.23 1 0 +github.com/muety/wakapi/utils/date.go:70.52,71.21 1 0 +github.com/muety/wakapi/utils/db.go:10.34,11.37 1 0 +github.com/muety/wakapi/utils/db.go:19.2,20.14 2 0 +github.com/muety/wakapi/utils/db.go:11.37,13.110 2 0 +github.com/muety/wakapi/utils/db.go:17.3,17.20 1 0 +github.com/muety/wakapi/utils/db.go:13.110,16.4 2 0 +github.com/muety/wakapi/utils/db.go:23.39,24.37 1 0 +github.com/muety/wakapi/utils/db.go:32.2,33.14 2 0 +github.com/muety/wakapi/utils/db.go:24.37,26.119 2 0 +github.com/muety/wakapi/utils/db.go:30.3,30.20 1 0 +github.com/muety/wakapi/utils/db.go:26.119,29.4 2 0 +github.com/muety/wakapi/utils/db.go:36.66,37.48 1 0 +github.com/muety/wakapi/utils/db.go:40.2,40.53 1 0 +github.com/muety/wakapi/utils/db.go:37.48,39.3 1 0 +github.com/muety/wakapi/utils/db.go:43.59,44.16 1 0 +github.com/muety/wakapi/utils/db.go:47.2,47.15 1 0 +github.com/muety/wakapi/utils/db.go:50.2,50.14 1 0 +github.com/muety/wakapi/utils/db.go:44.16,46.3 1 0 +github.com/muety/wakapi/utils/db.go:47.15,49.3 1 0 +github.com/muety/wakapi/utils/sync.go:11.66,13.12 2 0 +github.com/muety/wakapi/utils/sync.go:17.2,17.9 1 0 +github.com/muety/wakapi/utils/sync.go:13.12,16.3 2 0 +github.com/muety/wakapi/utils/sync.go:18.11,19.15 1 0 +github.com/muety/wakapi/utils/sync.go:20.29,21.14 1 0 +github.com/muety/wakapi/utils/auth.go:14.79,16.54 2 0 +github.com/muety/wakapi/utils/auth.go:20.2,22.16 3 0 +github.com/muety/wakapi/utils/auth.go:26.2,28.45 3 0 +github.com/muety/wakapi/utils/auth.go:31.2,32.32 2 0 +github.com/muety/wakapi/utils/auth.go:16.54,18.3 1 0 +github.com/muety/wakapi/utils/auth.go:22.16,24.3 1 0 +github.com/muety/wakapi/utils/auth.go:28.45,30.3 1 0 +github.com/muety/wakapi/utils/auth.go:35.65,37.85 2 0 +github.com/muety/wakapi/utils/auth.go:41.2,42.30 2 0 +github.com/muety/wakapi/utils/auth.go:37.85,39.3 1 0 +github.com/muety/wakapi/utils/auth.go:45.56,49.2 3 0 +github.com/muety/wakapi/utils/auth.go:51.55,54.16 3 0 +github.com/muety/wakapi/utils/auth.go:57.2,57.16 1 0 +github.com/muety/wakapi/utils/auth.go:54.16,56.3 1 0 github.com/muety/wakapi/utils/json.go:12.75,18.62 2 0 github.com/muety/wakapi/utils/json.go:22.2,22.29 1 0 github.com/muety/wakapi/utils/json.go:26.2,26.72 1 0 @@ -68,58 +82,16 @@ github.com/muety/wakapi/utils/json.go:18.62,20.3 1 0 github.com/muety/wakapi/utils/json.go:22.29,24.3 1 0 github.com/muety/wakapi/utils/json.go:26.72,28.3 1 0 github.com/muety/wakapi/utils/json.go:29.70,31.3 1 0 -github.com/muety/wakapi/utils/summary.go:10.66,11.40 1 0 -github.com/muety/wakapi/utils/summary.go:16.2,16.48 1 0 -github.com/muety/wakapi/utils/summary.go:11.40,12.27 1 0 -github.com/muety/wakapi/utils/summary.go:12.27,14.4 1 0 -github.com/muety/wakapi/utils/summary.go:19.88,22.2 2 0 -github.com/muety/wakapi/utils/summary.go:24.95,26.16 2 0 -github.com/muety/wakapi/utils/summary.go:29.2,29.38 1 0 -github.com/muety/wakapi/utils/summary.go:26.16,28.3 1 0 -github.com/muety/wakapi/utils/summary.go:32.105,36.18 3 0 -github.com/muety/wakapi/utils/summary.go:73.2,73.22 1 0 -github.com/muety/wakapi/utils/summary.go:37.28,38.26 1 0 -github.com/muety/wakapi/utils/summary.go:39.32,41.24 2 0 -github.com/muety/wakapi/utils/summary.go:42.31,43.29 1 0 -github.com/muety/wakapi/utils/summary.go:44.31,46.27 2 0 -github.com/muety/wakapi/utils/summary.go:47.32,48.30 1 0 -github.com/muety/wakapi/utils/summary.go:49.32,51.28 2 0 -github.com/muety/wakapi/utils/summary.go:52.31,53.29 1 0 -github.com/muety/wakapi/utils/summary.go:54.32,55.31 1 0 -github.com/muety/wakapi/utils/summary.go:56.41,58.42 2 0 -github.com/muety/wakapi/utils/summary.go:59.33,60.32 1 0 -github.com/muety/wakapi/utils/summary.go:61.33,62.32 1 0 -github.com/muety/wakapi/utils/summary.go:63.34,64.31 1 0 -github.com/muety/wakapi/utils/summary.go:65.35,66.32 1 0 -github.com/muety/wakapi/utils/summary.go:67.26,68.21 1 0 -github.com/muety/wakapi/utils/summary.go:69.10,70.39 1 0 -github.com/muety/wakapi/utils/summary.go:76.73,83.56 5 0 -github.com/muety/wakapi/utils/summary.go:99.2,109.8 3 0 -github.com/muety/wakapi/utils/summary.go:83.56,85.3 1 0 -github.com/muety/wakapi/utils/summary.go:85.8,85.54 1 0 -github.com/muety/wakapi/utils/summary.go:85.54,87.3 1 0 -github.com/muety/wakapi/utils/summary.go:87.8,89.17 2 0 -github.com/muety/wakapi/utils/summary.go:93.3,94.17 2 0 -github.com/muety/wakapi/utils/summary.go:89.17,91.4 1 0 -github.com/muety/wakapi/utils/summary.go:94.17,96.4 1 0 -github.com/muety/wakapi/utils/summary.go:112.59,114.48 2 0 -github.com/muety/wakapi/utils/summary.go:117.2,117.49 1 0 -github.com/muety/wakapi/utils/summary.go:120.2,120.47 1 0 -github.com/muety/wakapi/utils/summary.go:123.2,123.48 1 0 -github.com/muety/wakapi/utils/summary.go:126.2,126.57 1 0 -github.com/muety/wakapi/utils/summary.go:129.2,129.46 1 0 -github.com/muety/wakapi/utils/summary.go:132.2,132.47 1 0 -github.com/muety/wakapi/utils/summary.go:135.2,135.16 1 0 -github.com/muety/wakapi/utils/summary.go:114.48,116.3 1 0 -github.com/muety/wakapi/utils/summary.go:117.49,119.3 1 0 -github.com/muety/wakapi/utils/summary.go:120.47,122.3 1 0 -github.com/muety/wakapi/utils/summary.go:123.48,125.3 1 0 -github.com/muety/wakapi/utils/summary.go:126.57,128.3 1 0 -github.com/muety/wakapi/utils/summary.go:129.46,131.3 1 0 -github.com/muety/wakapi/utils/summary.go:132.47,134.3 1 0 -github.com/muety/wakapi/utils/summary.go:138.48,142.51 2 0 -github.com/muety/wakapi/utils/summary.go:145.2,145.12 1 0 -github.com/muety/wakapi/utils/summary.go:142.51,144.3 1 0 +github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0 +github.com/muety/wakapi/utils/strings.go:12.58,13.49 1 0 +github.com/muety/wakapi/utils/strings.go:13.49,14.32 1 0 +github.com/muety/wakapi/utils/strings.go:19.3,19.15 1 0 +github.com/muety/wakapi/utils/strings.go:14.32,15.22 1 0 +github.com/muety/wakapi/utils/strings.go:15.22,17.5 1 0 +github.com/muety/wakapi/utils/strings.go:23.77,24.29 1 0 +github.com/muety/wakapi/utils/strings.go:29.2,29.19 1 0 +github.com/muety/wakapi/utils/strings.go:24.29,25.18 1 0 +github.com/muety/wakapi/utils/strings.go:25.18,27.4 1 0 github.com/muety/wakapi/utils/template.go:13.41,15.16 2 0 github.com/muety/wakapi/utils/template.go:18.2,18.23 1 0 github.com/muety/wakapi/utils/template.go:15.16,17.3 1 0 @@ -139,55 +111,107 @@ github.com/muety/wakapi/utils/template.go:39.51,40.12 1 0 github.com/muety/wakapi/utils/template.go:44.17,46.4 1 0 github.com/muety/wakapi/utils/template.go:48.17,50.4 1 0 github.com/muety/wakapi/utils/template.go:55.17,57.4 1 0 -github.com/muety/wakapi/utils/common.go:18.73,19.58 1 0 -github.com/muety/wakapi/utils/common.go:22.2,22.87 1 0 -github.com/muety/wakapi/utils/common.go:25.2,25.64 1 0 -github.com/muety/wakapi/utils/common.go:19.58,21.3 1 0 -github.com/muety/wakapi/utils/common.go:22.87,24.3 1 0 -github.com/muety/wakapi/utils/common.go:28.40,30.2 1 0 -github.com/muety/wakapi/utils/common.go:32.44,34.2 1 0 -github.com/muety/wakapi/utils/common.go:36.49,38.2 1 0 -github.com/muety/wakapi/utils/common.go:40.45,42.2 1 0 -github.com/muety/wakapi/utils/common.go:44.24,46.2 1 0 -github.com/muety/wakapi/utils/common.go:48.56,51.45 3 1 -github.com/muety/wakapi/utils/common.go:54.2,54.40 1 1 -github.com/muety/wakapi/utils/common.go:51.45,53.3 1 1 -github.com/muety/wakapi/utils/common.go:57.52,58.26 1 0 -github.com/muety/wakapi/utils/common.go:61.2,61.28 1 0 -github.com/muety/wakapi/utils/common.go:58.26,60.3 1 0 -github.com/muety/wakapi/utils/date.go:9.48,11.2 1 0 -github.com/muety/wakapi/utils/date.go:13.51,15.2 1 0 -github.com/muety/wakapi/utils/date.go:17.52,19.2 1 0 -github.com/muety/wakapi/utils/date.go:21.51,23.2 1 0 -github.com/muety/wakapi/utils/date.go:26.41,28.21 2 0 -github.com/muety/wakapi/utils/date.go:31.2,31.33 1 0 -github.com/muety/wakapi/utils/date.go:28.21,30.3 1 0 -github.com/muety/wakapi/utils/date.go:35.67,38.33 2 1 -github.com/muety/wakapi/utils/date.go:47.2,47.18 1 1 -github.com/muety/wakapi/utils/date.go:38.33,40.19 2 1 -github.com/muety/wakapi/utils/date.go:43.3,44.10 2 1 -github.com/muety/wakapi/utils/date.go:40.19,42.4 1 1 -github.com/muety/wakapi/utils/date.go:50.50,56.2 5 0 -github.com/muety/wakapi/utils/date.go:59.36,62.2 2 0 -github.com/muety/wakapi/utils/db.go:10.34,11.37 1 0 -github.com/muety/wakapi/utils/db.go:19.2,20.14 2 0 -github.com/muety/wakapi/utils/db.go:11.37,13.110 2 0 -github.com/muety/wakapi/utils/db.go:17.3,17.20 1 0 -github.com/muety/wakapi/utils/db.go:13.110,16.4 2 0 -github.com/muety/wakapi/utils/db.go:23.39,24.37 1 0 -github.com/muety/wakapi/utils/db.go:32.2,33.14 2 0 -github.com/muety/wakapi/utils/db.go:24.37,26.119 2 0 -github.com/muety/wakapi/utils/db.go:30.3,30.20 1 0 -github.com/muety/wakapi/utils/db.go:26.119,29.4 2 0 -github.com/muety/wakapi/utils/db.go:36.66,37.48 1 0 -github.com/muety/wakapi/utils/db.go:40.2,40.53 1 0 -github.com/muety/wakapi/utils/db.go:37.48,39.3 1 0 -github.com/muety/wakapi/utils/db.go:43.59,44.16 1 0 -github.com/muety/wakapi/utils/db.go:47.2,47.15 1 0 -github.com/muety/wakapi/utils/db.go:50.2,50.14 1 0 -github.com/muety/wakapi/utils/db.go:44.16,46.3 1 0 -github.com/muety/wakapi/utils/db.go:47.15,49.3 1 0 -github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0 +github.com/muety/wakapi/utils/http.go:21.13,23.2 1 1 +github.com/muety/wakapi/utils/http.go:25.62,27.48 2 0 +github.com/muety/wakapi/utils/http.go:30.2,30.93 1 0 +github.com/muety/wakapi/utils/http.go:35.2,35.14 1 0 +github.com/muety/wakapi/utils/http.go:27.48,29.3 1 0 +github.com/muety/wakapi/utils/http.go:30.93,31.89 1 0 +github.com/muety/wakapi/utils/http.go:31.89,33.4 1 0 +github.com/muety/wakapi/utils/http.go:38.58,42.46 4 0 +github.com/muety/wakapi/utils/http.go:45.2,45.73 1 0 +github.com/muety/wakapi/utils/http.go:48.2,48.19 1 0 +github.com/muety/wakapi/utils/http.go:42.46,44.3 1 0 +github.com/muety/wakapi/utils/http.go:45.73,47.3 1 0 +github.com/muety/wakapi/utils/http.go:51.85,53.26 2 0 +github.com/muety/wakapi/utils/http.go:56.2,56.30 1 0 +github.com/muety/wakapi/utils/http.go:59.2,59.19 1 0 +github.com/muety/wakapi/utils/http.go:53.26,55.3 1 0 +github.com/muety/wakapi/utils/http.go:56.30,58.3 1 0 +github.com/muety/wakapi/utils/http.go:62.56,65.45 3 1 +github.com/muety/wakapi/utils/http.go:68.2,68.40 1 1 +github.com/muety/wakapi/utils/http.go:65.45,67.3 1 1 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:25.112,32.2 1 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:34.59,38.2 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:50.68,52.16 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:57.2,58.16 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:64.2,65.50 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:70.2,78.16 3 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:84.2,86.46 3 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:52.16,55.3 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:58.16,62.3 3 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:65.50,68.3 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:78.16,82.3 3 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:89.144,91.16 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:95.2,102.29 3 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:106.2,114.16 2 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:118.2,118.36 1 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:91.16,93.3 1 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:102.29,104.3 1 0 +github.com/muety/wakapi/routes/compat/shields/v1/badge.go:114.16,116.3 1 0 +github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0 +github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0 +github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0 +github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0 +github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0 +github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0 +github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0 +github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0 +github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0 +github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0 +github.com/muety/wakapi/middlewares/security.go:19.62,20.43 1 0 +github.com/muety/wakapi/middlewares/security.go:20.43,22.3 1 0 +github.com/muety/wakapi/middlewares/security.go:25.80,26.36 1 0 +github.com/muety/wakapi/middlewares/security.go:31.2,31.27 1 0 +github.com/muety/wakapi/middlewares/security.go:26.36,27.30 1 0 +github.com/muety/wakapi/middlewares/security.go:27.30,29.4 1 0 +github.com/muety/wakapi/middlewares/sentry.go:15.60,16.43 1 0 +github.com/muety/wakapi/middlewares/sentry.go:16.43,20.3 1 0 +github.com/muety/wakapi/middlewares/sentry.go:23.78,26.54 3 0 +github.com/muety/wakapi/middlewares/sentry.go:26.54,27.43 1 0 +github.com/muety/wakapi/middlewares/sentry.go:27.43,29.4 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:31.91,37.2 1 1 +github.com/muety/wakapi/middlewares/authenticate.go:39.90,42.2 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:44.90,47.2 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:49.71,50.71 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:50.71,52.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:55.107,59.16 3 0 +github.com/muety/wakapi/middlewares/authenticate.go:62.2,62.16 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:66.2,66.31 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:82.2,83.12 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:59.16,61.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:62.16,64.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:66.31,67.31 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:72.3,72.29 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:79.3,79.9 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:67.31,70.4 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:72.29,75.4 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:75.9,78.4 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:86.70,87.39 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.14 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:87.39,88.60 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:88.60,90.4 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:95.98,97.16 2 1 +github.com/muety/wakapi/middlewares/authenticate.go:101.2,104.16 4 1 +github.com/muety/wakapi/middlewares/authenticate.go:107.2,107.18 1 1 +github.com/muety/wakapi/middlewares/authenticate.go:97.16,99.3 1 1 +github.com/muety/wakapi/middlewares/authenticate.go:104.16,106.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:110.97,114.19 4 1 +github.com/muety/wakapi/middlewares/authenticate.go:117.2,118.16 2 1 +github.com/muety/wakapi/middlewares/authenticate.go:121.2,121.18 1 1 +github.com/muety/wakapi/middlewares/authenticate.go:114.19,116.3 1 1 +github.com/muety/wakapi/middlewares/authenticate.go:118.16,120.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:124.92,126.16 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:130.2,131.16 2 0 +github.com/muety/wakapi/middlewares/authenticate.go:138.2,138.18 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:126.16,128.3 1 0 +github.com/muety/wakapi/middlewares/authenticate.go:131.16,133.3 1 0 +github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0 +github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0 +github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0 +github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0 +github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0 +github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0 github.com/muety/wakapi/middlewares/logging.go:20.102,21.43 1 0 github.com/muety/wakapi/middlewares/logging.go:21.43,27.3 1 0 github.com/muety/wakapi/middlewares/logging.go:30.80,39.44 7 0 @@ -215,72 +239,153 @@ github.com/muety/wakapi/middlewares/logging.go:142.36,144.2 1 0 github.com/muety/wakapi/middlewares/logging.go:145.42,147.2 1 0 github.com/muety/wakapi/middlewares/logging.go:148.40,150.2 1 0 github.com/muety/wakapi/middlewares/logging.go:151.52,153.2 1 0 -github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0 -github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0 -github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0 -github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0 -github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0 -github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0 -github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0 -github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0 -github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0 -github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0 -github.com/muety/wakapi/middlewares/security.go:19.62,20.43 1 0 -github.com/muety/wakapi/middlewares/security.go:20.43,22.3 1 0 -github.com/muety/wakapi/middlewares/security.go:25.80,26.36 1 0 -github.com/muety/wakapi/middlewares/security.go:31.2,31.27 1 0 -github.com/muety/wakapi/middlewares/security.go:26.36,27.30 1 0 -github.com/muety/wakapi/middlewares/security.go:27.30,29.4 1 0 -github.com/muety/wakapi/middlewares/sentry.go:15.60,16.43 1 0 -github.com/muety/wakapi/middlewares/sentry.go:16.43,20.3 1 0 -github.com/muety/wakapi/middlewares/sentry.go:23.78,26.54 3 0 -github.com/muety/wakapi/middlewares/sentry.go:26.54,27.43 1 0 -github.com/muety/wakapi/middlewares/sentry.go:27.43,29.4 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:30.91,36.2 1 1 -github.com/muety/wakapi/middlewares/authenticate.go:38.90,41.2 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:43.90,46.2 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:48.71,49.71 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:49.71,51.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:54.107,58.16 3 0 -github.com/muety/wakapi/middlewares/authenticate.go:61.2,61.16 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:65.2,65.31 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:81.2,82.12 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:58.16,60.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:61.16,63.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:65.31,66.31 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:71.3,71.29 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:78.3,78.9 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:66.31,69.4 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:71.29,74.4 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:74.9,77.4 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:85.70,86.39 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:91.2,91.14 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:86.39,87.60 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:87.60,89.4 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:94.98,96.16 2 1 -github.com/muety/wakapi/middlewares/authenticate.go:100.2,103.16 4 1 -github.com/muety/wakapi/middlewares/authenticate.go:106.2,106.18 1 1 -github.com/muety/wakapi/middlewares/authenticate.go:96.16,98.3 1 1 -github.com/muety/wakapi/middlewares/authenticate.go:103.16,105.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:109.97,113.19 4 1 -github.com/muety/wakapi/middlewares/authenticate.go:116.2,117.16 2 1 -github.com/muety/wakapi/middlewares/authenticate.go:120.2,120.18 1 1 -github.com/muety/wakapi/middlewares/authenticate.go:113.19,115.3 1 1 -github.com/muety/wakapi/middlewares/authenticate.go:117.16,119.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:123.92,125.16 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:129.2,130.16 2 0 -github.com/muety/wakapi/middlewares/authenticate.go:137.2,137.18 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:125.16,127.3 1 0 -github.com/muety/wakapi/middlewares/authenticate.go:130.16,132.3 1 0 -github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0 -github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0 -github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0 -github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0 -github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0 -github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0 +github.com/muety/wakapi/config/config.go:165.64,167.2 1 0 +github.com/muety/wakapi/config/config.go:169.59,171.2 1 0 +github.com/muety/wakapi/config/config.go:173.82,183.2 1 0 +github.com/muety/wakapi/config/config.go:185.31,187.2 1 0 +github.com/muety/wakapi/config/config.go:189.32,191.2 1 0 +github.com/muety/wakapi/config/config.go:193.74,194.19 1 0 +github.com/muety/wakapi/config/config.go:195.10,196.34 1 0 +github.com/muety/wakapi/config/config.go:196.34,197.90 1 0 +github.com/muety/wakapi/config/config.go:200.4,200.100 1 0 +github.com/muety/wakapi/config/config.go:203.4,203.91 1 0 +github.com/muety/wakapi/config/config.go:206.4,206.95 1 0 +github.com/muety/wakapi/config/config.go:209.4,209.93 1 0 +github.com/muety/wakapi/config/config.go:212.4,212.97 1 0 +github.com/muety/wakapi/config/config.go:215.4,215.101 1 0 +github.com/muety/wakapi/config/config.go:218.4,218.98 1 0 +github.com/muety/wakapi/config/config.go:221.4,221.97 1 0 +github.com/muety/wakapi/config/config.go:224.4,224.101 1 0 +github.com/muety/wakapi/config/config.go:227.4,227.14 1 0 +github.com/muety/wakapi/config/config.go:197.90,199.5 1 0 +github.com/muety/wakapi/config/config.go:200.100,202.5 1 0 +github.com/muety/wakapi/config/config.go:203.91,205.5 1 0 +github.com/muety/wakapi/config/config.go:206.95,208.5 1 0 +github.com/muety/wakapi/config/config.go:209.93,211.5 1 0 +github.com/muety/wakapi/config/config.go:212.97,214.5 1 0 +github.com/muety/wakapi/config/config.go:215.101,217.5 1 0 +github.com/muety/wakapi/config/config.go:218.98,220.5 1 0 +github.com/muety/wakapi/config/config.go:221.97,223.5 1 0 +github.com/muety/wakapi/config/config.go:224.101,226.5 1 0 +github.com/muety/wakapi/config/config.go:232.60,234.2 1 0 +github.com/muety/wakapi/config/config.go:236.59,238.2 1 0 +github.com/muety/wakapi/config/config.go:240.57,242.2 1 0 +github.com/muety/wakapi/config/config.go:244.53,246.2 1 0 +github.com/muety/wakapi/config/config.go:248.53,249.46 1 0 +github.com/muety/wakapi/config/config.go:265.2,265.51 1 0 +github.com/muety/wakapi/config/config.go:249.46,253.17 3 0 +github.com/muety/wakapi/config/config.go:257.3,258.17 2 0 +github.com/muety/wakapi/config/config.go:262.3,262.44 1 0 +github.com/muety/wakapi/config/config.go:253.17,255.4 1 0 +github.com/muety/wakapi/config/config.go:258.17,260.4 1 0 +github.com/muety/wakapi/config/config.go:268.50,269.47 1 0 +github.com/muety/wakapi/config/config.go:288.2,288.52 1 0 +github.com/muety/wakapi/config/config.go:269.47,276.17 5 0 +github.com/muety/wakapi/config/config.go:280.3,281.17 2 0 +github.com/muety/wakapi/config/config.go:285.3,285.54 1 0 +github.com/muety/wakapi/config/config.go:276.17,278.4 1 0 +github.com/muety/wakapi/config/config.go:281.17,283.4 1 0 +github.com/muety/wakapi/config/config.go:291.65,296.56 3 0 +github.com/muety/wakapi/config/config.go:318.2,318.76 1 0 +github.com/muety/wakapi/config/config.go:322.2,322.14 1 0 +github.com/muety/wakapi/config/config.go:296.56,298.33 1 0 +github.com/muety/wakapi/config/config.go:298.33,301.18 3 0 +github.com/muety/wakapi/config/config.go:305.4,306.18 2 0 +github.com/muety/wakapi/config/config.go:310.4,310.45 1 0 +github.com/muety/wakapi/config/config.go:301.18,303.5 1 0 +github.com/muety/wakapi/config/config.go:306.18,308.5 1 0 +github.com/muety/wakapi/config/config.go:312.8,313.33 1 0 +github.com/muety/wakapi/config/config.go:313.33,315.4 1 0 +github.com/muety/wakapi/config/config.go:318.76,320.3 1 0 +github.com/muety/wakapi/config/config.go:325.54,328.2 2 0 +github.com/muety/wakapi/config/config.go:330.36,332.2 1 0 +github.com/muety/wakapi/config/config.go:334.35,336.2 1 0 +github.com/muety/wakapi/config/config.go:338.38,340.2 1 0 +github.com/muety/wakapi/config/config.go:342.46,344.2 1 0 +github.com/muety/wakapi/config/config.go:346.43,348.2 1 0 +github.com/muety/wakapi/config/config.go:350.29,352.2 1 1 +github.com/muety/wakapi/config/config.go:354.48,365.16 2 0 +github.com/muety/wakapi/config/config.go:369.2,370.53 2 0 +github.com/muety/wakapi/config/config.go:374.2,374.15 1 0 +github.com/muety/wakapi/config/config.go:365.16,367.3 1 0 +github.com/muety/wakapi/config/config.go:370.53,372.3 1 0 +github.com/muety/wakapi/config/config.go:377.38,378.43 1 0 +github.com/muety/wakapi/config/config.go:381.2,381.15 1 0 +github.com/muety/wakapi/config/config.go:378.43,380.3 1 0 +github.com/muety/wakapi/config/config.go:384.45,385.27 1 0 +github.com/muety/wakapi/config/config.go:388.2,388.24 1 0 +github.com/muety/wakapi/config/config.go:391.2,391.25 1 0 +github.com/muety/wakapi/config/config.go:394.2,394.15 1 0 +github.com/muety/wakapi/config/config.go:385.27,387.3 1 0 +github.com/muety/wakapi/config/config.go:388.24,390.3 1 0 +github.com/muety/wakapi/config/config.go:391.25,393.3 1 0 +github.com/muety/wakapi/config/config.go:397.26,399.2 1 0 +github.com/muety/wakapi/config/config.go:401.20,403.2 1 0 +github.com/muety/wakapi/config/config.go:405.35,410.96 3 0 +github.com/muety/wakapi/config/config.go:414.2,418.21 4 0 +github.com/muety/wakapi/config/config.go:422.2,430.52 5 0 +github.com/muety/wakapi/config/config.go:434.2,434.47 1 0 +github.com/muety/wakapi/config/config.go:440.2,440.29 1 0 +github.com/muety/wakapi/config/config.go:446.2,446.106 1 0 +github.com/muety/wakapi/config/config.go:449.2,449.28 1 0 +github.com/muety/wakapi/config/config.go:452.2,452.51 1 0 +github.com/muety/wakapi/config/config.go:456.2,456.100 1 0 +github.com/muety/wakapi/config/config.go:459.2,459.74 1 0 +github.com/muety/wakapi/config/config.go:463.2,465.78 2 0 +github.com/muety/wakapi/config/config.go:468.2,468.81 1 0 +github.com/muety/wakapi/config/config.go:471.2,471.66 1 0 +github.com/muety/wakapi/config/config.go:478.2,478.55 1 0 +github.com/muety/wakapi/config/config.go:481.2,481.56 1 0 +github.com/muety/wakapi/config/config.go:484.2,484.65 1 0 +github.com/muety/wakapi/config/config.go:488.2,489.14 2 0 +github.com/muety/wakapi/config/config.go:410.96,412.3 1 0 +github.com/muety/wakapi/config/config.go:418.21,420.3 1 0 +github.com/muety/wakapi/config/config.go:430.52,432.3 1 0 +github.com/muety/wakapi/config/config.go:434.47,435.14 1 0 +github.com/muety/wakapi/config/config.go:435.14,437.4 1 0 +github.com/muety/wakapi/config/config.go:440.29,443.3 2 0 +github.com/muety/wakapi/config/config.go:446.106,448.3 1 0 +github.com/muety/wakapi/config/config.go:449.28,451.3 1 0 +github.com/muety/wakapi/config/config.go:452.51,455.3 2 0 +github.com/muety/wakapi/config/config.go:456.100,458.3 1 0 +github.com/muety/wakapi/config/config.go:459.74,461.3 1 0 +github.com/muety/wakapi/config/config.go:465.78,467.3 1 0 +github.com/muety/wakapi/config/config.go:468.81,470.3 1 0 +github.com/muety/wakapi/config/config.go:471.66,472.48 1 0 +github.com/muety/wakapi/config/config.go:472.48,474.4 1 0 +github.com/muety/wakapi/config/config.go:478.55,480.3 1 0 +github.com/muety/wakapi/config/config.go:481.56,483.3 1 0 +github.com/muety/wakapi/config/config.go:484.65,486.3 1 0 +github.com/muety/wakapi/config/db.go:39.50,40.19 1 0 +github.com/muety/wakapi/config/db.go:53.2,53.12 1 0 +github.com/muety/wakapi/config/db.go:41.23,45.5 1 0 +github.com/muety/wakapi/config/db.go:46.26,49.5 1 0 +github.com/muety/wakapi/config/db.go:50.24,51.48 1 0 +github.com/muety/wakapi/config/db.go:56.53,66.2 1 1 +github.com/muety/wakapi/config/db.go:68.56,69.25 1 1 +github.com/muety/wakapi/config/db.go:73.2,74.16 2 1 +github.com/muety/wakapi/config/db.go:78.2,85.3 1 1 +github.com/muety/wakapi/config/db.go:69.25,71.3 1 0 +github.com/muety/wakapi/config/db.go:74.16,76.3 1 0 +github.com/muety/wakapi/config/db.go:88.54,90.2 1 1 +github.com/muety/wakapi/config/eventbus.go:26.13,28.2 1 1 +github.com/muety/wakapi/config/eventbus.go:30.26,32.2 1 0 github.com/muety/wakapi/config/fs.go:9.56,10.19 1 0 github.com/muety/wakapi/config/fs.go:13.2,13.19 1 0 github.com/muety/wakapi/config/fs.go:10.19,12.3 1 0 +github.com/muety/wakapi/config/jobqueue.go:27.13,34.2 5 1 +github.com/muety/wakapi/config/jobqueue.go:36.48,37.34 1 1 +github.com/muety/wakapi/config/jobqueue.go:40.2,43.12 4 1 +github.com/muety/wakapi/config/jobqueue.go:37.34,39.3 1 0 +github.com/muety/wakapi/config/jobqueue.go:46.44,48.2 1 0 +github.com/muety/wakapi/config/jobqueue.go:50.48,51.35 1 0 +github.com/muety/wakapi/config/jobqueue.go:54.2,54.24 1 0 +github.com/muety/wakapi/config/jobqueue.go:51.35,53.3 1 0 +github.com/muety/wakapi/config/jobqueue.go:57.43,59.37 2 0 +github.com/muety/wakapi/config/jobqueue.go:66.2,66.16 1 0 +github.com/muety/wakapi/config/jobqueue.go:59.37,65.3 1 0 +github.com/muety/wakapi/config/jobqueue.go:69.20,70.30 1 0 +github.com/muety/wakapi/config/jobqueue.go:70.30,72.3 1 0 github.com/muety/wakapi/config/sentry.go:22.35,24.2 1 0 github.com/muety/wakapi/config/sentry.go:26.62,29.2 2 0 github.com/muety/wakapi/config/sentry.go:39.33,46.2 2 0 @@ -314,260 +419,6 @@ github.com/muety/wakapi/config/sentry.go:143.17,145.3 1 0 github.com/muety/wakapi/config/sentry.go:148.49,152.51 2 0 github.com/muety/wakapi/config/sentry.go:155.2,155.12 1 0 github.com/muety/wakapi/config/sentry.go:152.51,154.3 1 0 -github.com/muety/wakapi/config/utils.go:5.78,7.22 2 0 -github.com/muety/wakapi/config/utils.go:13.2,13.11 1 0 -github.com/muety/wakapi/config/utils.go:7.22,8.18 1 0 -github.com/muety/wakapi/config/utils.go:11.3,11.12 1 0 -github.com/muety/wakapi/config/utils.go:8.18,10.4 1 0 -github.com/muety/wakapi/config/config.go:162.64,164.2 1 0 -github.com/muety/wakapi/config/config.go:166.59,168.2 1 0 -github.com/muety/wakapi/config/config.go:170.82,180.2 1 0 -github.com/muety/wakapi/config/config.go:182.31,184.2 1 0 -github.com/muety/wakapi/config/config.go:186.32,188.2 1 0 -github.com/muety/wakapi/config/config.go:190.74,191.19 1 0 -github.com/muety/wakapi/config/config.go:192.10,193.34 1 0 -github.com/muety/wakapi/config/config.go:193.34,194.90 1 0 -github.com/muety/wakapi/config/config.go:197.4,197.100 1 0 -github.com/muety/wakapi/config/config.go:200.4,200.91 1 0 -github.com/muety/wakapi/config/config.go:203.4,203.95 1 0 -github.com/muety/wakapi/config/config.go:206.4,206.93 1 0 -github.com/muety/wakapi/config/config.go:209.4,209.97 1 0 -github.com/muety/wakapi/config/config.go:212.4,212.101 1 0 -github.com/muety/wakapi/config/config.go:215.4,215.98 1 0 -github.com/muety/wakapi/config/config.go:218.4,218.97 1 0 -github.com/muety/wakapi/config/config.go:221.4,221.101 1 0 -github.com/muety/wakapi/config/config.go:224.4,224.14 1 0 -github.com/muety/wakapi/config/config.go:194.90,196.5 1 0 -github.com/muety/wakapi/config/config.go:197.100,199.5 1 0 -github.com/muety/wakapi/config/config.go:200.91,202.5 1 0 -github.com/muety/wakapi/config/config.go:203.95,205.5 1 0 -github.com/muety/wakapi/config/config.go:206.93,208.5 1 0 -github.com/muety/wakapi/config/config.go:209.97,211.5 1 0 -github.com/muety/wakapi/config/config.go:212.101,214.5 1 0 -github.com/muety/wakapi/config/config.go:215.98,217.5 1 0 -github.com/muety/wakapi/config/config.go:218.97,220.5 1 0 -github.com/muety/wakapi/config/config.go:221.101,223.5 1 0 -github.com/muety/wakapi/config/config.go:229.60,231.2 1 0 -github.com/muety/wakapi/config/config.go:233.59,235.2 1 0 -github.com/muety/wakapi/config/config.go:237.57,239.2 1 0 -github.com/muety/wakapi/config/config.go:241.53,243.2 1 0 -github.com/muety/wakapi/config/config.go:245.55,248.2 2 0 -github.com/muety/wakapi/config/config.go:250.50,252.2 1 0 -github.com/muety/wakapi/config/config.go:254.54,257.2 2 0 -github.com/muety/wakapi/config/config.go:259.36,261.2 1 0 -github.com/muety/wakapi/config/config.go:263.35,265.2 1 0 -github.com/muety/wakapi/config/config.go:267.38,269.2 1 0 -github.com/muety/wakapi/config/config.go:271.46,273.2 1 0 -github.com/muety/wakapi/config/config.go:275.43,277.2 1 0 -github.com/muety/wakapi/config/config.go:279.29,281.2 1 1 -github.com/muety/wakapi/config/config.go:283.48,294.16 2 0 -github.com/muety/wakapi/config/config.go:298.2,299.53 2 0 -github.com/muety/wakapi/config/config.go:303.2,303.15 1 0 -github.com/muety/wakapi/config/config.go:294.16,296.3 1 0 -github.com/muety/wakapi/config/config.go:299.53,301.3 1 0 -github.com/muety/wakapi/config/config.go:306.38,307.43 1 0 -github.com/muety/wakapi/config/config.go:310.2,310.15 1 0 -github.com/muety/wakapi/config/config.go:307.43,309.3 1 0 -github.com/muety/wakapi/config/config.go:313.45,314.27 1 0 -github.com/muety/wakapi/config/config.go:317.2,317.24 1 0 -github.com/muety/wakapi/config/config.go:320.2,320.25 1 0 -github.com/muety/wakapi/config/config.go:323.2,323.15 1 0 -github.com/muety/wakapi/config/config.go:314.27,316.3 1 0 -github.com/muety/wakapi/config/config.go:317.24,319.3 1 0 -github.com/muety/wakapi/config/config.go:320.25,322.3 1 0 -github.com/muety/wakapi/config/config.go:326.77,327.29 1 0 -github.com/muety/wakapi/config/config.go:332.2,332.19 1 0 -github.com/muety/wakapi/config/config.go:327.29,328.18 1 0 -github.com/muety/wakapi/config/config.go:328.18,330.4 1 0 -github.com/muety/wakapi/config/config.go:335.42,336.28 1 0 -github.com/muety/wakapi/config/config.go:352.2,352.20 1 0 -github.com/muety/wakapi/config/config.go:337.52,338.21 1 0 -github.com/muety/wakapi/config/config.go:339.53,340.22 1 0 -github.com/muety/wakapi/config/config.go:341.55,342.24 1 0 -github.com/muety/wakapi/config/config.go:343.54,344.23 1 0 -github.com/muety/wakapi/config/config.go:345.52,346.21 1 0 -github.com/muety/wakapi/config/config.go:347.54,348.23 1 0 -github.com/muety/wakapi/config/config.go:349.52,350.21 1 0 -github.com/muety/wakapi/config/config.go:355.26,357.2 1 0 -github.com/muety/wakapi/config/config.go:359.20,361.2 1 0 -github.com/muety/wakapi/config/config.go:363.35,368.96 3 0 -github.com/muety/wakapi/config/config.go:372.2,376.21 4 0 -github.com/muety/wakapi/config/config.go:380.2,388.52 5 0 -github.com/muety/wakapi/config/config.go:392.2,392.47 1 0 -github.com/muety/wakapi/config/config.go:398.2,398.29 1 0 -github.com/muety/wakapi/config/config.go:404.2,404.106 1 0 -github.com/muety/wakapi/config/config.go:407.2,407.28 1 0 -github.com/muety/wakapi/config/config.go:410.2,410.51 1 0 -github.com/muety/wakapi/config/config.go:414.2,414.94 1 0 -github.com/muety/wakapi/config/config.go:417.2,417.81 1 0 -github.com/muety/wakapi/config/config.go:420.2,420.75 1 0 -github.com/muety/wakapi/config/config.go:423.2,423.74 1 0 -github.com/muety/wakapi/config/config.go:427.2,428.14 2 0 -github.com/muety/wakapi/config/config.go:368.96,370.3 1 0 -github.com/muety/wakapi/config/config.go:376.21,378.3 1 0 -github.com/muety/wakapi/config/config.go:388.52,390.3 1 0 -github.com/muety/wakapi/config/config.go:392.47,393.14 1 0 -github.com/muety/wakapi/config/config.go:393.14,395.4 1 0 -github.com/muety/wakapi/config/config.go:398.29,401.3 2 0 -github.com/muety/wakapi/config/config.go:404.106,406.3 1 0 -github.com/muety/wakapi/config/config.go:407.28,409.3 1 0 -github.com/muety/wakapi/config/config.go:410.51,413.3 2 0 -github.com/muety/wakapi/config/config.go:414.94,416.3 1 0 -github.com/muety/wakapi/config/config.go:417.81,419.3 1 0 -github.com/muety/wakapi/config/config.go:420.75,422.3 1 0 -github.com/muety/wakapi/config/config.go:423.74,425.3 1 0 -github.com/muety/wakapi/config/db.go:39.50,40.19 1 0 -github.com/muety/wakapi/config/db.go:53.2,53.12 1 0 -github.com/muety/wakapi/config/db.go:41.23,45.5 1 0 -github.com/muety/wakapi/config/db.go:46.26,49.5 1 0 -github.com/muety/wakapi/config/db.go:50.24,51.48 1 0 -github.com/muety/wakapi/config/db.go:56.53,66.2 1 1 -github.com/muety/wakapi/config/db.go:68.56,69.25 1 1 -github.com/muety/wakapi/config/db.go:73.2,74.16 2 1 -github.com/muety/wakapi/config/db.go:78.2,85.3 1 1 -github.com/muety/wakapi/config/db.go:69.25,71.3 1 0 -github.com/muety/wakapi/config/db.go:74.16,76.3 1 0 -github.com/muety/wakapi/config/db.go:88.54,90.2 1 1 -github.com/muety/wakapi/config/eventbus.go:26.13,28.2 1 1 -github.com/muety/wakapi/config/eventbus.go:30.26,32.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:22.112,28.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:30.59,41.2 6 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:61.68,66.56 4 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:70.2,71.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:77.2,78.22 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:82.2,83.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:89.2,91.69 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:97.2,98.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:104.2,107.33 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:110.2,110.35 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:113.2,113.34 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:116.2,116.29 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:119.2,119.34 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:123.2,123.47 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:66.56,68.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:71.16,75.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:78.22,80.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:83.16,87.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:91.69,95.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:98.16,102.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:107.33,109.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:110.35,112.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:113.34,115.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:116.29,118.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:119.34,121.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:126.136,135.16 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:139.2,139.36 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:135.16,137.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:28.120,34.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:36.63,45.2 5 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:56.72,58.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:61.2,64.22 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:68.2,69.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:75.2,76.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:81.2,85.4 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:58.16,60.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:64.22,66.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:69.16,73.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:76.16,80.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:88.115,97.29 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:101.2,102.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:106.2,106.36 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:97.29,99.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:102.16,104.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:26.120,32.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:34.63,40.2 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:65.72,67.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:71.2,72.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:78.2,79.44 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:67.16,69.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:72.16,76.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:82.95,88.19 5 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:94.2,95.22 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:124.2,138.37 6 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:149.2,149.38 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:88.19,89.56 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:89.56,91.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:95.22,97.96 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:97.96,99.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:99.9,101.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:102.8,102.128 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:102.128,105.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:105.8,110.17 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:114.3,115.17 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:110.17,112.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:115.17,117.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:138.37,140.17 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:144.3,146.25 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:140.17,142.4 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:21.116,27.2 1 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:29.59,35.2 3 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:46.68,48.16 2 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:52.2,53.72 2 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:59.2,59.70 1 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:48.16,50.3 1 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:53.72,55.3 1 1 -github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:55.8,57.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:22.116,28.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:30.61,36.2 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:47.70,49.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:53.2,54.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:60.2,61.44 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:49.16,51.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:54.16,58.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:64.116,73.29 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:77.2,85.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:89.2,89.36 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:73.29,75.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:85.16,87.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:29.124,34.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:36.63,42.2 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:53.72,55.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:59.2,62.16 4 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:68.2,72.16 4 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:79.2,85.45 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:55.16,57.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:62.16,66.3 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:72.16,77.3 4 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:23.123,29.2 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:31.62,37.2 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:49.71,51.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:55.2,56.16 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:63.2,66.28 3 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:72.2,73.44 2 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:51.16,53.3 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:56.16,61.3 4 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:66.28,67.30 1 0 -github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:67.30,69.4 1 0 -github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0 -github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0 -github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0 -github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0 -github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0 -github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0 -github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0 -github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0 -github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0 -github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0 -github.com/muety/wakapi/models/project_label.go:14.39,16.2 1 0 -github.com/muety/wakapi/models/alias.go:18.32,20.2 1 0 -github.com/muety/wakapi/models/alias.go:22.37,23.35 1 0 -github.com/muety/wakapi/models/alias.go:28.2,28.14 1 0 -github.com/muety/wakapi/models/alias.go:23.35,24.18 1 0 -github.com/muety/wakapi/models/alias.go:24.18,26.4 1 0 -github.com/muety/wakapi/models/duration.go:24.55,38.2 2 0 -github.com/muety/wakapi/models/duration.go:40.39,42.16 2 0 -github.com/muety/wakapi/models/duration.go:45.2,46.10 2 0 -github.com/muety/wakapi/models/duration.go:42.16,44.3 1 0 -github.com/muety/wakapi/models/duration.go:49.49,50.11 1 0 -github.com/muety/wakapi/models/duration.go:65.2,65.15 1 0 -github.com/muety/wakapi/models/duration.go:69.2,69.12 1 0 -github.com/muety/wakapi/models/duration.go:51.22,52.18 1 0 -github.com/muety/wakapi/models/duration.go:53.21,54.17 1 0 -github.com/muety/wakapi/models/duration.go:55.23,56.19 1 0 -github.com/muety/wakapi/models/duration.go:57.17,58.26 1 0 -github.com/muety/wakapi/models/duration.go:59.22,60.18 1 0 -github.com/muety/wakapi/models/duration.go:61.21,62.17 1 0 -github.com/muety/wakapi/models/duration.go:65.15,67.3 1 0 github.com/muety/wakapi/models/heartbeat.go:33.34,35.2 1 1 github.com/muety/wakapi/models/heartbeat.go:37.55,40.2 2 0 github.com/muety/wakapi/models/heartbeat.go:42.65,44.46 2 1 @@ -588,6 +439,111 @@ github.com/muety/wakapi/models/heartbeat.go:99.41,101.16 2 0 github.com/muety/wakapi/models/heartbeat.go:104.2,105.10 2 0 github.com/muety/wakapi/models/heartbeat.go:101.16,103.3 1 0 github.com/muety/wakapi/models/heartbeat.go:108.38,118.2 1 0 +github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0 +github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0 +github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0 +github.com/muety/wakapi/models/leaderboard.go:28.73,30.2 1 0 +github.com/muety/wakapi/models/leaderboard.go:34.56,35.103 1 0 +github.com/muety/wakapi/models/leaderboard.go:35.103,37.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:37.13,39.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:42.63,43.29 1 0 +github.com/muety/wakapi/models/leaderboard.go:43.29,45.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:48.41,49.123 1 0 +github.com/muety/wakapi/models/leaderboard.go:49.123,51.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:54.50,56.2 1 0 +github.com/muety/wakapi/models/leaderboard.go:58.65,59.95 1 0 +github.com/muety/wakapi/models/leaderboard.go:59.95,61.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:64.49,72.25 3 0 +github.com/muety/wakapi/models/leaderboard.go:83.2,83.129 1 0 +github.com/muety/wakapi/models/leaderboard.go:86.2,86.67 1 0 +github.com/muety/wakapi/models/leaderboard.go:90.2,90.79 1 0 +github.com/muety/wakapi/models/leaderboard.go:72.25,73.58 1 0 +github.com/muety/wakapi/models/leaderboard.go:76.3,77.38 2 0 +github.com/muety/wakapi/models/leaderboard.go:80.3,80.40 1 0 +github.com/muety/wakapi/models/leaderboard.go:73.58,74.12 1 0 +github.com/muety/wakapi/models/leaderboard.go:77.38,79.4 1 0 +github.com/muety/wakapi/models/leaderboard.go:83.129,85.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:86.67,88.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:90.79,92.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:95.70,96.107 1 0 +github.com/muety/wakapi/models/leaderboard.go:96.107,98.3 1 0 +github.com/muety/wakapi/models/leaderboard.go:101.45,103.25 2 0 +github.com/muety/wakapi/models/leaderboard.go:108.2,108.19 1 0 +github.com/muety/wakapi/models/leaderboard.go:103.25,104.43 1 0 +github.com/muety/wakapi/models/leaderboard.go:104.43,106.4 1 0 +github.com/muety/wakapi/models/project_label.go:14.39,16.2 1 0 +github.com/muety/wakapi/models/alias.go:18.32,20.2 1 0 +github.com/muety/wakapi/models/alias.go:22.37,23.35 1 0 +github.com/muety/wakapi/models/alias.go:28.2,28.14 1 0 +github.com/muety/wakapi/models/alias.go:23.35,24.18 1 0 +github.com/muety/wakapi/models/alias.go:24.18,26.4 1 0 +github.com/muety/wakapi/models/interval.go:41.47,42.23 1 0 +github.com/muety/wakapi/models/interval.go:47.2,47.14 1 0 +github.com/muety/wakapi/models/interval.go:42.23,43.13 1 0 +github.com/muety/wakapi/models/interval.go:43.13,45.4 1 0 +github.com/muety/wakapi/models/user.go:11.13,13.2 1 1 +github.com/muety/wakapi/models/user.go:85.36,86.22 1 1 +github.com/muety/wakapi/models/user.go:89.2,90.16 2 1 +github.com/muety/wakapi/models/user.go:93.2,93.11 1 1 +github.com/muety/wakapi/models/user.go:86.22,88.3 1 1 +github.com/muety/wakapi/models/user.go:90.16,92.3 1 0 +github.com/muety/wakapi/models/user.go:98.41,101.2 2 1 +github.com/muety/wakapi/models/user.go:103.53,106.54 3 0 +github.com/muety/wakapi/models/user.go:109.2,109.51 1 0 +github.com/muety/wakapi/models/user.go:112.2,112.20 1 0 +github.com/muety/wakapi/models/user.go:106.54,108.3 1 0 +github.com/muety/wakapi/models/user.go:109.51,111.3 1 0 +github.com/muety/wakapi/models/user.go:116.52,117.28 1 0 +github.com/muety/wakapi/models/user.go:120.2,120.17 1 0 +github.com/muety/wakapi/models/user.go:117.28,119.3 1 0 +github.com/muety/wakapi/models/user.go:123.43,126.2 1 0 +github.com/muety/wakapi/models/user.go:128.45,131.2 1 0 +github.com/muety/wakapi/models/user.go:133.33,138.2 1 0 +github.com/muety/wakapi/models/user.go:140.41,142.2 1 0 +github.com/muety/wakapi/models/user.go:144.45,146.2 1 0 +github.com/muety/wakapi/models/user.go:148.45,150.2 1 0 +github.com/muety/wakapi/models/user.go:152.39,154.2 1 0 +github.com/muety/wakapi/models/user.go:156.39,159.2 2 0 +github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1 +github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0 +github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 1 +github.com/muety/wakapi/models/mail_address.go:36.2,36.11 1 1 +github.com/muety/wakapi/models/mail_address.go:30.21,31.21 1 1 +github.com/muety/wakapi/models/mail_address.go:34.3,34.18 1 1 +github.com/muety/wakapi/models/mail_address.go:31.21,33.4 1 1 +github.com/muety/wakapi/models/mail_address.go:39.35,41.2 1 1 +github.com/muety/wakapi/models/mail_address.go:43.43,45.22 2 0 +github.com/muety/wakapi/models/mail_address.go:48.2,48.12 1 0 +github.com/muety/wakapi/models/mail_address.go:45.22,47.3 1 0 +github.com/muety/wakapi/models/mail_address.go:51.46,53.22 2 1 +github.com/muety/wakapi/models/mail_address.go:56.2,56.12 1 1 +github.com/muety/wakapi/models/mail_address.go:53.22,55.3 1 1 +github.com/muety/wakapi/models/mail_address.go:59.40,60.22 1 1 +github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 1 +github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 1 +github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 1 +github.com/muety/wakapi/models/models.go:3.14,5.2 0 1 +github.com/muety/wakapi/models/shared.go:45.52,47.2 1 0 +github.com/muety/wakapi/models/shared.go:49.52,52.16 3 0 +github.com/muety/wakapi/models/shared.go:55.2,57.12 3 0 +github.com/muety/wakapi/models/shared.go:52.16,54.3 1 0 +github.com/muety/wakapi/models/shared.go:60.52,66.22 2 0 +github.com/muety/wakapi/models/shared.go:81.2,84.12 3 0 +github.com/muety/wakapi/models/shared.go:67.14,71.17 2 0 +github.com/muety/wakapi/models/shared.go:74.17,76.8 2 0 +github.com/muety/wakapi/models/shared.go:77.10,78.64 1 0 +github.com/muety/wakapi/models/shared.go:71.17,73.4 1 0 +github.com/muety/wakapi/models/shared.go:87.51,90.2 2 0 +github.com/muety/wakapi/models/shared.go:92.45,94.2 1 0 +github.com/muety/wakapi/models/shared.go:96.37,98.2 1 0 +github.com/muety/wakapi/models/shared.go:100.35,102.2 1 0 +github.com/muety/wakapi/models/shared.go:104.34,106.2 1 0 +github.com/muety/wakapi/models/shared.go:108.34,109.20 1 0 +github.com/muety/wakapi/models/shared.go:112.2,112.19 1 0 +github.com/muety/wakapi/models/shared.go:109.20,111.3 1 0 +github.com/muety/wakapi/models/shared.go:115.35,116.21 1 0 +github.com/muety/wakapi/models/shared.go:119.2,119.34 1 0 +github.com/muety/wakapi/models/shared.go:116.21,118.3 1 0 github.com/muety/wakapi/models/summary.go:64.29,66.2 1 1 github.com/muety/wakapi/models/summary.go:68.35,70.2 1 0 github.com/muety/wakapi/models/summary.go:72.38,74.2 1 0 @@ -686,6 +642,20 @@ github.com/muety/wakapi/models/summary.go:361.50,365.2 1 1 github.com/muety/wakapi/models/summary.go:367.33,369.2 1 1 github.com/muety/wakapi/models/summary.go:371.43,373.2 1 1 github.com/muety/wakapi/models/summary.go:375.38,377.2 1 1 +github.com/muety/wakapi/models/duration.go:24.55,38.2 2 0 +github.com/muety/wakapi/models/duration.go:40.39,42.16 2 0 +github.com/muety/wakapi/models/duration.go:45.2,46.10 2 0 +github.com/muety/wakapi/models/duration.go:42.16,44.3 1 0 +github.com/muety/wakapi/models/duration.go:49.49,50.11 1 0 +github.com/muety/wakapi/models/duration.go:65.2,65.15 1 0 +github.com/muety/wakapi/models/duration.go:69.2,69.12 1 0 +github.com/muety/wakapi/models/duration.go:51.22,52.18 1 0 +github.com/muety/wakapi/models/duration.go:53.21,54.17 1 0 +github.com/muety/wakapi/models/duration.go:55.23,56.19 1 0 +github.com/muety/wakapi/models/duration.go:57.17,58.26 1 0 +github.com/muety/wakapi/models/duration.go:59.22,60.18 1 0 +github.com/muety/wakapi/models/duration.go:61.21,62.17 1 0 +github.com/muety/wakapi/models/duration.go:65.15,67.3 1 0 github.com/muety/wakapi/models/durations.go:7.30,9.2 1 0 github.com/muety/wakapi/models/durations.go:11.40,13.2 1 0 github.com/muety/wakapi/models/durations.go:15.35,17.2 1 0 @@ -786,164 +756,180 @@ github.com/muety/wakapi/models/filters.go:219.2,219.28 1 1 github.com/muety/wakapi/models/filters.go:222.2,222.10 1 1 github.com/muety/wakapi/models/filters.go:216.41,218.3 1 0 github.com/muety/wakapi/models/filters.go:219.28,221.3 1 1 -github.com/muety/wakapi/models/interval.go:41.47,42.23 1 0 -github.com/muety/wakapi/models/interval.go:47.2,47.14 1 0 -github.com/muety/wakapi/models/interval.go:42.23,43.13 1 0 -github.com/muety/wakapi/models/interval.go:43.13,45.4 1 0 -github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0 -github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0 -github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0 +github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0 +github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0 +github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0 +github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0 +github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0 +github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0 +github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0 +github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0 +github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0 +github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0 github.com/muety/wakapi/models/mail.go:19.44,23.2 3 0 github.com/muety/wakapi/models/mail.go:25.44,29.2 3 0 github.com/muety/wakapi/models/mail.go:31.32,44.2 1 0 github.com/muety/wakapi/models/mail.go:46.41,48.2 1 0 -github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1 -github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0 -github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 1 -github.com/muety/wakapi/models/mail_address.go:36.2,36.11 1 1 -github.com/muety/wakapi/models/mail_address.go:30.21,31.21 1 1 -github.com/muety/wakapi/models/mail_address.go:34.3,34.18 1 1 -github.com/muety/wakapi/models/mail_address.go:31.21,33.4 1 1 -github.com/muety/wakapi/models/mail_address.go:39.35,41.2 1 1 -github.com/muety/wakapi/models/mail_address.go:43.43,45.22 2 0 -github.com/muety/wakapi/models/mail_address.go:48.2,48.12 1 0 -github.com/muety/wakapi/models/mail_address.go:45.22,47.3 1 0 -github.com/muety/wakapi/models/mail_address.go:51.46,53.22 2 1 -github.com/muety/wakapi/models/mail_address.go:56.2,56.12 1 1 -github.com/muety/wakapi/models/mail_address.go:53.22,55.3 1 1 -github.com/muety/wakapi/models/mail_address.go:59.40,60.22 1 1 -github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 1 -github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 1 -github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 1 -github.com/muety/wakapi/models/leaderboard.go:28.73,30.2 1 0 -github.com/muety/wakapi/models/leaderboard.go:34.56,35.103 1 0 -github.com/muety/wakapi/models/leaderboard.go:35.103,37.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:37.13,39.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:42.63,43.29 1 0 -github.com/muety/wakapi/models/leaderboard.go:43.29,45.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:48.41,49.123 1 0 -github.com/muety/wakapi/models/leaderboard.go:49.123,51.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:54.50,56.2 1 0 -github.com/muety/wakapi/models/leaderboard.go:58.65,59.95 1 0 -github.com/muety/wakapi/models/leaderboard.go:59.95,61.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:64.49,72.25 3 0 -github.com/muety/wakapi/models/leaderboard.go:83.2,83.129 1 0 -github.com/muety/wakapi/models/leaderboard.go:86.2,86.67 1 0 -github.com/muety/wakapi/models/leaderboard.go:90.2,90.79 1 0 -github.com/muety/wakapi/models/leaderboard.go:72.25,73.58 1 0 -github.com/muety/wakapi/models/leaderboard.go:76.3,77.38 2 0 -github.com/muety/wakapi/models/leaderboard.go:80.3,80.40 1 0 -github.com/muety/wakapi/models/leaderboard.go:73.58,74.12 1 0 -github.com/muety/wakapi/models/leaderboard.go:77.38,79.4 1 0 -github.com/muety/wakapi/models/leaderboard.go:83.129,85.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:86.67,88.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:90.79,92.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:95.70,96.107 1 0 -github.com/muety/wakapi/models/leaderboard.go:96.107,98.3 1 0 -github.com/muety/wakapi/models/leaderboard.go:101.45,103.25 2 0 -github.com/muety/wakapi/models/leaderboard.go:108.2,108.19 1 0 -github.com/muety/wakapi/models/leaderboard.go:103.25,104.43 1 0 -github.com/muety/wakapi/models/leaderboard.go:104.43,106.4 1 0 -github.com/muety/wakapi/models/models.go:3.14,5.2 0 1 -github.com/muety/wakapi/models/shared.go:45.52,47.2 1 0 -github.com/muety/wakapi/models/shared.go:49.52,52.16 3 0 -github.com/muety/wakapi/models/shared.go:55.2,57.12 3 0 -github.com/muety/wakapi/models/shared.go:52.16,54.3 1 0 -github.com/muety/wakapi/models/shared.go:60.52,66.22 2 0 -github.com/muety/wakapi/models/shared.go:81.2,84.12 3 0 -github.com/muety/wakapi/models/shared.go:67.14,71.17 2 0 -github.com/muety/wakapi/models/shared.go:74.17,76.8 2 0 -github.com/muety/wakapi/models/shared.go:77.10,78.64 1 0 -github.com/muety/wakapi/models/shared.go:71.17,73.4 1 0 -github.com/muety/wakapi/models/shared.go:87.51,90.2 2 0 -github.com/muety/wakapi/models/shared.go:92.45,94.2 1 0 -github.com/muety/wakapi/models/shared.go:96.37,98.2 1 0 -github.com/muety/wakapi/models/shared.go:100.35,102.2 1 0 -github.com/muety/wakapi/models/shared.go:104.34,106.2 1 0 -github.com/muety/wakapi/models/shared.go:108.34,109.20 1 0 -github.com/muety/wakapi/models/shared.go:112.2,112.19 1 0 -github.com/muety/wakapi/models/shared.go:109.20,111.3 1 0 -github.com/muety/wakapi/models/shared.go:115.35,116.21 1 0 -github.com/muety/wakapi/models/shared.go:119.2,119.34 1 0 -github.com/muety/wakapi/models/shared.go:116.21,118.3 1 0 -github.com/muety/wakapi/models/user.go:11.13,13.2 1 1 -github.com/muety/wakapi/models/user.go:85.36,86.22 1 1 -github.com/muety/wakapi/models/user.go:89.2,90.16 2 1 -github.com/muety/wakapi/models/user.go:93.2,93.11 1 1 -github.com/muety/wakapi/models/user.go:86.22,88.3 1 1 -github.com/muety/wakapi/models/user.go:90.16,92.3 1 0 -github.com/muety/wakapi/models/user.go:98.41,101.2 2 1 -github.com/muety/wakapi/models/user.go:103.53,106.54 3 0 -github.com/muety/wakapi/models/user.go:109.2,109.51 1 0 -github.com/muety/wakapi/models/user.go:112.2,112.20 1 0 -github.com/muety/wakapi/models/user.go:106.54,108.3 1 0 -github.com/muety/wakapi/models/user.go:109.51,111.3 1 0 -github.com/muety/wakapi/models/user.go:116.52,117.28 1 0 -github.com/muety/wakapi/models/user.go:120.2,120.17 1 0 -github.com/muety/wakapi/models/user.go:117.28,119.3 1 0 -github.com/muety/wakapi/models/user.go:123.43,126.2 1 0 -github.com/muety/wakapi/models/user.go:128.45,131.2 1 0 -github.com/muety/wakapi/models/user.go:133.33,138.2 1 0 -github.com/muety/wakapi/models/user.go:140.41,142.2 1 0 -github.com/muety/wakapi/models/user.go:144.45,146.2 1 0 -github.com/muety/wakapi/models/user.go:148.45,150.2 1 0 -github.com/muety/wakapi/models/user.go:152.39,154.2 1 0 -github.com/muety/wakapi/models/user.go:156.39,159.2 2 0 -github.com/muety/wakapi/services/duration.go:18.78,24.2 2 1 -github.com/muety/wakapi/services/duration.go:26.123,29.42 2 1 -github.com/muety/wakapi/services/duration.go:35.2,36.16 2 1 -github.com/muety/wakapi/services/duration.go:43.2,48.31 4 1 -github.com/muety/wakapi/services/duration.go:96.2,98.31 2 1 -github.com/muety/wakapi/services/duration.go:111.2,111.49 1 1 -github.com/muety/wakapi/services/duration.go:115.2,115.32 1 1 -github.com/muety/wakapi/services/duration.go:29.42,30.90 1 1 -github.com/muety/wakapi/services/duration.go:30.90,32.4 1 1 -github.com/muety/wakapi/services/duration.go:36.16,38.3 1 0 -github.com/muety/wakapi/services/duration.go:48.31,49.42 1 1 -github.com/muety/wakapi/services/duration.go:53.3,55.62 2 1 -github.com/muety/wakapi/services/duration.go:59.3,59.20 1 1 -github.com/muety/wakapi/services/duration.go:64.3,74.15 3 1 -github.com/muety/wakapi/services/duration.go:77.3,83.84 2 1 -github.com/muety/wakapi/services/duration.go:93.3,93.10 1 1 -github.com/muety/wakapi/services/duration.go:49.42,50.12 1 1 -github.com/muety/wakapi/services/duration.go:55.62,57.4 1 1 -github.com/muety/wakapi/services/duration.go:59.20,61.12 2 1 -github.com/muety/wakapi/services/duration.go:74.15,76.4 1 0 -github.com/muety/wakapi/services/duration.go:83.84,85.41 2 1 -github.com/muety/wakapi/services/duration.go:88.4,88.15 1 1 -github.com/muety/wakapi/services/duration.go:85.41,87.5 1 1 -github.com/muety/wakapi/services/duration.go:89.9,91.4 1 1 -github.com/muety/wakapi/services/duration.go:98.31,99.26 1 1 -github.com/muety/wakapi/services/duration.go:99.26,104.23 1 1 -github.com/muety/wakapi/services/duration.go:107.4,107.36 1 1 -github.com/muety/wakapi/services/duration.go:104.23,106.5 1 1 -github.com/muety/wakapi/services/duration.go:111.49,113.3 1 1 -github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0 -github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0 -github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0 -github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0 -github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0 -github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0 -github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0 -github.com/muety/wakapi/services/misc.go:21.126,28.2 1 0 -github.com/muety/wakapi/services/misc.go:40.50,44.2 3 0 -github.com/muety/wakapi/services/misc.go:46.51,48.16 2 0 -github.com/muety/wakapi/services/misc.go:52.2,55.26 3 0 -github.com/muety/wakapi/services/misc.go:61.2,63.40 2 0 -github.com/muety/wakapi/services/misc.go:68.2,70.33 3 0 -github.com/muety/wakapi/services/misc.go:74.2,79.17 2 0 -github.com/muety/wakapi/services/misc.go:83.2,86.17 1 0 -github.com/muety/wakapi/services/misc.go:90.2,90.12 1 0 -github.com/muety/wakapi/services/misc.go:48.16,50.3 1 0 -github.com/muety/wakapi/services/misc.go:55.26,60.3 1 0 -github.com/muety/wakapi/services/misc.go:63.40,65.3 1 0 -github.com/muety/wakapi/services/misc.go:70.33,73.3 2 0 -github.com/muety/wakapi/services/misc.go:79.17,81.3 1 0 -github.com/muety/wakapi/services/misc.go:86.17,88.3 1 0 -github.com/muety/wakapi/services/misc.go:93.116,94.24 1 0 -github.com/muety/wakapi/services/misc.go:94.24,95.156 1 0 -github.com/muety/wakapi/services/misc.go:95.156,97.4 1 0 -github.com/muety/wakapi/services/misc.go:97.9,102.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:28.120,34.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:36.63,45.2 5 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:56.72,58.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:61.2,64.22 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:68.2,69.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:75.2,76.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:81.2,85.4 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:58.16,60.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:64.22,66.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:69.16,73.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:76.16,80.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:88.115,97.29 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:101.2,102.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:106.2,106.36 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:97.29,99.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/statusbar.go:102.16,104.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:27.120,33.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:35.63,41.2 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:66.72,68.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:72.2,73.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:79.2,80.46 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:68.16,70.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:73.16,77.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:83.95,89.19 5 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:95.2,96.22 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:125.2,139.37 6 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:150.2,150.38 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:89.19,90.56 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:90.56,92.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:96.22,98.98 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:98.98,100.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:100.9,102.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:103.8,103.130 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:103.130,106.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:106.8,111.17 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:115.3,116.17 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:111.17,113.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:116.17,118.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:139.37,141.17 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:145.3,147.25 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/summaries.go:141.17,143.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:21.116,27.2 1 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:29.59,35.2 3 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:46.68,48.16 2 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:52.2,53.72 2 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:59.2,59.72 1 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:48.16,50.3 1 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:53.72,55.3 1 1 +github.com/muety/wakapi/routes/compat/wakatime/v1/users.go:55.8,57.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:22.116,28.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:30.61,36.2 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:47.70,49.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:53.2,54.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:60.2,61.46 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:49.16,51.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:54.16,58.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:64.116,73.29 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:77.2,85.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:89.2,89.36 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:73.29,75.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/all_time.go:85.16,87.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:29.124,34.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:36.63,42.2 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:53.72,55.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:59.2,62.16 4 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:68.2,72.16 4 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:79.2,85.47 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:55.16,57.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:62.16,66.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/heartbeat.go:72.16,77.3 4 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:23.123,29.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:31.62,37.2 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:49.71,51.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:55.2,56.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:63.2,66.28 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:72.2,73.46 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:51.16,53.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:56.16,61.3 4 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:66.28,67.30 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/projects.go:67.30,69.4 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:22.112,28.2 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:30.59,41.2 6 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:61.68,66.56 4 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:70.2,71.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:77.2,78.22 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:82.2,83.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:89.2,91.69 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:97.2,98.16 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:104.2,107.33 2 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:110.2,110.35 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:113.2,113.34 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:116.2,116.29 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:119.2,119.34 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:123.2,123.49 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:66.56,68.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:71.16,75.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:78.22,80.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:83.16,87.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:91.69,95.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:98.16,102.3 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:107.33,109.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:110.35,112.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:113.34,115.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:116.29,118.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:119.34,121.3 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:126.136,135.16 3 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:139.2,139.36 1 0 +github.com/muety/wakapi/routes/compat/wakatime/v1/stats.go:135.16,137.3 1 0 +github.com/muety/wakapi/services/diagnostics.go:14.101,19.2 1 0 +github.com/muety/wakapi/services/diagnostics.go:21.101,24.2 2 0 +github.com/muety/wakapi/services/project_label.go:21.111,28.2 1 0 +github.com/muety/wakapi/services/project_label.go:30.80,32.2 1 0 +github.com/muety/wakapi/services/project_label.go:34.90,35.51 1 0 +github.com/muety/wakapi/services/project_label.go:39.2,40.16 2 0 +github.com/muety/wakapi/services/project_label.go:43.2,44.20 2 0 +github.com/muety/wakapi/services/project_label.go:35.51,37.3 1 0 +github.com/muety/wakapi/services/project_label.go:40.16,42.3 1 0 +github.com/muety/wakapi/services/project_label.go:48.108,50.16 2 0 +github.com/muety/wakapi/services/project_label.go:53.2,53.112 1 0 +github.com/muety/wakapi/services/project_label.go:56.2,56.26 1 0 +github.com/muety/wakapi/services/project_label.go:50.16,52.3 1 0 +github.com/muety/wakapi/services/project_label.go:53.112,55.3 1 0 +github.com/muety/wakapi/services/project_label.go:60.116,62.16 2 0 +github.com/muety/wakapi/services/project_label.go:65.2,65.112 1 0 +github.com/muety/wakapi/services/project_label.go:68.2,68.26 1 0 +github.com/muety/wakapi/services/project_label.go:62.16,64.3 1 0 +github.com/muety/wakapi/services/project_label.go:65.112,67.3 1 0 +github.com/muety/wakapi/services/project_label.go:71.98,73.16 2 0 +github.com/muety/wakapi/services/project_label.go:77.2,79.20 3 0 +github.com/muety/wakapi/services/project_label.go:73.16,75.3 1 0 +github.com/muety/wakapi/services/project_label.go:82.74,83.24 1 0 +github.com/muety/wakapi/services/project_label.go:86.2,89.12 4 0 +github.com/muety/wakapi/services/project_label.go:83.24,85.3 1 0 +github.com/muety/wakapi/services/project_label.go:92.89,94.14 2 0 +github.com/muety/wakapi/services/project_label.go:97.2,100.4 1 0 +github.com/muety/wakapi/services/project_label.go:94.14,96.3 1 0 +github.com/muety/wakapi/services/report.go:31.122,44.2 2 0 +github.com/muety/wakapi/services/report.go:46.38,49.45 2 0 +github.com/muety/wakapi/services/report.go:67.2,67.49 1 0 +github.com/muety/wakapi/services/report.go:87.2,87.16 1 0 +github.com/muety/wakapi/services/report.go:49.45,50.46 1 0 +github.com/muety/wakapi/services/report.go:50.46,53.57 2 0 +github.com/muety/wakapi/services/report.go:58.4,58.58 1 0 +github.com/muety/wakapi/services/report.go:53.57,55.5 1 0 +github.com/muety/wakapi/services/report.go:58.58,61.5 2 0 +github.com/muety/wakapi/services/report.go:62.18,64.4 1 0 +github.com/muety/wakapi/services/report.go:67.49,70.17 2 0 +github.com/muety/wakapi/services/report.go:76.3,76.78 1 0 +github.com/muety/wakapi/services/report.go:81.3,82.27 2 0 +github.com/muety/wakapi/services/report.go:70.17,73.4 2 0 +github.com/muety/wakapi/services/report.go:76.78,78.4 1 0 +github.com/muety/wakapi/services/report.go:82.27,84.4 1 0 +github.com/muety/wakapi/services/report.go:87.16,89.3 1 0 +github.com/muety/wakapi/services/report.go:92.87,93.22 1 0 +github.com/muety/wakapi/services/report.go:98.2,104.16 5 0 +github.com/muety/wakapi/services/report.go:109.2,116.65 2 0 +github.com/muety/wakapi/services/report.go:121.2,122.12 2 0 +github.com/muety/wakapi/services/report.go:93.22,96.3 2 0 +github.com/muety/wakapi/services/report.go:104.16,107.3 2 0 +github.com/muety/wakapi/services/report.go:116.65,119.3 2 0 github.com/muety/wakapi/services/summary.go:29.189,41.33 3 1 github.com/muety/wakapi/services/summary.go:47.2,47.12 1 1 github.com/muety/wakapi/services/summary.go:41.33,42.31 1 1 @@ -1072,8 +1058,113 @@ github.com/muety/wakapi/services/summary.go:469.3,469.24 1 1 github.com/muety/wakapi/services/summary.go:460.17,461.33 1 1 github.com/muety/wakapi/services/summary.go:461.33,463.5 1 1 github.com/muety/wakapi/services/summary.go:466.28,468.4 1 1 -github.com/muety/wakapi/services/diagnostics.go:14.101,19.2 1 0 -github.com/muety/wakapi/services/diagnostics.go:21.101,24.2 2 0 +github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0 +github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0 +github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0 +github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0 +github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0 +github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0 +github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0 +github.com/muety/wakapi/services/aggregation.go:31.142,41.2 1 0 +github.com/muety/wakapi/services/aggregation.go:50.43,53.52 2 0 +github.com/muety/wakapi/services/aggregation.go:53.52,54.80 1 0 +github.com/muety/wakapi/services/aggregation.go:54.80,56.4 1 0 +github.com/muety/wakapi/services/aggregation.go:57.58,59.3 1 0 +github.com/muety/wakapi/services/aggregation.go:62.92,63.47 1 0 +github.com/muety/wakapi/services/aggregation.go:66.2,72.16 4 0 +github.com/muety/wakapi/services/aggregation.go:78.2,79.16 2 0 +github.com/muety/wakapi/services/aggregation.go:85.2,86.44 2 0 +github.com/muety/wakapi/services/aggregation.go:91.2,93.12 3 0 +github.com/muety/wakapi/services/aggregation.go:104.2,104.41 1 0 +github.com/muety/wakapi/services/aggregation.go:122.2,122.12 1 0 +github.com/muety/wakapi/services/aggregation.go:63.47,65.3 1 0 +github.com/muety/wakapi/services/aggregation.go:72.16,75.3 2 0 +github.com/muety/wakapi/services/aggregation.go:79.16,82.3 2 0 +github.com/muety/wakapi/services/aggregation.go:86.44,88.3 1 0 +github.com/muety/wakapi/services/aggregation.go:93.12,94.25 1 0 +github.com/muety/wakapi/services/aggregation.go:94.25,95.47 1 0 +github.com/muety/wakapi/services/aggregation.go:95.47,97.5 1 0 +github.com/muety/wakapi/services/aggregation.go:97.19,99.5 1 0 +github.com/muety/wakapi/services/aggregation.go:104.41,105.71 1 0 +github.com/muety/wakapi/services/aggregation.go:109.3,109.21 1 0 +github.com/muety/wakapi/services/aggregation.go:105.71,106.12 1 0 +github.com/muety/wakapi/services/aggregation.go:109.21,113.4 1 0 +github.com/muety/wakapi/services/aggregation.go:113.9,113.62 1 0 +github.com/muety/wakapi/services/aggregation.go:113.62,117.4 1 0 +github.com/muety/wakapi/services/aggregation.go:125.61,126.115 1 0 +github.com/muety/wakapi/services/aggregation.go:126.115,128.3 1 0 +github.com/muety/wakapi/services/aggregation.go:128.8,130.60 2 0 +github.com/muety/wakapi/services/aggregation.go:130.60,132.4 1 0 +github.com/muety/wakapi/services/aggregation.go:136.83,151.41 5 0 +github.com/muety/wakapi/services/aggregation.go:151.41,161.3 3 0 +github.com/muety/wakapi/services/aggregation.go:164.83,167.27 3 0 +github.com/muety/wakapi/services/aggregation.go:172.2,173.12 2 0 +github.com/muety/wakapi/services/aggregation.go:167.27,168.34 1 0 +github.com/muety/wakapi/services/aggregation.go:168.34,170.4 1 0 +github.com/muety/wakapi/services/aggregation.go:176.79,179.27 3 0 +github.com/muety/wakapi/services/aggregation.go:179.27,181.3 1 0 +github.com/muety/wakapi/services/aggregation.go:184.34,187.2 2 0 +github.com/muety/wakapi/services/alias.go:19.77,24.2 1 1 +github.com/muety/wakapi/services/alias.go:28.60,29.43 1 1 +github.com/muety/wakapi/services/alias.go:32.2,32.14 1 1 +github.com/muety/wakapi/services/alias.go:29.43,31.3 1 1 +github.com/muety/wakapi/services/alias.go:35.62,37.16 2 1 +github.com/muety/wakapi/services/alias.go:40.2,40.12 1 1 +github.com/muety/wakapi/services/alias.go:37.16,39.3 1 1 +github.com/muety/wakapi/services/alias.go:43.59,44.51 1 1 +github.com/muety/wakapi/services/alias.go:44.51,46.3 1 0 +github.com/muety/wakapi/services/alias.go:49.76,50.32 1 0 +github.com/muety/wakapi/services/alias.go:53.2,53.49 1 0 +github.com/muety/wakapi/services/alias.go:50.32,52.3 1 0 +github.com/muety/wakapi/services/alias.go:53.49,55.3 1 0 +github.com/muety/wakapi/services/alias.go:55.8,57.3 1 0 +github.com/muety/wakapi/services/alias.go:60.102,61.38 1 0 +github.com/muety/wakapi/services/alias.go:64.2,64.39 1 0 +github.com/muety/wakapi/services/alias.go:61.38,63.3 1 0 +github.com/muety/wakapi/services/alias.go:67.113,68.38 1 0 +github.com/muety/wakapi/services/alias.go:71.2,71.39 1 0 +github.com/muety/wakapi/services/alias.go:68.38,70.3 1 0 +github.com/muety/wakapi/services/alias.go:74.108,75.32 1 1 +github.com/muety/wakapi/services/alias.go:79.2,79.49 1 1 +github.com/muety/wakapi/services/alias.go:87.2,87.19 1 1 +github.com/muety/wakapi/services/alias.go:75.32,77.3 1 1 +github.com/muety/wakapi/services/alias.go:79.49,80.47 1 1 +github.com/muety/wakapi/services/alias.go:80.47,81.49 1 1 +github.com/muety/wakapi/services/alias.go:81.49,83.5 1 1 +github.com/muety/wakapi/services/alias.go:90.77,92.16 2 0 +github.com/muety/wakapi/services/alias.go:96.2,100.20 3 0 +github.com/muety/wakapi/services/alias.go:92.16,94.3 1 0 +github.com/muety/wakapi/services/alias.go:103.60,104.24 1 0 +github.com/muety/wakapi/services/alias.go:107.2,110.16 2 0 +github.com/muety/wakapi/services/alias.go:114.2,116.12 2 0 +github.com/muety/wakapi/services/alias.go:104.24,106.3 1 0 +github.com/muety/wakapi/services/alias.go:110.16,112.3 1 0 +github.com/muety/wakapi/services/alias.go:119.69,123.28 3 0 +github.com/muety/wakapi/services/alias.go:131.2,134.16 2 0 +github.com/muety/wakapi/services/alias.go:140.2,140.31 1 0 +github.com/muety/wakapi/services/alias.go:144.2,144.12 1 0 +github.com/muety/wakapi/services/alias.go:123.28,124.21 1 0 +github.com/muety/wakapi/services/alias.go:127.3,128.16 2 0 +github.com/muety/wakapi/services/alias.go:124.21,126.4 1 0 +github.com/muety/wakapi/services/alias.go:134.16,135.29 1 0 +github.com/muety/wakapi/services/alias.go:135.29,137.4 1 0 +github.com/muety/wakapi/services/alias.go:140.31,142.3 1 0 +github.com/muety/wakapi/services/alias.go:147.74,148.14 1 0 +github.com/muety/wakapi/services/alias.go:148.14,149.57 1 0 +github.com/muety/wakapi/services/alias.go:149.57,153.4 3 0 +github.com/muety/wakapi/services/alias.go:154.8,155.57 1 0 +github.com/muety/wakapi/services/alias.go:155.57,157.48 2 0 +github.com/muety/wakapi/services/alias.go:162.4,162.52 1 0 +github.com/muety/wakapi/services/alias.go:157.48,158.26 1 0 +github.com/muety/wakapi/services/alias.go:158.26,160.6 1 0 +github.com/muety/wakapi/services/alias.go:167.116,168.32 1 0 +github.com/muety/wakapi/services/alias.go:171.2,171.49 1 0 +github.com/muety/wakapi/services/alias.go:168.32,170.3 1 0 +github.com/muety/wakapi/services/alias.go:171.49,173.47 2 0 +github.com/muety/wakapi/services/alias.go:178.3,178.30 1 0 +github.com/muety/wakapi/services/alias.go:173.47,174.16 1 0 +github.com/muety/wakapi/services/alias.go:174.16,176.5 1 0 +github.com/muety/wakapi/services/alias.go:179.8,181.3 1 0 github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0 github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0 github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0 @@ -1093,109 +1184,92 @@ github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0 github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0 github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0 github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0 -github.com/muety/wakapi/services/leaderboard.go:27.159,38.33 3 0 -github.com/muety/wakapi/services/leaderboard.go:62.2,62.12 1 0 -github.com/muety/wakapi/services/leaderboard.go:38.33,39.31 1 0 -github.com/muety/wakapi/services/leaderboard.go:39.31,45.18 3 0 -github.com/muety/wakapi/services/leaderboard.go:49.4,49.41 1 0 -github.com/muety/wakapi/services/leaderboard.go:45.18,47.5 1 0 -github.com/muety/wakapi/services/leaderboard.go:49.41,52.5 2 0 -github.com/muety/wakapi/services/leaderboard.go:52.10,52.48 1 0 -github.com/muety/wakapi/services/leaderboard.go:52.48,54.64 2 0 -github.com/muety/wakapi/services/leaderboard.go:57.5,57.22 1 0 -github.com/muety/wakapi/services/leaderboard.go:54.64,56.6 1 0 -github.com/muety/wakapi/services/leaderboard.go:65.50,66.64 1 0 -github.com/muety/wakapi/services/leaderboard.go:76.2,78.19 3 0 -github.com/muety/wakapi/services/leaderboard.go:66.64,68.17 2 0 -github.com/muety/wakapi/services/leaderboard.go:73.3,73.31 1 0 -github.com/muety/wakapi/services/leaderboard.go:68.17,71.4 2 0 -github.com/muety/wakapi/services/leaderboard.go:81.106,84.29 2 0 -github.com/muety/wakapi/services/leaderboard.go:119.2,121.12 3 0 -github.com/muety/wakapi/services/leaderboard.go:84.29,85.83 1 0 -github.com/muety/wakapi/services/leaderboard.go:90.3,91.17 2 0 -github.com/muety/wakapi/services/leaderboard.go:96.3,96.85 1 0 -github.com/muety/wakapi/services/leaderboard.go:101.3,101.25 1 0 -github.com/muety/wakapi/services/leaderboard.go:85.83,87.12 2 0 -github.com/muety/wakapi/services/leaderboard.go:91.17,93.12 2 0 -github.com/muety/wakapi/services/leaderboard.go:96.85,98.12 2 0 -github.com/muety/wakapi/services/leaderboard.go:101.25,103.18 2 0 -github.com/muety/wakapi/services/leaderboard.go:108.4,108.23 1 0 -github.com/muety/wakapi/services/leaderboard.go:112.4,112.60 1 0 -github.com/muety/wakapi/services/leaderboard.go:103.18,105.13 2 0 -github.com/muety/wakapi/services/leaderboard.go:108.23,109.13 1 0 -github.com/muety/wakapi/services/leaderboard.go:112.60,114.13 2 0 -github.com/muety/wakapi/services/leaderboard.go:124.77,127.2 2 0 -github.com/muety/wakapi/services/leaderboard.go:129.60,132.52 2 0 -github.com/muety/wakapi/services/leaderboard.go:136.2,137.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:140.2,140.19 1 0 -github.com/muety/wakapi/services/leaderboard.go:132.52,134.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:137.16,139.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:143.154,145.2 1 0 -github.com/muety/wakapi/services/leaderboard.go:147.144,149.2 1 0 -github.com/muety/wakapi/services/leaderboard.go:151.175,154.52 2 0 -github.com/muety/wakapi/services/leaderboard.go:158.2,159.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:163.2,163.18 1 0 -github.com/muety/wakapi/services/leaderboard.go:176.2,177.19 2 0 -github.com/muety/wakapi/services/leaderboard.go:154.52,156.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:159.16,161.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:163.18,165.17 2 0 -github.com/muety/wakapi/services/leaderboard.go:165.17,167.4 1 0 -github.com/muety/wakapi/services/leaderboard.go:167.9,168.31 1 0 -github.com/muety/wakapi/services/leaderboard.go:168.31,169.40 1 0 -github.com/muety/wakapi/services/leaderboard.go:169.40,171.6 1 0 -github.com/muety/wakapi/services/leaderboard.go:180.165,183.52 2 0 -github.com/muety/wakapi/services/leaderboard.go:187.2,188.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:192.2,192.17 1 0 -github.com/muety/wakapi/services/leaderboard.go:203.2,204.19 2 0 -github.com/muety/wakapi/services/leaderboard.go:183.52,185.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:188.16,190.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:192.17,194.17 2 0 -github.com/muety/wakapi/services/leaderboard.go:194.17,196.4 1 0 -github.com/muety/wakapi/services/leaderboard.go:196.9,197.31 1 0 -github.com/muety/wakapi/services/leaderboard.go:197.31,199.5 1 0 -github.com/muety/wakapi/services/leaderboard.go:207.129,209.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:213.2,214.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:218.2,223.8 1 0 -github.com/muety/wakapi/services/leaderboard.go:209.16,211.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:214.16,216.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:226.151,228.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:232.2,233.16 2 0 -github.com/muety/wakapi/services/leaderboard.go:237.2,240.42 3 0 -github.com/muety/wakapi/services/leaderboard.go:252.2,252.19 1 0 -github.com/muety/wakapi/services/leaderboard.go:228.16,230.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:233.16,235.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:240.42,250.3 2 0 -github.com/muety/wakapi/services/leaderboard.go:255.132,257.47 2 0 -github.com/muety/wakapi/services/leaderboard.go:260.2,260.23 1 0 -github.com/muety/wakapi/services/leaderboard.go:263.2,263.10 1 0 -github.com/muety/wakapi/services/leaderboard.go:257.47,259.3 1 0 -github.com/muety/wakapi/services/leaderboard.go:260.23,262.3 1 0 -github.com/muety/wakapi/services/project_label.go:21.111,28.2 1 0 -github.com/muety/wakapi/services/project_label.go:30.80,32.2 1 0 -github.com/muety/wakapi/services/project_label.go:34.90,35.51 1 0 -github.com/muety/wakapi/services/project_label.go:39.2,40.16 2 0 -github.com/muety/wakapi/services/project_label.go:43.2,44.20 2 0 -github.com/muety/wakapi/services/project_label.go:35.51,37.3 1 0 -github.com/muety/wakapi/services/project_label.go:40.16,42.3 1 0 -github.com/muety/wakapi/services/project_label.go:48.108,50.16 2 0 -github.com/muety/wakapi/services/project_label.go:53.2,53.112 1 0 -github.com/muety/wakapi/services/project_label.go:56.2,56.26 1 0 -github.com/muety/wakapi/services/project_label.go:50.16,52.3 1 0 -github.com/muety/wakapi/services/project_label.go:53.112,55.3 1 0 -github.com/muety/wakapi/services/project_label.go:60.116,62.16 2 0 -github.com/muety/wakapi/services/project_label.go:65.2,65.112 1 0 -github.com/muety/wakapi/services/project_label.go:68.2,68.26 1 0 -github.com/muety/wakapi/services/project_label.go:62.16,64.3 1 0 -github.com/muety/wakapi/services/project_label.go:65.112,67.3 1 0 -github.com/muety/wakapi/services/project_label.go:71.98,73.16 2 0 -github.com/muety/wakapi/services/project_label.go:77.2,79.20 3 0 -github.com/muety/wakapi/services/project_label.go:73.16,75.3 1 0 -github.com/muety/wakapi/services/project_label.go:82.74,83.24 1 0 -github.com/muety/wakapi/services/project_label.go:86.2,89.12 4 0 -github.com/muety/wakapi/services/project_label.go:83.24,85.3 1 0 -github.com/muety/wakapi/services/project_label.go:92.89,94.14 2 0 -github.com/muety/wakapi/services/project_label.go:97.2,100.4 1 0 -github.com/muety/wakapi/services/project_label.go:94.14,96.3 1 0 +github.com/muety/wakapi/services/user.go:26.99,36.33 3 0 +github.com/muety/wakapi/services/user.go:57.2,57.12 1 0 +github.com/muety/wakapi/services/user.go:36.33,37.31 1 0 +github.com/muety/wakapi/services/user.go:37.31,43.73 4 0 +github.com/muety/wakapi/services/user.go:47.4,47.24 1 0 +github.com/muety/wakapi/services/user.go:43.73,45.5 1 0 +github.com/muety/wakapi/services/user.go:47.24,48.80 1 0 +github.com/muety/wakapi/services/user.go:48.80,50.6 1 0 +github.com/muety/wakapi/services/user.go:50.11,52.6 1 0 +github.com/muety/wakapi/services/user.go:60.74,61.40 1 0 +github.com/muety/wakapi/services/user.go:65.2,66.16 2 0 +github.com/muety/wakapi/services/user.go:70.2,71.15 2 0 +github.com/muety/wakapi/services/user.go:61.40,63.3 1 0 +github.com/muety/wakapi/services/user.go:66.16,68.3 1 0 +github.com/muety/wakapi/services/user.go:74.72,75.37 1 0 +github.com/muety/wakapi/services/user.go:79.2,80.16 2 0 +github.com/muety/wakapi/services/user.go:84.2,85.15 2 0 +github.com/muety/wakapi/services/user.go:75.37,77.3 1 0 +github.com/muety/wakapi/services/user.go:80.16,82.3 1 0 +github.com/muety/wakapi/services/user.go:88.76,90.2 1 0 +github.com/muety/wakapi/services/user.go:92.86,94.2 1 0 +github.com/muety/wakapi/services/user.go:96.58,98.2 1 0 +github.com/muety/wakapi/services/user.go:100.71,102.2 1 0 +github.com/muety/wakapi/services/user.go:104.86,106.16 2 0 +github.com/muety/wakapi/services/user.go:109.2,109.112 1 0 +github.com/muety/wakapi/services/user.go:106.16,108.3 1 0 +github.com/muety/wakapi/services/user.go:109.112,111.3 1 0 +github.com/muety/wakapi/services/user.go:114.86,116.2 1 0 +github.com/muety/wakapi/services/user.go:118.94,120.2 1 0 +github.com/muety/wakapi/services/user.go:122.71,124.12 2 0 +github.com/muety/wakapi/services/user.go:128.2,129.42 2 0 +github.com/muety/wakapi/services/user.go:133.2,134.16 2 0 +github.com/muety/wakapi/services/user.go:138.2,139.21 2 0 +github.com/muety/wakapi/services/user.go:124.12,126.3 1 0 +github.com/muety/wakapi/services/user.go:129.42,131.3 1 0 +github.com/muety/wakapi/services/user.go:134.16,136.3 1 0 +github.com/muety/wakapi/services/user.go:142.48,144.2 1 0 +github.com/muety/wakapi/services/user.go:146.102,156.93 2 0 +github.com/muety/wakapi/services/user.go:162.2,162.38 1 0 +github.com/muety/wakapi/services/user.go:156.93,158.3 1 0 +github.com/muety/wakapi/services/user.go:158.8,160.3 1 0 +github.com/muety/wakapi/services/user.go:165.73,169.2 3 0 +github.com/muety/wakapi/services/user.go:171.78,175.2 3 0 +github.com/muety/wakapi/services/user.go:177.122,180.35 2 0 +github.com/muety/wakapi/services/user.go:186.2,186.35 1 0 +github.com/muety/wakapi/services/user.go:190.2,190.18 1 0 +github.com/muety/wakapi/services/user.go:180.35,181.89 1 0 +github.com/muety/wakapi/services/user.go:181.89,183.4 1 0 +github.com/muety/wakapi/services/user.go:186.35,188.3 1 0 +github.com/muety/wakapi/services/user.go:193.106,196.96 3 0 +github.com/muety/wakapi/services/user.go:201.2,201.68 1 0 +github.com/muety/wakapi/services/user.go:196.96,198.3 1 0 +github.com/muety/wakapi/services/user.go:198.8,200.3 1 0 +github.com/muety/wakapi/services/user.go:204.85,206.2 1 0 +github.com/muety/wakapi/services/user.go:208.57,215.2 4 0 +github.com/muety/wakapi/services/user.go:217.38,219.2 1 0 +github.com/muety/wakapi/services/user.go:221.57,226.2 1 0 +github.com/muety/wakapi/services/duration.go:18.78,24.2 2 1 +github.com/muety/wakapi/services/duration.go:26.123,29.42 2 1 +github.com/muety/wakapi/services/duration.go:35.2,36.16 2 1 +github.com/muety/wakapi/services/duration.go:43.2,48.31 4 1 +github.com/muety/wakapi/services/duration.go:96.2,98.31 2 1 +github.com/muety/wakapi/services/duration.go:111.2,111.49 1 1 +github.com/muety/wakapi/services/duration.go:115.2,115.32 1 1 +github.com/muety/wakapi/services/duration.go:29.42,30.90 1 1 +github.com/muety/wakapi/services/duration.go:30.90,32.4 1 1 +github.com/muety/wakapi/services/duration.go:36.16,38.3 1 0 +github.com/muety/wakapi/services/duration.go:48.31,49.42 1 1 +github.com/muety/wakapi/services/duration.go:53.3,55.62 2 1 +github.com/muety/wakapi/services/duration.go:59.3,59.20 1 1 +github.com/muety/wakapi/services/duration.go:64.3,74.15 3 1 +github.com/muety/wakapi/services/duration.go:77.3,83.84 2 1 +github.com/muety/wakapi/services/duration.go:93.3,93.10 1 1 +github.com/muety/wakapi/services/duration.go:49.42,50.12 1 1 +github.com/muety/wakapi/services/duration.go:55.62,57.4 1 1 +github.com/muety/wakapi/services/duration.go:59.20,61.12 2 1 +github.com/muety/wakapi/services/duration.go:74.15,76.4 1 0 +github.com/muety/wakapi/services/duration.go:83.84,85.41 2 1 +github.com/muety/wakapi/services/duration.go:88.4,88.15 1 1 +github.com/muety/wakapi/services/duration.go:85.41,87.5 1 1 +github.com/muety/wakapi/services/duration.go:89.9,91.4 1 1 +github.com/muety/wakapi/services/duration.go:98.31,99.26 1 1 +github.com/muety/wakapi/services/duration.go:99.26,104.23 1 1 +github.com/muety/wakapi/services/duration.go:107.4,107.36 1 1 +github.com/muety/wakapi/services/duration.go:104.23,106.5 1 1 +github.com/muety/wakapi/services/duration.go:111.49,113.3 1 1 github.com/muety/wakapi/services/heartbeat.go:26.141,40.33 3 0 github.com/muety/wakapi/services/heartbeat.go:48.2,48.12 1 0 github.com/muety/wakapi/services/heartbeat.go:40.33,41.31 1 0 @@ -1267,191 +1341,101 @@ github.com/muety/wakapi/services/heartbeat.go:259.94,261.48 2 0 github.com/muety/wakapi/services/heartbeat.go:267.2,267.18 1 0 github.com/muety/wakapi/services/heartbeat.go:261.48,263.18 2 0 github.com/muety/wakapi/services/heartbeat.go:263.18,265.4 1 0 -github.com/muety/wakapi/services/aggregation.go:30.142,38.2 1 0 -github.com/muety/wakapi/services/aggregation.go:47.43,51.2 3 0 -github.com/muety/wakapi/services/aggregation.go:53.77,54.47 1 0 -github.com/muety/wakapi/services/aggregation.go:57.2,62.40 4 0 -github.com/muety/wakapi/services/aggregation.go:66.2,66.50 1 0 -github.com/muety/wakapi/services/aggregation.go:71.2,71.60 1 0 -github.com/muety/wakapi/services/aggregation.go:77.2,77.35 1 0 -github.com/muety/wakapi/services/aggregation.go:54.47,56.3 1 0 -github.com/muety/wakapi/services/aggregation.go:62.40,64.3 1 0 -github.com/muety/wakapi/services/aggregation.go:66.50,68.3 1 0 -github.com/muety/wakapi/services/aggregation.go:71.60,75.3 3 0 -github.com/muety/wakapi/services/aggregation.go:80.109,81.24 1 0 -github.com/muety/wakapi/services/aggregation.go:81.24,82.116 1 0 -github.com/muety/wakapi/services/aggregation.go:82.116,84.4 1 0 -github.com/muety/wakapi/services/aggregation.go:84.9,87.4 2 0 -github.com/muety/wakapi/services/aggregation.go:91.80,92.33 1 0 -github.com/muety/wakapi/services/aggregation.go:92.33,93.60 1 0 -github.com/muety/wakapi/services/aggregation.go:93.60,95.4 1 0 -github.com/muety/wakapi/services/aggregation.go:99.110,104.16 3 0 -github.com/muety/wakapi/services/aggregation.go:110.2,111.16 2 0 -github.com/muety/wakapi/services/aggregation.go:117.2,118.44 2 0 -github.com/muety/wakapi/services/aggregation.go:123.2,123.41 1 0 -github.com/muety/wakapi/services/aggregation.go:141.2,141.12 1 0 -github.com/muety/wakapi/services/aggregation.go:104.16,107.3 2 0 -github.com/muety/wakapi/services/aggregation.go:111.16,114.3 2 0 -github.com/muety/wakapi/services/aggregation.go:118.44,120.3 1 0 -github.com/muety/wakapi/services/aggregation.go:123.41,124.71 1 0 -github.com/muety/wakapi/services/aggregation.go:128.3,128.21 1 0 -github.com/muety/wakapi/services/aggregation.go:124.71,125.12 1 0 -github.com/muety/wakapi/services/aggregation.go:128.21,132.4 1 0 -github.com/muety/wakapi/services/aggregation.go:132.9,132.62 1 0 -github.com/muety/wakapi/services/aggregation.go:132.62,136.4 1 0 -github.com/muety/wakapi/services/aggregation.go:144.83,147.27 3 0 -github.com/muety/wakapi/services/aggregation.go:152.2,153.12 2 0 -github.com/muety/wakapi/services/aggregation.go:147.27,148.34 1 0 -github.com/muety/wakapi/services/aggregation.go:148.34,150.4 1 0 -github.com/muety/wakapi/services/aggregation.go:156.79,159.27 3 0 -github.com/muety/wakapi/services/aggregation.go:159.27,161.3 1 0 -github.com/muety/wakapi/services/aggregation.go:164.83,179.41 5 0 -github.com/muety/wakapi/services/aggregation.go:179.41,189.3 3 0 -github.com/muety/wakapi/services/aggregation.go:192.34,195.2 2 0 -github.com/muety/wakapi/services/alias.go:19.77,24.2 1 1 -github.com/muety/wakapi/services/alias.go:28.60,29.43 1 1 -github.com/muety/wakapi/services/alias.go:32.2,32.14 1 1 -github.com/muety/wakapi/services/alias.go:29.43,31.3 1 1 -github.com/muety/wakapi/services/alias.go:35.62,37.16 2 1 -github.com/muety/wakapi/services/alias.go:40.2,40.12 1 1 -github.com/muety/wakapi/services/alias.go:37.16,39.3 1 1 -github.com/muety/wakapi/services/alias.go:43.59,44.51 1 1 -github.com/muety/wakapi/services/alias.go:44.51,46.3 1 0 -github.com/muety/wakapi/services/alias.go:49.76,50.32 1 0 -github.com/muety/wakapi/services/alias.go:53.2,53.49 1 0 -github.com/muety/wakapi/services/alias.go:50.32,52.3 1 0 -github.com/muety/wakapi/services/alias.go:53.49,55.3 1 0 -github.com/muety/wakapi/services/alias.go:55.8,57.3 1 0 -github.com/muety/wakapi/services/alias.go:60.102,61.38 1 0 -github.com/muety/wakapi/services/alias.go:64.2,64.39 1 0 -github.com/muety/wakapi/services/alias.go:61.38,63.3 1 0 -github.com/muety/wakapi/services/alias.go:67.113,68.38 1 0 -github.com/muety/wakapi/services/alias.go:71.2,71.39 1 0 -github.com/muety/wakapi/services/alias.go:68.38,70.3 1 0 -github.com/muety/wakapi/services/alias.go:74.108,75.32 1 1 -github.com/muety/wakapi/services/alias.go:79.2,79.49 1 1 -github.com/muety/wakapi/services/alias.go:87.2,87.19 1 1 -github.com/muety/wakapi/services/alias.go:75.32,77.3 1 1 -github.com/muety/wakapi/services/alias.go:79.49,80.47 1 1 -github.com/muety/wakapi/services/alias.go:80.47,81.49 1 1 -github.com/muety/wakapi/services/alias.go:81.49,83.5 1 1 -github.com/muety/wakapi/services/alias.go:90.77,92.16 2 0 -github.com/muety/wakapi/services/alias.go:96.2,100.20 3 0 -github.com/muety/wakapi/services/alias.go:92.16,94.3 1 0 -github.com/muety/wakapi/services/alias.go:103.60,104.24 1 0 -github.com/muety/wakapi/services/alias.go:107.2,110.16 2 0 -github.com/muety/wakapi/services/alias.go:114.2,116.12 2 0 -github.com/muety/wakapi/services/alias.go:104.24,106.3 1 0 -github.com/muety/wakapi/services/alias.go:110.16,112.3 1 0 -github.com/muety/wakapi/services/alias.go:119.69,123.28 3 0 -github.com/muety/wakapi/services/alias.go:131.2,134.16 2 0 -github.com/muety/wakapi/services/alias.go:140.2,140.31 1 0 -github.com/muety/wakapi/services/alias.go:144.2,144.12 1 0 -github.com/muety/wakapi/services/alias.go:123.28,124.21 1 0 -github.com/muety/wakapi/services/alias.go:127.3,128.16 2 0 -github.com/muety/wakapi/services/alias.go:124.21,126.4 1 0 -github.com/muety/wakapi/services/alias.go:134.16,135.29 1 0 -github.com/muety/wakapi/services/alias.go:135.29,137.4 1 0 -github.com/muety/wakapi/services/alias.go:140.31,142.3 1 0 -github.com/muety/wakapi/services/alias.go:147.74,148.14 1 0 -github.com/muety/wakapi/services/alias.go:148.14,149.57 1 0 -github.com/muety/wakapi/services/alias.go:149.57,153.4 3 0 -github.com/muety/wakapi/services/alias.go:154.8,155.57 1 0 -github.com/muety/wakapi/services/alias.go:155.57,157.48 2 0 -github.com/muety/wakapi/services/alias.go:162.4,162.52 1 0 -github.com/muety/wakapi/services/alias.go:157.48,158.26 1 0 -github.com/muety/wakapi/services/alias.go:158.26,160.6 1 0 -github.com/muety/wakapi/services/alias.go:167.116,168.32 1 0 -github.com/muety/wakapi/services/alias.go:171.2,171.49 1 0 -github.com/muety/wakapi/services/alias.go:168.32,170.3 1 0 -github.com/muety/wakapi/services/alias.go:171.49,173.47 2 0 -github.com/muety/wakapi/services/alias.go:178.3,178.30 1 0 -github.com/muety/wakapi/services/alias.go:173.47,174.16 1 0 -github.com/muety/wakapi/services/alias.go:174.16,176.5 1 0 -github.com/muety/wakapi/services/alias.go:179.8,181.3 1 0 -github.com/muety/wakapi/services/report.go:30.122,44.33 4 0 -github.com/muety/wakapi/services/report.go:50.2,50.12 1 0 -github.com/muety/wakapi/services/report.go:44.33,45.31 1 0 -github.com/muety/wakapi/services/report.go:45.31,47.4 1 0 -github.com/muety/wakapi/services/report.go:53.38,57.16 3 0 -github.com/muety/wakapi/services/report.go:61.2,62.26 2 0 -github.com/muety/wakapi/services/report.go:57.16,59.3 1 0 -github.com/muety/wakapi/services/report.go:62.26,64.3 1 0 -github.com/muety/wakapi/services/report.go:69.61,74.22 3 0 -github.com/muety/wakapi/services/report.go:81.2,81.65 1 0 -github.com/muety/wakapi/services/report.go:98.2,98.24 1 0 -github.com/muety/wakapi/services/report.go:74.22,78.3 3 0 -github.com/muety/wakapi/services/report.go:81.65,91.47 3 0 -github.com/muety/wakapi/services/report.go:91.47,93.4 1 0 -github.com/muety/wakapi/services/report.go:93.9,95.4 1 0 -github.com/muety/wakapi/services/report.go:101.80,102.22 1 0 -github.com/muety/wakapi/services/report.go:107.2,107.29 1 0 -github.com/muety/wakapi/services/report.go:112.2,116.16 4 0 -github.com/muety/wakapi/services/report.go:121.2,128.65 2 0 -github.com/muety/wakapi/services/report.go:133.2,134.12 2 0 -github.com/muety/wakapi/services/report.go:102.22,105.3 2 0 -github.com/muety/wakapi/services/report.go:107.29,110.3 2 0 -github.com/muety/wakapi/services/report.go:116.16,119.3 2 0 -github.com/muety/wakapi/services/report.go:128.65,131.3 2 0 -github.com/muety/wakapi/services/report.go:137.63,138.41 1 0 -github.com/muety/wakapi/services/report.go:145.2,145.12 1 0 -github.com/muety/wakapi/services/report.go:138.41,139.30 1 0 -github.com/muety/wakapi/services/report.go:139.30,140.16 1 0 -github.com/muety/wakapi/services/report.go:140.16,142.5 1 0 -github.com/muety/wakapi/services/user.go:26.99,36.33 3 0 -github.com/muety/wakapi/services/user.go:57.2,57.12 1 0 -github.com/muety/wakapi/services/user.go:36.33,37.31 1 0 -github.com/muety/wakapi/services/user.go:37.31,43.73 4 0 -github.com/muety/wakapi/services/user.go:47.4,47.24 1 0 -github.com/muety/wakapi/services/user.go:43.73,45.5 1 0 -github.com/muety/wakapi/services/user.go:47.24,48.80 1 0 -github.com/muety/wakapi/services/user.go:48.80,50.6 1 0 -github.com/muety/wakapi/services/user.go:50.11,52.6 1 0 -github.com/muety/wakapi/services/user.go:60.74,61.40 1 0 -github.com/muety/wakapi/services/user.go:65.2,66.16 2 0 -github.com/muety/wakapi/services/user.go:70.2,71.15 2 0 -github.com/muety/wakapi/services/user.go:61.40,63.3 1 0 -github.com/muety/wakapi/services/user.go:66.16,68.3 1 0 -github.com/muety/wakapi/services/user.go:74.72,75.37 1 0 -github.com/muety/wakapi/services/user.go:79.2,80.16 2 0 -github.com/muety/wakapi/services/user.go:84.2,85.15 2 0 -github.com/muety/wakapi/services/user.go:75.37,77.3 1 0 -github.com/muety/wakapi/services/user.go:80.16,82.3 1 0 -github.com/muety/wakapi/services/user.go:88.76,90.2 1 0 -github.com/muety/wakapi/services/user.go:92.86,94.2 1 0 -github.com/muety/wakapi/services/user.go:96.58,98.2 1 0 -github.com/muety/wakapi/services/user.go:100.71,102.2 1 0 -github.com/muety/wakapi/services/user.go:104.86,106.16 2 0 -github.com/muety/wakapi/services/user.go:109.2,109.112 1 0 -github.com/muety/wakapi/services/user.go:106.16,108.3 1 0 -github.com/muety/wakapi/services/user.go:109.112,111.3 1 0 -github.com/muety/wakapi/services/user.go:114.86,116.2 1 0 -github.com/muety/wakapi/services/user.go:118.94,120.2 1 0 -github.com/muety/wakapi/services/user.go:122.71,124.12 2 0 -github.com/muety/wakapi/services/user.go:128.2,129.42 2 0 -github.com/muety/wakapi/services/user.go:133.2,134.16 2 0 -github.com/muety/wakapi/services/user.go:138.2,139.21 2 0 -github.com/muety/wakapi/services/user.go:124.12,126.3 1 0 -github.com/muety/wakapi/services/user.go:129.42,131.3 1 0 -github.com/muety/wakapi/services/user.go:134.16,136.3 1 0 -github.com/muety/wakapi/services/user.go:142.48,144.2 1 0 -github.com/muety/wakapi/services/user.go:146.102,156.93 2 0 -github.com/muety/wakapi/services/user.go:162.2,162.38 1 0 -github.com/muety/wakapi/services/user.go:156.93,158.3 1 0 -github.com/muety/wakapi/services/user.go:158.8,160.3 1 0 -github.com/muety/wakapi/services/user.go:165.73,169.2 3 0 -github.com/muety/wakapi/services/user.go:171.78,175.2 3 0 -github.com/muety/wakapi/services/user.go:177.122,180.35 2 0 -github.com/muety/wakapi/services/user.go:186.2,186.35 1 0 -github.com/muety/wakapi/services/user.go:190.2,190.18 1 0 -github.com/muety/wakapi/services/user.go:180.35,181.89 1 0 -github.com/muety/wakapi/services/user.go:181.89,183.4 1 0 -github.com/muety/wakapi/services/user.go:186.35,188.3 1 0 -github.com/muety/wakapi/services/user.go:193.106,196.96 3 0 -github.com/muety/wakapi/services/user.go:201.2,201.68 1 0 -github.com/muety/wakapi/services/user.go:196.96,198.3 1 0 -github.com/muety/wakapi/services/user.go:198.8,200.3 1 0 -github.com/muety/wakapi/services/user.go:204.85,206.2 1 0 -github.com/muety/wakapi/services/user.go:208.57,215.2 4 0 -github.com/muety/wakapi/services/user.go:217.38,219.2 1 0 -github.com/muety/wakapi/services/user.go:221.57,226.2 1 0 +github.com/muety/wakapi/services/leaderboard.go:29.159,42.33 3 0 +github.com/muety/wakapi/services/leaderboard.go:66.2,66.12 1 0 +github.com/muety/wakapi/services/leaderboard.go:42.33,43.31 1 0 +github.com/muety/wakapi/services/leaderboard.go:43.31,49.18 3 0 +github.com/muety/wakapi/services/leaderboard.go:53.4,53.41 1 0 +github.com/muety/wakapi/services/leaderboard.go:49.18,51.5 1 0 +github.com/muety/wakapi/services/leaderboard.go:53.41,56.5 2 0 +github.com/muety/wakapi/services/leaderboard.go:56.10,56.48 1 0 +github.com/muety/wakapi/services/leaderboard.go:56.48,58.64 2 0 +github.com/muety/wakapi/services/leaderboard.go:61.5,61.22 1 0 +github.com/muety/wakapi/services/leaderboard.go:58.64,60.6 1 0 +github.com/muety/wakapi/services/leaderboard.go:69.43,72.21 2 0 +github.com/muety/wakapi/services/leaderboard.go:81.2,81.76 1 0 +github.com/muety/wakapi/services/leaderboard.go:72.21,74.17 2 0 +github.com/muety/wakapi/services/leaderboard.go:78.3,78.91 1 0 +github.com/muety/wakapi/services/leaderboard.go:74.17,77.4 2 0 +github.com/muety/wakapi/services/leaderboard.go:81.76,82.77 1 0 +github.com/muety/wakapi/services/leaderboard.go:82.77,84.4 1 0 +github.com/muety/wakapi/services/leaderboard.go:88.121,91.29 2 0 +github.com/muety/wakapi/services/leaderboard.go:126.2,128.12 3 0 +github.com/muety/wakapi/services/leaderboard.go:91.29,92.83 1 0 +github.com/muety/wakapi/services/leaderboard.go:97.3,98.17 2 0 +github.com/muety/wakapi/services/leaderboard.go:103.3,103.85 1 0 +github.com/muety/wakapi/services/leaderboard.go:108.3,108.25 1 0 +github.com/muety/wakapi/services/leaderboard.go:92.83,94.12 2 0 +github.com/muety/wakapi/services/leaderboard.go:98.17,100.12 2 0 +github.com/muety/wakapi/services/leaderboard.go:103.85,105.12 2 0 +github.com/muety/wakapi/services/leaderboard.go:108.25,110.18 2 0 +github.com/muety/wakapi/services/leaderboard.go:115.4,115.23 1 0 +github.com/muety/wakapi/services/leaderboard.go:119.4,119.60 1 0 +github.com/muety/wakapi/services/leaderboard.go:110.18,112.13 2 0 +github.com/muety/wakapi/services/leaderboard.go:115.23,116.13 1 0 +github.com/muety/wakapi/services/leaderboard.go:119.60,121.13 2 0 +github.com/muety/wakapi/services/leaderboard.go:131.77,134.2 2 0 +github.com/muety/wakapi/services/leaderboard.go:136.60,139.52 2 0 +github.com/muety/wakapi/services/leaderboard.go:143.2,144.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:147.2,147.19 1 0 +github.com/muety/wakapi/services/leaderboard.go:139.52,141.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:144.16,146.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:150.154,152.2 1 0 +github.com/muety/wakapi/services/leaderboard.go:154.144,156.2 1 0 +github.com/muety/wakapi/services/leaderboard.go:158.175,161.52 2 0 +github.com/muety/wakapi/services/leaderboard.go:165.2,166.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:170.2,170.18 1 0 +github.com/muety/wakapi/services/leaderboard.go:183.2,184.19 2 0 +github.com/muety/wakapi/services/leaderboard.go:161.52,163.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:166.16,168.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:170.18,172.17 2 0 +github.com/muety/wakapi/services/leaderboard.go:172.17,174.4 1 0 +github.com/muety/wakapi/services/leaderboard.go:174.9,175.31 1 0 +github.com/muety/wakapi/services/leaderboard.go:175.31,176.40 1 0 +github.com/muety/wakapi/services/leaderboard.go:176.40,178.6 1 0 +github.com/muety/wakapi/services/leaderboard.go:187.165,190.52 2 0 +github.com/muety/wakapi/services/leaderboard.go:194.2,195.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:199.2,199.17 1 0 +github.com/muety/wakapi/services/leaderboard.go:210.2,211.19 2 0 +github.com/muety/wakapi/services/leaderboard.go:190.52,192.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:195.16,197.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:199.17,201.17 2 0 +github.com/muety/wakapi/services/leaderboard.go:201.17,203.4 1 0 +github.com/muety/wakapi/services/leaderboard.go:203.9,204.31 1 0 +github.com/muety/wakapi/services/leaderboard.go:204.31,206.5 1 0 +github.com/muety/wakapi/services/leaderboard.go:214.129,216.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:220.2,221.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:225.2,230.8 1 0 +github.com/muety/wakapi/services/leaderboard.go:216.16,218.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:221.16,223.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:233.151,235.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:239.2,240.16 2 0 +github.com/muety/wakapi/services/leaderboard.go:244.2,247.42 3 0 +github.com/muety/wakapi/services/leaderboard.go:259.2,259.19 1 0 +github.com/muety/wakapi/services/leaderboard.go:235.16,237.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:240.16,242.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:247.42,257.3 2 0 +github.com/muety/wakapi/services/leaderboard.go:262.132,264.47 2 0 +github.com/muety/wakapi/services/leaderboard.go:267.2,267.23 1 0 +github.com/muety/wakapi/services/leaderboard.go:270.2,270.10 1 0 +github.com/muety/wakapi/services/leaderboard.go:264.47,266.3 1 0 +github.com/muety/wakapi/services/leaderboard.go:267.23,269.3 1 0 +github.com/muety/wakapi/services/misc.go:24.126,33.2 1 0 +github.com/muety/wakapi/services/misc.go:35.50,37.91 2 0 +github.com/muety/wakapi/services/misc.go:37.91,39.3 1 0 +github.com/muety/wakapi/services/misc.go:42.42,44.16 2 0 +github.com/muety/wakapi/services/misc.go:48.2,52.26 4 0 +github.com/muety/wakapi/services/misc.go:63.2,63.30 1 0 +github.com/muety/wakapi/services/misc.go:44.16,46.3 1 0 +github.com/muety/wakapi/services/misc.go:52.26,53.46 1 0 +github.com/muety/wakapi/services/misc.go:53.46,56.4 2 0 +github.com/muety/wakapi/services/misc.go:56.18,59.4 2 0 +github.com/muety/wakapi/services/misc.go:63.30,64.54 1 0 +github.com/muety/wakapi/services/misc.go:64.54,68.19 1 0 +github.com/muety/wakapi/services/misc.go:72.4,75.19 1 0 +github.com/muety/wakapi/services/misc.go:68.19,70.5 1 0 +github.com/muety/wakapi/services/misc.go:75.19,77.5 1 0 +github.com/muety/wakapi/services/misc.go:78.9,80.4 1 0 +github.com/muety/wakapi/services/misc.go:84.73,86.16 2 0 +github.com/muety/wakapi/services/misc.go:90.2,90.27 1 0 +github.com/muety/wakapi/services/misc.go:86.16,89.3 2 0 diff --git a/helpers/http.go b/helpers/http.go index 4318832..dee40d4 100644 --- a/helpers/http.go +++ b/helpers/http.go @@ -2,13 +2,23 @@ package helpers import ( "encoding/json" + "errors" "github.com/muety/wakapi/config" - "github.com/muety/wakapi/utils" + "github.com/muety/wakapi/models" "net/http" ) -func ExtractCookieAuth(r *http.Request) (username *string, err error) { - return utils.ExtractCookieAuth(r, config.Get().Security.SecureCookie) +func ExtractCookieAuth(r *http.Request, config *config.Config) (username *string, err error) { + cookie, err := r.Cookie(models.AuthCookieKey) + if err != nil { + return nil, errors.New("missing authentication") + } + + if err := config.Security.SecureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil { + return nil, errors.New("cookie is invalid") + } + + return username, nil } func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) { diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go index 3972b28..06bc41b 100644 --- a/middlewares/authenticate.go +++ b/middlewares/authenticate.go @@ -122,7 +122,7 @@ func (m *AuthenticateMiddleware) tryGetUserByApiKeyQuery(r *http.Request) (*mode } func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.User, error) { - username, err := helpers.ExtractCookieAuth(r) + username, err := helpers.ExtractCookieAuth(r, m.config) if err != nil { return nil, err } diff --git a/utils/auth.go b/utils/auth.go index 28ae791..1c68041 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -3,8 +3,6 @@ package utils import ( "encoding/base64" "errors" - "github.com/gorilla/securecookie" - "github.com/muety/wakapi/models" "golang.org/x/crypto/bcrypt" "net/http" "regexp" @@ -44,19 +42,6 @@ func ExtractBearerAuth(r *http.Request) (key string, err error) { return string(keyBytes), err } -func ExtractCookieAuth(r *http.Request, secureCookie *securecookie.SecureCookie) (username *string, err error) { - cookie, err := r.Cookie(models.AuthCookieKey) - if err != nil { - return nil, errors.New("missing authentication") - } - - if err := secureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil { - return nil, errors.New("cookie is invalid") - } - - return username, nil -} - func CompareBcrypt(wanted, actual, pepper string) bool { plainPassword := []byte(strings.TrimSpace(actual) + pepper) err := bcrypt.CompareHashAndPassword([]byte(wanted), plainPassword) From aab9e98ebde2160263e7c6ee992c5ecba4a25e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 13:46:06 +0100 Subject: [PATCH 11/13] fix: error handling for user counting fix: make user counting thread-safe --- services/misc.go | 23 ++++++++++++++++++----- services/services.go | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/services/misc.go b/services/misc.go index 7f178b7..bb7fb24 100644 --- a/services/misc.go +++ b/services/misc.go @@ -5,6 +5,7 @@ import ( "github.com/muety/artifex" "github.com/muety/wakapi/config" "github.com/muety/wakapi/utils" + "go.uber.org/atomic" "strconv" "sync" "time" @@ -12,6 +13,12 @@ import ( "github.com/muety/wakapi/models" ) +const ( + countUsersEvery = 1 * time.Hour +) + +var countLock = sync.Mutex{} + type MiscService struct { config *config.Config userService IUserService @@ -34,25 +41,31 @@ func NewMiscService(userService IUserService, summaryService ISummaryService, ke func (srv *MiscService) ScheduleCountTotalTime() { logbuch.Info("scheduling total time counting") - if _, err := srv.queueDefault.DispatchEvery(srv.CountTotalTime, 1*time.Hour); err != nil { + if _, err := srv.queueDefault.DispatchEvery(srv.CountTotalTime, countUsersEvery); err != nil { config.Log().Error("failed to schedule user counting jobs, %v", err) } } func (srv *MiscService) CountTotalTime() { + logbuch.Info("counting users total time") + if ok := countLock.TryLock(); !ok { + config.Log().Warn("couldn't acquire lock for counting users total time, job is still pending") + } + defer countLock.Unlock() + users, err := srv.userService.GetAll() if err != nil { config.Log().Error("failed to fetch users for time counting, %v", err) } - var totalTime time.Duration = 0 + var totalTime = atomic.NewDuration(0) var pendingJobs sync.WaitGroup pendingJobs.Add(len(users)) for _, u := range users { if err := srv.queueWorkers.Dispatch(func() { defer pendingJobs.Done() - totalTime += srv.countUserTotalTime(u.ID) + totalTime.Add(srv.countUserTotalTime(u.ID)) }); err != nil { config.Log().Error("failed to enqueue counting job for user '%s'", u.ID) pendingJobs.Done() @@ -61,10 +74,10 @@ func (srv *MiscService) CountTotalTime() { // persist go func(wg *sync.WaitGroup) { - if utils.WaitTimeout(&pendingJobs, 10*time.Minute) { + if !utils.WaitTimeout(&pendingJobs, 2*countUsersEvery) { if err := srv.keyValueService.PutString(&models.KeyStringValue{ Key: config.KeyLatestTotalTime, - Value: totalTime.String(), + Value: totalTime.Load().String(), }); err != nil { config.Log().Error("failed to save total time count: %v", err) } diff --git a/services/services.go b/services/services.go index cbee61a..d3db4ed 100644 --- a/services/services.go +++ b/services/services.go @@ -13,6 +13,7 @@ type IAggregationService interface { type IMiscService interface { ScheduleCountTotalTime() + CountTotalTime() } type IAliasService interface { From a4b89d3a6961fefe75e777120d8f6b244682452e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 14:13:52 +0100 Subject: [PATCH 12/13] fix: concurrency bugs with summary aggregation and user counting --- services/aggregation.go | 5 +++-- services/misc.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/services/aggregation.go b/services/aggregation.go index d2276a4..d39454a 100644 --- a/services/aggregation.go +++ b/services/aggregation.go @@ -91,7 +91,8 @@ func (srv *AggregationService) AggregateSummaries(userIds datastructure.Set[stri jobs := make(chan *AggregationJob) defer close(jobs) go func() { - for job := range jobs { + for jobRef := range jobs { + job := *jobRef if err := srv.queueWorkers.Dispatch(func() { srv.process(job) }); err != nil { @@ -122,7 +123,7 @@ func (srv *AggregationService) AggregateSummaries(userIds datastructure.Set[stri return nil } -func (srv *AggregationService) process(job *AggregationJob) { +func (srv *AggregationService) process(job AggregationJob) { if summary, err := srv.summaryService.Summarize(job.From, job.To, &models.User{ID: job.UserID}, nil); err != nil { config.Log().Error("failed to generate summary (%v, %v, %s) - %v", job.From, job.To, job.UserID, err) } else { diff --git a/services/misc.go b/services/misc.go index bb7fb24..9ad8e54 100644 --- a/services/misc.go +++ b/services/misc.go @@ -63,11 +63,12 @@ func (srv *MiscService) CountTotalTime() { pendingJobs.Add(len(users)) for _, u := range users { + user := *u if err := srv.queueWorkers.Dispatch(func() { defer pendingJobs.Done() - totalTime.Add(srv.countUserTotalTime(u.ID)) + totalTime.Add(srv.countUserTotalTime(user.ID)) }); err != nil { - config.Log().Error("failed to enqueue counting job for user '%s'", u.ID) + config.Log().Error("failed to enqueue counting job for user '%s'", user.ID) pendingJobs.Done() } } From 0e5c5a56d2d816dd5b6e65adc05fadc962991add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Thu, 1 Dec 2022 15:31:19 +0100 Subject: [PATCH 13/13] chore: dependency upgrades --- config/jobqueue.go | 2 +- go.mod | 12 +++---- go.sum | 68 +++++++++++------------------------- services/aggregation.go | 2 +- services/imports/wakatime.go | 2 +- services/leaderboard.go | 2 +- services/misc.go | 2 +- services/report.go | 2 +- 8 files changed, 33 insertions(+), 59 deletions(-) diff --git a/config/jobqueue.go b/config/jobqueue.go index 2e82741..d6c2f94 100644 --- a/config/jobqueue.go +++ b/config/jobqueue.go @@ -3,7 +3,7 @@ package config import ( "fmt" "github.com/emvi/logbuch" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "math" "runtime" ) diff --git a/go.mod b/go.mod index 4e0e256..808d77e 100644 --- a/go.mod +++ b/go.mod @@ -19,21 +19,21 @@ require ( github.com/leandro-lugaresi/hub v1.1.1 github.com/lpar/gzipped/v2 v2.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 - github.com/muety/artifex v0.0.0-20221120093027-024690fdd028 + github.com/muety/artifex/v2 v2.0.1-0.20221201142708-74e7d3f6feaf github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e github.com/patrickmn/go-cache v2.1.0+incompatible github.com/robfig/cron/v3 v3.0.1 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.8.0 github.com/swaggo/http-swagger v1.3.3 - github.com/swaggo/swag v1.8.7 + github.com/swaggo/swag v1.8.8 go.uber.org/atomic v1.10.0 golang.org/x/crypto v0.3.0 golang.org/x/sync v0.1.0 gorm.io/driver/mysql v1.4.4 gorm.io/driver/postgres v1.4.5 gorm.io/driver/sqlite v1.4.3 - gorm.io/gorm v1.24.1 + gorm.io/gorm v1.24.2 ) require ( @@ -41,7 +41,7 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/glebarez/go-sqlite v1.19.2 // indirect + github.com/glebarez/go-sqlite v1.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.7 // indirect @@ -75,8 +75,8 @@ require ( golang.org/x/tools v0.3.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.21.4 // indirect + modernc.org/libc v1.21.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.4.0 // indirect - modernc.org/sqlite v1.19.4 // indirect + modernc.org/sqlite v1.20.0 // indirect ) diff --git a/go.sum b/go.sum index 7a85ae6..c0fe5f2 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,15 @@ codeberg.org/Codeberg/avatars v1.0.0 h1:MRx5QxuT/oVCcPvC5rXwgwWKD7hc6J0GnZ0Kl67lYEM= codeberg.org/Codeberg/avatars v1.0.0/go.mod h1:ML/htpPRb3+owhkm4+qG2ZrXnk5WXaQLASOZ5GLCPi8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -18,8 +19,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/duke-git/lancet/v2 v2.1.6 h1:zRWZkK3IAoGnzEonbrkmUP2NyHqtH9qIlW0AaSQrzmY= -github.com/duke-git/lancet/v2 v2.1.6/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA= github.com/duke-git/lancet/v2 v2.1.10 h1:q6YKhbYg6KChBS+T41e/IhK+sTDPVk2wRhWLTevCeuY= github.com/duke-git/lancet/v2 v2.1.10/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -33,14 +32,11 @@ github.com/emvi/logbuch v1.2.0/go.mod h1:hFxe0XQOFl76SkE/f0Pt5oQbXRZtyGa8EroBrrb github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= -github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA= github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= -github.com/glebarez/go-sqlite v1.19.1 h1:o2XhjyR8CQ2m84+bVz10G0cabmG0tY4sIMiCbrcUTrY= github.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M= -github.com/glebarez/go-sqlite v1.19.2 h1:mTtntWN3wk9UNjIf6F7Upqnfq96p+cjhfgCsupUd1hY= -github.com/glebarez/go-sqlite v1.19.2/go.mod h1:DoubC3Kn5X6EBvDa2iaxAdIJqPNmY7M/sOCpfa8fus0= +github.com/glebarez/go-sqlite v1.19.5 h1:krEVjICcImFNi+X81GmEkSe/brhzLL3Csbkb/ihi8sI= +github.com/glebarez/go-sqlite v1.19.5/go.mod h1:IjVxx3ezfL9clKLLSzVgv2sGZe28yIa116YyLTIvp84= github.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk= github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -67,6 +63,7 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -80,7 +77,7 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -101,7 +98,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -177,8 +173,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/muety/artifex v0.0.0-20221120093027-024690fdd028 h1:kUGQw7rRtg7OkkjXqnUvz0Eh9pOVMv9n8B2oKhthdTI= -github.com/muety/artifex v0.0.0-20221120093027-024690fdd028/go.mod h1:ohgA8vHxRH0ErHcJejxICnAp49PK/6ZezEJ69zrx0/A= +github.com/muety/artifex/v2 v2.0.1-0.20221201142708-74e7d3f6feaf h1:zd7IU9rxVMl2FBwSwiWCUh6s0TkPKgOU6GyVBciNdlo= +github.com/muety/artifex/v2 v2.0.1-0.20221201142708-74e7d3f6feaf/go.mod h1:eElbcdMwTDc7Wzl7A46IopgkC6a9nV7jOB6Mw8r0waE= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e h1:bR8DQ4ZfItytLJwRlrLOPUHd5z18V6tECwYQFy8W+8g= github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e/go.mod h1:m9BzkaxwU4IfPQi9ko23cmuFltayFe8iS0dlRlnEWiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -223,10 +219,8 @@ github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowN github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc= github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo= -github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= -github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= -github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU= -github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= +github.com/swaggo/swag v1.8.8 h1:/GgJmrJ8/c0z4R4hoEPZ5UeEhVGdvsII4JbVDLbR7Xc= +github.com/swaggo/swag v1.8.8/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= @@ -254,19 +248,14 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -278,15 +267,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -304,11 +289,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= -golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -319,7 +303,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -333,7 +316,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= @@ -356,25 +338,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic= -gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ= gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM= -gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw= -gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/driver/sqlite v1.4.2 h1:F6vYJcmR4Cnh0ErLyoY8JSfabBGyR0epIGuhgHJuNws= -gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= -gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0= +gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= @@ -385,17 +359,18 @@ modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v3 v3.16.12/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= +modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.20.3 h1:BodaDPuUse7taQchAClMmbE/yZp3T2ZBiwCDFyBLEXw= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= +modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= @@ -406,11 +381,10 @@ modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4= modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms= -modernc.org/sqlite v1.19.2/go.mod h1:fEgebDYAGTFJj2c/ukKmnaq/0ZQZg0PSYxRa/bHyCDs= -modernc.org/sqlite v1.19.4 h1:nlPIDqumn6/mSvs7T5C8MNYEuN73sISzPdKtMdURpUI= -modernc.org/sqlite v1.19.4/go.mod h1:x/yZNb3h5+I3zGQSlwIv4REL5eJhiRkUH5MReogAeIc= +modernc.org/sqlite v1.19.5/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw= +modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY= +modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM= diff --git a/services/aggregation.go b/services/aggregation.go index d39454a..0fec34b 100644 --- a/services/aggregation.go +++ b/services/aggregation.go @@ -4,7 +4,7 @@ import ( "errors" datastructure "github.com/duke-git/lancet/v2/datastructure/set" "github.com/emvi/logbuch" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "github.com/muety/wakapi/config" "sync" "time" diff --git a/services/imports/wakatime.go b/services/imports/wakatime.go index 50a318b..626452f 100644 --- a/services/imports/wakatime.go +++ b/services/imports/wakatime.go @@ -7,7 +7,7 @@ import ( "errors" "fmt" "github.com/duke-git/lancet/v2/datetime" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "github.com/muety/wakapi/utils" "net/http" "strings" diff --git a/services/leaderboard.go b/services/leaderboard.go index a2e1015..ec74c58 100644 --- a/services/leaderboard.go +++ b/services/leaderboard.go @@ -3,7 +3,7 @@ package services import ( "github.com/emvi/logbuch" "github.com/leandro-lugaresi/hub" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "github.com/muety/wakapi/config" "github.com/muety/wakapi/helpers" "github.com/muety/wakapi/models" diff --git a/services/misc.go b/services/misc.go index 9ad8e54..e148a11 100644 --- a/services/misc.go +++ b/services/misc.go @@ -2,7 +2,7 @@ package services import ( "github.com/emvi/logbuch" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "github.com/muety/wakapi/config" "github.com/muety/wakapi/utils" "go.uber.org/atomic" diff --git a/services/report.go b/services/report.go index fbc21d3..3702b77 100644 --- a/services/report.go +++ b/services/report.go @@ -4,7 +4,7 @@ import ( "github.com/duke-git/lancet/v2/slice" "github.com/emvi/logbuch" "github.com/leandro-lugaresi/hub" - "github.com/muety/artifex" + "github.com/muety/artifex/v2" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" "math/rand"