From fd9e2acdf17f894e0619ade554571fe61141ae9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Fri, 5 Feb 2021 18:47:28 +0100
Subject: [PATCH 01/15] feat: wakatime data import (resolve #87)
---
config/config.go | 20 ++-
go.mod | 1 +
main.go | 2 +-
middlewares/custom/wakatime.go | 2 +-
models/compat/wakatime/v1/all_time.go | 6 +-
models/compat/wakatime/v1/heartbeat.go | 25 +++
models/compat/wakatime/v1/summaries.go | 66 +++----
models/compat/wakatime/v1/user_agent.go | 12 ++
models/heartbeat.go | 7 +-
repositories/heartbeart.go | 11 ++
repositories/repositories.go | 1 +
routes/settings.go | 108 +++++++++--
services/heartbeat.go | 8 +
services/imports/importers.go | 7 +
services/imports/wakatime.go | 229 ++++++++++++++++++++++++
services/key_value.go | 11 ++
services/services.go | 3 +
version.txt | 2 +-
views/settings.tpl.html | 25 ++-
19 files changed, 483 insertions(+), 63 deletions(-)
create mode 100644 models/compat/wakatime/v1/heartbeat.go
create mode 100644 models/compat/wakatime/v1/user_agent.go
create mode 100644 services/imports/importers.go
create mode 100644 services/imports/wakatime.go
diff --git a/config/config.go b/config/config.go
index daeac5f..05bcda5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -29,22 +29,28 @@ const (
KeyLatestTotalTime = "latest_total_time"
KeyLatestTotalUsers = "latest_total_users"
+ KeyLastImportImport = "last_import"
)
const (
- WakatimeApiUrl = "https://wakatime.com/api/v1"
- WakatimeApiHeartbeatsEndpoint = "/users/current/heartbeats.bulk"
- WakatimeApiUserEndpoint = "/users/current"
+ WakatimeApiUrl = "https://wakatime.com/api/v1"
+ WakatimeApiUserUrl = "/users/current"
+ WakatimeApiAllTimeUrl = "/users/current/all_time_since_today"
+ WakatimeApiHeartbeatsUrl = "/users/current/heartbeats"
+ WakatimeApiHeartbeatsBulkUrl = "/users/current/heartbeats.bulk"
+ WakatimeApiUserAgentsUrl = "/users/current/user_agents"
)
var cfg *Config
var cFlag = flag.String("config", defaultConfigPath, "config file location")
type appConfig struct {
- AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
- CountingTime string `yaml:"counting_time" default:"05:15" env:"WAKAPI_COUNTING_TIME"`
- CustomLanguages map[string]string `yaml:"custom_languages"`
- Colors map[string]map[string]string `yaml:"-"`
+ AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
+ CountingTime string `yaml:"counting_time" default:"05:15" env:"WAKAPI_COUNTING_TIME"`
+ ImportBackoffMin int `yaml:"import_backoff_min" default:"5" env:"WAKAPI_IMPORT_BACKOFF_MIN"`
+ ImportBatchSize int `yaml:"import_batch_size" default:"25" env:"WAKAPI_IMPORT_BATCH_SIZE"`
+ CustomLanguages map[string]string `yaml:"custom_languages"`
+ Colors map[string]map[string]string `yaml:"-"`
}
type securityConfig struct {
diff --git a/go.mod b/go.mod
index d427bb1..3c617be 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
github.com/stretchr/testify v1.6.1
go.uber.org/atomic v1.6.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
+ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gorm.io/driver/mysql v1.0.3
diff --git a/main.go b/main.go
index 2bf1826..0d5ce1b 100644
--- a/main.go
+++ b/main.go
@@ -136,7 +136,7 @@ func main() {
// MVC Handlers
summaryHandler := routes.NewSummaryHandler(summaryService, userService)
- settingsHandler := routes.NewSettingsHandler(userService, summaryService, aliasService, aggregationService, languageMappingService)
+ settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, keyValueService)
homeHandler := routes.NewHomeHandler(keyValueService)
loginHandler := routes.NewLoginHandler(userService)
imprintHandler := routes.NewImprintHandler(keyValueService)
diff --git a/middlewares/custom/wakatime.go b/middlewares/custom/wakatime.go
index eaa669d..c7ab5df 100644
--- a/middlewares/custom/wakatime.go
+++ b/middlewares/custom/wakatime.go
@@ -63,7 +63,7 @@ func (m *WakatimeRelayMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
go m.send(
http.MethodPost,
- config.WakatimeApiUrl+config.WakatimeApiHeartbeatsEndpoint,
+ config.WakatimeApiUrl+config.WakatimeApiHeartbeatsBulkUrl,
bytes.NewReader(body),
headers,
)
diff --git a/models/compat/wakatime/v1/all_time.go b/models/compat/wakatime/v1/all_time.go
index 3d87a32..b2444c1 100644
--- a/models/compat/wakatime/v1/all_time.go
+++ b/models/compat/wakatime/v1/all_time.go
@@ -9,10 +9,10 @@ import (
// https://wakatime.com/developers#all_time_since_today
type AllTimeViewModel struct {
- Data *allTimeData `json:"data"`
+ Data *AllTimeData `json:"data"`
}
-type allTimeData struct {
+type AllTimeData struct {
TotalSeconds float32 `json:"total_seconds"` // total number of seconds logged since account created
Text string `json:"text"` // total time logged since account created as human readable string>
IsUpToDate bool `json:"is_up_to_date"` // true if the stats are up to date; when false, a 202 response code is returned and stats will be refreshed soon>
@@ -27,7 +27,7 @@ func NewAllTimeFrom(summary *models.Summary, filters *models.Filters) *AllTimeVi
}
return &AllTimeViewModel{
- Data: &allTimeData{
+ Data: &AllTimeData{
TotalSeconds: float32(total.Seconds()),
Text: utils.FmtWakatimeDuration(total),
IsUpToDate: true,
diff --git a/models/compat/wakatime/v1/heartbeat.go b/models/compat/wakatime/v1/heartbeat.go
new file mode 100644
index 0000000..8461db4
--- /dev/null
+++ b/models/compat/wakatime/v1/heartbeat.go
@@ -0,0 +1,25 @@
+package v1
+
+import "github.com/muety/wakapi/models"
+
+type HeartbeatsViewModel struct {
+ Data []*HeartbeatEntry `json:"data"`
+}
+
+// Incomplete, for now, only the subset of fields is implemented
+// that is actually required for the import
+
+type HeartbeatEntry struct {
+ Id string
+ Branch string
+ Category string
+ Entity string
+ IsWrite bool `json:"is_write"`
+ Language string
+ Project string
+ Time models.CustomTime
+ Type string
+ UserId string `json:"user_id"`
+ MachineNameId string `json:"machine_name_id"`
+ UserAgentId string `json:"user_agent_id"`
+}
diff --git a/models/compat/wakatime/v1/summaries.go b/models/compat/wakatime/v1/summaries.go
index 9383e02..23cf1d3 100644
--- a/models/compat/wakatime/v1/summaries.go
+++ b/models/compat/wakatime/v1/summaries.go
@@ -13,24 +13,24 @@ import (
// https://pastr.de/v/736450
type SummariesViewModel struct {
- Data []*summariesData `json:"data"`
+ Data []*SummariesData `json:"data"`
End time.Time `json:"end"`
Start time.Time `json:"start"`
}
-type summariesData struct {
- Categories []*summariesEntry `json:"categories"`
- Dependencies []*summariesEntry `json:"dependencies"`
- Editors []*summariesEntry `json:"editors"`
- Languages []*summariesEntry `json:"languages"`
- Machines []*summariesEntry `json:"machines"`
- OperatingSystems []*summariesEntry `json:"operating_systems"`
- Projects []*summariesEntry `json:"projects"`
- GrandTotal *summariesGrandTotal `json:"grand_total"`
- Range *summariesRange `json:"range"`
+type SummariesData struct {
+ Categories []*SummariesEntry `json:"categories"`
+ Dependencies []*SummariesEntry `json:"dependencies"`
+ Editors []*SummariesEntry `json:"editors"`
+ Languages []*SummariesEntry `json:"languages"`
+ Machines []*SummariesEntry `json:"machines"`
+ OperatingSystems []*SummariesEntry `json:"operating_systems"`
+ Projects []*SummariesEntry `json:"projects"`
+ GrandTotal *SummariesGrandTotal `json:"grand_total"`
+ Range *SummariesRange `json:"range"`
}
-type summariesEntry struct {
+type SummariesEntry struct {
Digital string `json:"digital"`
Hours int `json:"hours"`
Minutes int `json:"minutes"`
@@ -41,7 +41,7 @@ type summariesEntry struct {
TotalSeconds float64 `json:"total_seconds"`
}
-type summariesGrandTotal struct {
+type SummariesGrandTotal struct {
Digital string `json:"digital"`
Hours int `json:"hours"`
Minutes int `json:"minutes"`
@@ -49,7 +49,7 @@ type summariesGrandTotal struct {
TotalSeconds float64 `json:"total_seconds"`
}
-type summariesRange struct {
+type SummariesRange struct {
Date string `json:"date"`
End time.Time `json:"end"`
Start time.Time `json:"start"`
@@ -58,7 +58,7 @@ type summariesRange struct {
}
func NewSummariesFrom(summaries []*models.Summary, filters *models.Filters) *SummariesViewModel {
- data := make([]*summariesData, len(summaries))
+ data := make([]*SummariesData, len(summaries))
minDate, maxDate := time.Now().Add(1*time.Second), time.Time{}
for i, s := range summaries {
@@ -79,27 +79,27 @@ func NewSummariesFrom(summaries []*models.Summary, filters *models.Filters) *Sum
}
}
-func newDataFrom(s *models.Summary) *summariesData {
+func newDataFrom(s *models.Summary) *SummariesData {
zone, _ := time.Now().Zone()
total := s.TotalTime()
totalHrs, totalMins := int(total.Hours()), int((total - time.Duration(total.Hours())*time.Hour).Minutes())
- data := &summariesData{
- Categories: make([]*summariesEntry, 0),
- Dependencies: make([]*summariesEntry, 0),
- Editors: make([]*summariesEntry, len(s.Editors)),
- Languages: make([]*summariesEntry, len(s.Languages)),
- Machines: make([]*summariesEntry, len(s.Machines)),
- OperatingSystems: make([]*summariesEntry, len(s.OperatingSystems)),
- Projects: make([]*summariesEntry, len(s.Projects)),
- GrandTotal: &summariesGrandTotal{
+ data := &SummariesData{
+ Categories: make([]*SummariesEntry, 0),
+ Dependencies: make([]*SummariesEntry, 0),
+ Editors: make([]*SummariesEntry, len(s.Editors)),
+ Languages: make([]*SummariesEntry, len(s.Languages)),
+ Machines: make([]*SummariesEntry, len(s.Machines)),
+ OperatingSystems: make([]*SummariesEntry, len(s.OperatingSystems)),
+ Projects: make([]*SummariesEntry, len(s.Projects)),
+ GrandTotal: &SummariesGrandTotal{
Digital: fmt.Sprintf("%d:%d", totalHrs, totalMins),
Hours: totalHrs,
Minutes: totalMins,
Text: utils.FmtWakatimeDuration(total),
TotalSeconds: total.Seconds(),
},
- Range: &summariesRange{
+ Range: &SummariesRange{
Date: time.Now().Format(time.RFC3339),
End: s.ToTime.T(),
Start: s.FromTime.T(),
@@ -111,21 +111,21 @@ func newDataFrom(s *models.Summary) *summariesData {
var wg sync.WaitGroup
wg.Add(5)
- go func(data *summariesData) {
+ go func(data *SummariesData) {
defer wg.Done()
for i, e := range s.Projects {
data.Projects[i] = convertEntry(e, s.TotalTimeBy(models.SummaryProject))
}
}(data)
- go func(data *summariesData) {
+ go func(data *SummariesData) {
defer wg.Done()
for i, e := range s.Editors {
data.Editors[i] = convertEntry(e, s.TotalTimeBy(models.SummaryEditor))
}
}(data)
- go func(data *summariesData) {
+ go func(data *SummariesData) {
defer wg.Done()
for i, e := range s.Languages {
data.Languages[i] = convertEntry(e, s.TotalTimeBy(models.SummaryLanguage))
@@ -133,14 +133,14 @@ func newDataFrom(s *models.Summary) *summariesData {
}
}(data)
- go func(data *summariesData) {
+ go func(data *SummariesData) {
defer wg.Done()
for i, e := range s.OperatingSystems {
data.OperatingSystems[i] = convertEntry(e, s.TotalTimeBy(models.SummaryOS))
}
}(data)
- go func(data *summariesData) {
+ go func(data *SummariesData) {
defer wg.Done()
for i, e := range s.Machines {
data.Machines[i] = convertEntry(e, s.TotalTimeBy(models.SummaryMachine))
@@ -151,7 +151,7 @@ func newDataFrom(s *models.Summary) *summariesData {
return data
}
-func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *summariesEntry {
+func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *SummariesEntry {
// this is a workaround, since currently, the total time of a summary item is mistakenly represented in seconds
// TODO: fix some day, while migrating persisted summary items
total := e.Total * time.Second
@@ -163,7 +163,7 @@ func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *summariesEn
percentage = 0
}
- return &summariesEntry{
+ return &SummariesEntry{
Digital: fmt.Sprintf("%d:%d:%d", hrs, mins, secs),
Hours: hrs,
Minutes: mins,
diff --git a/models/compat/wakatime/v1/user_agent.go b/models/compat/wakatime/v1/user_agent.go
new file mode 100644
index 0000000..a73a755
--- /dev/null
+++ b/models/compat/wakatime/v1/user_agent.go
@@ -0,0 +1,12 @@
+package v1
+
+type UserAgentsViewModel struct {
+ Data []*UserAgentEntry `json:"data"`
+}
+
+type UserAgentEntry struct {
+ Id string
+ Editor string
+ Os string
+ Value string
+}
diff --git a/models/heartbeat.go b/models/heartbeat.go
index 9412fbd..dc68c7f 100644
--- a/models/heartbeat.go
+++ b/models/heartbeat.go
@@ -19,11 +19,12 @@ type Heartbeat struct {
Branch string `json:"branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
- Editor string `json:"editor"`
- OperatingSystem string `json:"operating_system"`
- Machine string `json:"machine"`
+ Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime
+ OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime
+ Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"`
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
+ Origin string `json:"-" hash:"ignore"`
languageRegex *regexp.Regexp `hash:"ignore"`
}
diff --git a/repositories/heartbeart.go b/repositories/heartbeart.go
index 9de9f67..631642e 100644
--- a/repositories/heartbeart.go
+++ b/repositories/heartbeart.go
@@ -26,6 +26,17 @@ func (r *HeartbeatRepository) InsertBatch(heartbeats []*models.Heartbeat) error
return nil
}
+func (r *HeartbeatRepository) CountByUser(user *models.User) (int64, error) {
+ var count int64
+ if err := r.db.
+ Model(&models.Heartbeat{}).
+ Where(&models.Heartbeat{UserID: user.ID}).
+ Count(&count).Error; err != nil {
+ return 0, err
+ }
+ return count, nil
+}
+
func (r *HeartbeatRepository) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
var heartbeats []*models.Heartbeat
if err := r.db.
diff --git a/repositories/repositories.go b/repositories/repositories.go
index 2186d88..588e888 100644
--- a/repositories/repositories.go
+++ b/repositories/repositories.go
@@ -17,6 +17,7 @@ type IAliasRepository interface {
type IHeartbeatRepository interface {
InsertBatch([]*models.Heartbeat) error
+ CountByUser(*models.User) (int64, error)
GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error)
GetFirstByUsers() ([]*models.TimeByUser, error)
DeleteBefore(time.Time) error
diff --git a/routes/settings.go b/routes/settings.go
index f46f1d0..f8ccfb8 100644
--- a/routes/settings.go
+++ b/routes/settings.go
@@ -11,6 +11,7 @@ import (
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/models/view"
"github.com/muety/wakapi/services"
+ "github.com/muety/wakapi/services/imports"
"github.com/muety/wakapi/utils"
"net/http"
"strconv"
@@ -21,15 +22,25 @@ type SettingsHandler struct {
config *conf.Config
userSrvc services.IUserService
summarySrvc services.ISummaryService
+ heartbeatSrvc services.IHeartbeatService
aliasSrvc services.IAliasService
aggregationSrvc services.IAggregationService
languageMappingSrvc services.ILanguageMappingService
+ keyValueSrvc services.IKeyValueService
httpClient *http.Client
}
var credentialsDecoder = schema.NewDecoder()
-func NewSettingsHandler(userService services.IUserService, summaryService services.ISummaryService, aliasService services.IAliasService, aggregationService services.IAggregationService, languageMappingService services.ILanguageMappingService) *SettingsHandler {
+func NewSettingsHandler(
+ userService services.IUserService,
+ heartbeatService services.IHeartbeatService,
+ summaryService services.ISummaryService,
+ aliasService services.IAliasService,
+ aggregationService services.IAggregationService,
+ languageMappingService services.ILanguageMappingService,
+ keyValueService services.IKeyValueService,
+) *SettingsHandler {
return &SettingsHandler{
config: conf.Get(),
summarySrvc: summaryService,
@@ -37,6 +48,8 @@ func NewSettingsHandler(userService services.IUserService, summaryService servic
aggregationSrvc: aggregationService,
languageMappingSrvc: languageMappingService,
userSrvc: userService,
+ heartbeatSrvc: heartbeatService,
+ keyValueSrvc: keyValueService,
httpClient: &http.Client{Timeout: 10 * time.Second},
}
}
@@ -88,10 +101,12 @@ func (h *SettingsHandler) PostIndex(w http.ResponseWriter, r *http.Request) {
}
if errorMsg != "" {
+ w.WriteHeader(status)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError(errorMsg))
return
}
if successMsg != "" {
+ w.WriteHeader(status)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess(successMsg))
return
}
@@ -116,6 +131,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
return h.actionToggleBadges
case "toggle_wakatime":
return h.actionSetWakatimeApiKey
+ case "import_wakatime":
+ return h.actionImportWaktime
case "regenerate_summaries":
return h.actionRegenerateSummaries
case "delete_account":
@@ -315,6 +332,66 @@ func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http
return http.StatusOK, "Wakatime API Key updated successfully", ""
}
+func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Request) (int, string, string) {
+ if h.config.IsDev() {
+ loadTemplates()
+ }
+
+ user := r.Context().Value(models.UserKey).(*models.User)
+ if user.WakatimeApiKey == "" {
+ return http.StatusForbidden, "", "not connected to wakatime"
+ }
+
+ kvKey := fmt.Sprintf("%s_%s", conf.KeyLastImportImport, user.ID)
+
+ if !h.config.IsDev() {
+ lastImportKv := h.keyValueSrvc.MustGetString(kvKey)
+ lastImport, _ := time.Parse(time.RFC822, lastImportKv.Value)
+ if time.Now().Sub(lastImport) < time.Duration(h.config.App.ImportBackoffMin)*time.Minute {
+ return http.StatusTooManyRequests,
+ "",
+ fmt.Sprintf("Too many data imports. You are only allowed to request an import every %d minutes.", h.config.App.ImportBackoffMin)
+ }
+ }
+
+ go func(user *models.User) {
+ importer := imports.NewWakatimeHeartbeatImporter(user.WakatimeApiKey)
+
+ countBefore, err := h.heartbeatSrvc.CountByUser(user)
+ if err != nil {
+ println(err)
+ }
+
+ count := 0
+ batch := make([]*models.Heartbeat, 0)
+
+ for hb := range importer.Import(user) {
+ count++
+ batch = append(batch, hb)
+
+ if len(batch) == h.config.App.ImportBatchSize {
+ if err := h.heartbeatSrvc.InsertBatch(batch); err != nil {
+ logbuch.Warn("failed to insert imported heartbeat, already existing? – %v", err)
+ }
+
+ batch = make([]*models.Heartbeat, 0)
+ }
+ }
+
+ countAfter, _ := h.heartbeatSrvc.CountByUser(user)
+ logbuch.Info("downloaded %d heartbeats for user '%s' (%d actually imported)", count, user.ID, countAfter-countBefore)
+
+ h.regenerateSummaries(user)
+ }(user)
+
+ h.keyValueSrvc.PutString(&models.KeyStringValue{
+ Key: kvKey,
+ Value: time.Now().Format(time.RFC822),
+ })
+
+ return http.StatusAccepted, "Import started. This may take a few minutes.", ""
+}
+
func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
@@ -322,16 +399,8 @@ func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *ht
user := r.Context().Value(models.UserKey).(*models.User)
- logbuch.Info("clearing summaries for user '%s'", user.ID)
- if err := h.summarySrvc.DeleteByUser(user.ID); err != nil {
- logbuch.Error("failed to clear summaries: %v", err)
- return http.StatusInternalServerError, "", "failed to delete old summaries"
- }
-
- if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
- logbuch.Error("failed to regenerate summaries: %v", err)
- return http.StatusInternalServerError, "", "failed to generate aggregations"
-
+ if err := h.regenerateSummaries(user); err != nil {
+ return http.StatusInternalServerError, "", "failed to regenerate summaries"
}
return http.StatusOK, "summaries are being regenerated – this may take a few seconds", ""
@@ -368,7 +437,7 @@ func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool {
request, err := http.NewRequest(
http.MethodGet,
- conf.WakatimeApiUrl+conf.WakatimeApiUserEndpoint,
+ conf.WakatimeApiUrl+conf.WakatimeApiUserUrl,
nil,
)
if err != nil {
@@ -385,6 +454,21 @@ func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool {
return true
}
+func (h *SettingsHandler) regenerateSummaries(user *models.User) error {
+ logbuch.Info("clearing summaries for user '%s'", user.ID)
+ if err := h.summarySrvc.DeleteByUser(user.ID); err != nil {
+ logbuch.Error("failed to clear summaries: %v", err)
+ return err
+ }
+
+ if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
+ logbuch.Error("failed to regenerate summaries: %v", err)
+ return err
+ }
+
+ return nil
+}
+
func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel {
user := r.Context().Value(models.UserKey).(*models.User)
mappings, _ := h.languageMappingSrvc.GetByUser(user.ID)
diff --git a/services/heartbeat.go b/services/heartbeat.go
index da8dd53..656fe5e 100644
--- a/services/heartbeat.go
+++ b/services/heartbeat.go
@@ -22,10 +22,18 @@ func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, langua
}
}
+func (srv *HeartbeatService) Insert(heartbeat *models.Heartbeat) error {
+ return srv.repository.InsertBatch([]*models.Heartbeat{heartbeat})
+}
+
func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
return srv.repository.InsertBatch(heartbeats)
}
+func (srv *HeartbeatService) CountByUser(user *models.User) (int64, error) {
+ return srv.repository.CountByUser(user)
+}
+
func (srv *HeartbeatService) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
heartbeats, err := srv.repository.GetAllWithin(from, to, user)
if err != nil {
diff --git a/services/imports/importers.go b/services/imports/importers.go
new file mode 100644
index 0000000..2316461
--- /dev/null
+++ b/services/imports/importers.go
@@ -0,0 +1,7 @@
+package imports
+
+import "github.com/muety/wakapi/models"
+
+type HeartbeatImporter interface {
+ Import(*models.User) <-chan *models.Heartbeat
+}
diff --git a/services/imports/wakatime.go b/services/imports/wakatime.go
new file mode 100644
index 0000000..1f3c7f2
--- /dev/null
+++ b/services/imports/wakatime.go
@@ -0,0 +1,229 @@
+package imports
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/emvi/logbuch"
+ "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/models"
+ wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
+ "github.com/muety/wakapi/utils"
+ "go.uber.org/atomic"
+ "golang.org/x/sync/semaphore"
+ "net/http"
+ "time"
+)
+
+const (
+ maxWorkers = 6
+)
+
+type WakatimeHeartbeatImporter struct {
+ ApiKey string
+}
+
+func NewWakatimeHeartbeatImporter(apiKey string) *WakatimeHeartbeatImporter {
+ return &WakatimeHeartbeatImporter{
+ ApiKey: apiKey,
+ }
+}
+
+func (w *WakatimeHeartbeatImporter) Import(user *models.User) <-chan *models.Heartbeat {
+ out := make(chan *models.Heartbeat)
+
+ go func(user *models.User, out chan *models.Heartbeat) {
+ startDate, endDate, err := w.fetchRange()
+ if err != nil {
+ logbuch.Error("failed to fetch date range while importing wakatime heartbeats for user '%s' – %v", user.ID, err)
+ return
+ }
+
+ userAgents, err := w.fetchUserAgents()
+ if err != nil {
+ logbuch.Error("failed to fetch user agents while importing wakatime heartbeats for user '%s' – %v", user.ID, err)
+ return
+ }
+
+ days := generateDays(startDate, endDate)
+
+ c := atomic.NewUint32(uint32(len(days)))
+ ctx := context.TODO()
+ sem := semaphore.NewWeighted(maxWorkers)
+
+ for _, d := range days {
+ if err := sem.Acquire(ctx, 1); err != nil {
+ logbuch.Error("failed to acquire semaphore – %v", err)
+ break
+ }
+
+ go func(day time.Time) {
+ defer sem.Release(1)
+
+ d := day.Format("2006-01-02")
+ heartbeats, err := w.fetchHeartbeats(d)
+ if err != nil {
+ logbuch.Error("failed to fetch heartbeats for day '%s' and user '%s' – &v", day, user.ID, err)
+ }
+
+ for _, h := range heartbeats {
+ out <- mapHeartbeat(h, userAgents, user)
+ }
+
+ if c.Dec() == 0 {
+ close(out)
+ }
+ }(d)
+ }
+ }(user, out)
+
+ return out
+}
+
+// https://wakatime.com/api/v1/users/current/heartbeats?date=2021-02-05
+func (w *WakatimeHeartbeatImporter) fetchHeartbeats(day string) ([]*wakatime.HeartbeatEntry, error) {
+ httpClient := &http.Client{Timeout: 10 * time.Second}
+
+ req, err := http.NewRequest(http.MethodGet, config.WakatimeApiUrl+config.WakatimeApiHeartbeatsUrl, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ q := req.URL.Query()
+ q.Add("date", day)
+ req.URL.RawQuery = q.Encode()
+
+ res, err := httpClient.Do(w.withHeaders(req))
+ if err != nil {
+ return nil, err
+ }
+
+ var heartbeatsData wakatime.HeartbeatsViewModel
+ if err := json.NewDecoder(res.Body).Decode(&heartbeatsData); err != nil {
+ return nil, err
+ }
+
+ return heartbeatsData.Data, nil
+}
+
+// https://wakatime.com/api/v1/users/current/all_time_since_today
+func (w *WakatimeHeartbeatImporter) fetchRange() (time.Time, time.Time, error) {
+ httpClient := &http.Client{Timeout: 10 * time.Second}
+
+ notime := time.Time{}
+
+ req, err := http.NewRequest(http.MethodGet, config.WakatimeApiUrl+config.WakatimeApiAllTimeUrl, nil)
+ if err != nil {
+ return notime, notime, err
+ }
+
+ res, err := httpClient.Do(w.withHeaders(req))
+ if err != nil {
+ return notime, notime, err
+ }
+
+ var allTimeData map[string]interface{}
+ if err := json.NewDecoder(res.Body).Decode(&allTimeData); err != nil {
+ return notime, notime, err
+ }
+
+ data := allTimeData["data"].(map[string]interface{})
+ if data == nil {
+ return notime, notime, errors.New("invalid response")
+ }
+
+ dataRange := data["range"].(map[string]interface{})
+ if dataRange == nil {
+ return notime, notime, errors.New("invalid response")
+ }
+
+ startDate, err := time.Parse("2006-01-02", dataRange["start_date"].(string))
+ if err != nil {
+ return notime, notime, err
+ }
+
+ endDate, err := time.Parse("2006-01-02", dataRange["end_date"].(string))
+ if err != nil {
+ return notime, notime, err
+ }
+
+ return startDate, endDate, nil
+}
+
+// https://wakatime.com/api/v1/users/current/user_agents
+func (w *WakatimeHeartbeatImporter) fetchUserAgents() (map[string]*wakatime.UserAgentEntry, error) {
+ httpClient := &http.Client{Timeout: 10 * time.Second}
+
+ req, err := http.NewRequest(http.MethodGet, config.WakatimeApiUrl+config.WakatimeApiUserAgentsUrl, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := httpClient.Do(w.withHeaders(req))
+ if err != nil {
+ return nil, err
+ }
+
+ var userAgentsData wakatime.UserAgentsViewModel
+ if err := json.NewDecoder(res.Body).Decode(&userAgentsData); err != nil {
+ return nil, err
+ }
+
+ userAgents := make(map[string]*wakatime.UserAgentEntry)
+ for _, ua := range userAgentsData.Data {
+ userAgents[ua.Id] = ua
+ }
+
+ return userAgents, nil
+}
+
+func (w *WakatimeHeartbeatImporter) withHeaders(req *http.Request) *http.Request {
+ req.Header.Set("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(w.ApiKey))))
+ return req
+}
+
+func mapHeartbeat(
+ entry *wakatime.HeartbeatEntry,
+ userAgents map[string]*wakatime.UserAgentEntry,
+ user *models.User,
+) *models.Heartbeat {
+ ua := userAgents[entry.UserAgentId]
+ if ua == nil {
+ ua = &wakatime.UserAgentEntry{
+ Editor: "unknown",
+ Os: "unknown",
+ }
+ }
+
+ return (&models.Heartbeat{
+ User: user,
+ UserID: user.ID,
+ Entity: entry.Entity,
+ Type: entry.Type,
+ Category: entry.Category,
+ Project: entry.Project,
+ Branch: entry.Branch,
+ Language: entry.Language,
+ IsWrite: entry.IsWrite,
+ Editor: ua.Editor,
+ OperatingSystem: ua.Os,
+ Machine: entry.MachineNameId, // TODO
+ Time: entry.Time,
+ Origin: fmt.Sprintf("wt@%s", entry.Id),
+ }).Hashed()
+}
+
+func generateDays(from, to time.Time) []time.Time {
+ days := make([]time.Time, 0)
+
+ from = utils.StartOfDay(from)
+ to = utils.StartOfDay(to.Add(24 * time.Hour))
+
+ for d := from; d.Before(to); d = d.Add(24 * time.Hour) {
+ days = append(days, d)
+ }
+
+ return days
+}
diff --git a/services/key_value.go b/services/key_value.go
index 11c7e67..deef6bb 100644
--- a/services/key_value.go
+++ b/services/key_value.go
@@ -22,6 +22,17 @@ func (srv *KeyValueService) GetString(key string) (*models.KeyStringValue, error
return srv.repository.GetString(key)
}
+func (srv *KeyValueService) MustGetString(key string) *models.KeyStringValue {
+ kv, err := srv.repository.GetString(key)
+ if err != nil {
+ return &models.KeyStringValue{
+ Key: key,
+ Value: "",
+ }
+ }
+ return kv
+}
+
func (srv *KeyValueService) PutString(kv *models.KeyStringValue) error {
return srv.repository.PutString(kv)
}
diff --git a/services/services.go b/services/services.go
index 1b638ea..02d73bc 100644
--- a/services/services.go
+++ b/services/services.go
@@ -26,7 +26,9 @@ type IAliasService interface {
}
type IHeartbeatService interface {
+ Insert(*models.Heartbeat) error
InsertBatch([]*models.Heartbeat) error
+ CountByUser(*models.User) (int64, error)
GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error)
GetFirstByUsers() ([]*models.TimeByUser, error)
DeleteBefore(time.Time) error
@@ -34,6 +36,7 @@ type IHeartbeatService interface {
type IKeyValueService interface {
GetString(string) (*models.KeyStringValue, error)
+ MustGetString(string) *models.KeyStringValue
PutString(*models.KeyStringValue) error
DeleteString(string) error
}
diff --git a/version.txt b/version.txt
index e81ed8b..bfbadb3 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.22.6
\ No newline at end of file
+1.23.0
\ No newline at end of file
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 3187569..9085bea 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -356,17 +356,32 @@
{{ else }}
Disconnect
+ class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm"
+ style="width: 130px">Disconnect
{{ end }}
+
+ {{ if .User.WakatimeApiKey }}
+
+
+ ⤵ Import Data
+
+
+ {{ end }}
+
+
+
👉 Please note:
When enabling this feature, the operators of this server will, in theory (!), have unlimited access to your data stored in WakaTime. If you are concerned about your privacy, please do not enable this integration or wait for OAuth 2 authentication (#94 ) to be implemented.
+ class="underline" target="_blank" href="https://github.com/muety/wakapi/issues/94"
+ rel="noopener noreferrer">#94) to be implemented.
@@ -455,6 +470,12 @@
formDelete.submit()
}
})
+
+ const btnImportWakatime = document.querySelector('#btn-import-wakatime')
+ const formImportWakatime = document.querySelector('#form-import-wakatime')
+ btnImportWakatime.addEventListener('click', () => {
+ formImportWakatime.submit()
+ })
{{ template "footer.tpl.html" . }}
From e1906abd38df38b94b2a44275364679666f8e04a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Fri, 5 Feb 2021 18:52:21 +0100
Subject: [PATCH 02/15] fix: tests
---
coverage/coverage.out | 671 +++++++++++++++++++------------------
mocks/heartbeat_service.go | 10 +
2 files changed, 348 insertions(+), 333 deletions(-)
diff --git a/coverage/coverage.out b/coverage/coverage.out
index 6f46aca..a364713 100644
--- a/coverage/coverage.out
+++ b/coverage/coverage.out
@@ -1,81 +1,5 @@
mode: set
-github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
-github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
-github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
-github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
-github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
-github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
-github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
-github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
-github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
-github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
-github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
-github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
-github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
-github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
-github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
-github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
-github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
-github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
-github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
-github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
-github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
-github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
-github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:30.34,32.2 1 1
-github.com/muety/wakapi/models/heartbeat.go:34.65,35.28 1 1
-github.com/muety/wakapi/models/heartbeat.go:38.2,39.45 2 1
-github.com/muety/wakapi/models/heartbeat.go:42.2,43.44 2 1
-github.com/muety/wakapi/models/heartbeat.go:46.2,46.42 1 1
-github.com/muety/wakapi/models/heartbeat.go:35.28,37.3 1 1
-github.com/muety/wakapi/models/heartbeat.go:39.45,41.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:43.44,45.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:49.50,50.11 1 1
-github.com/muety/wakapi/models/heartbeat.go:63.2,63.15 1 1
-github.com/muety/wakapi/models/heartbeat.go:67.2,67.12 1 1
-github.com/muety/wakapi/models/heartbeat.go:51.22,52.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:53.21,54.17 1 1
-github.com/muety/wakapi/models/heartbeat.go:55.23,56.19 1 1
-github.com/muety/wakapi/models/heartbeat.go:57.17,58.26 1 1
-github.com/muety/wakapi/models/heartbeat.go:59.22,60.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:63.15,65.3 1 1
-github.com/muety/wakapi/models/heartbeat.go:70.37,86.2 1 0
-github.com/muety/wakapi/models/heartbeat.go:94.41,96.16 2 0
-github.com/muety/wakapi/models/heartbeat.go:99.2,100.10 2 0
-github.com/muety/wakapi/models/heartbeat.go:96.16,98.3 1 0
-github.com/muety/wakapi/models/user.go:35.43,38.2 1 0
-github.com/muety/wakapi/models/user.go:40.33,44.2 1 0
-github.com/muety/wakapi/models/user.go:46.45,48.2 1 0
-github.com/muety/wakapi/models/user.go:50.45,52.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/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/models.go:3.14,5.2 0 1
-github.com/muety/wakapi/models/shared.go:34.52,37.16 3 0
-github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
-github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
-github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
-github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
-github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
-github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
-github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
-github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
-github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
-github.com/muety/wakapi/models/shared.go:74.45,76.2 1 0
-github.com/muety/wakapi/models/shared.go:78.51,81.2 2 0
-github.com/muety/wakapi/models/shared.go:83.37,86.2 2 0
-github.com/muety/wakapi/models/shared.go:88.35,90.2 1 0
-github.com/muety/wakapi/models/shared.go:92.34,94.2 1 0
github.com/muety/wakapi/models/summary.go:41.27,45.2 1 0
github.com/muety/wakapi/models/summary.go:97.29,99.2 1 1
github.com/muety/wakapi/models/summary.go:101.37,108.2 6 1
@@ -126,6 +50,187 @@ github.com/muety/wakapi/models/summary.go:236.11,244.6 1 1
github.com/muety/wakapi/models/summary.go:261.33,263.2 1 1
github.com/muety/wakapi/models/summary.go:265.43,267.2 1 1
github.com/muety/wakapi/models/summary.go:269.38,271.2 1 1
+github.com/muety/wakapi/models/user.go:35.43,38.2 1 0
+github.com/muety/wakapi/models/user.go:40.33,44.2 1 0
+github.com/muety/wakapi/models/user.go:46.45,48.2 1 0
+github.com/muety/wakapi/models/user.go:50.45,52.2 1 0
+github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
+github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
+github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
+github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
+github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
+github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
+github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
+github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
+github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
+github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
+github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
+github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
+github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
+github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
+github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
+github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
+github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
+github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
+github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
+github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
+github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
+github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
+github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:31.34,33.2 1 1
+github.com/muety/wakapi/models/heartbeat.go:35.65,36.28 1 1
+github.com/muety/wakapi/models/heartbeat.go:39.2,40.45 2 1
+github.com/muety/wakapi/models/heartbeat.go:43.2,44.44 2 1
+github.com/muety/wakapi/models/heartbeat.go:47.2,47.42 1 1
+github.com/muety/wakapi/models/heartbeat.go:36.28,38.3 1 1
+github.com/muety/wakapi/models/heartbeat.go:40.45,42.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:44.44,46.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:50.50,51.11 1 1
+github.com/muety/wakapi/models/heartbeat.go:64.2,64.15 1 1
+github.com/muety/wakapi/models/heartbeat.go:68.2,68.12 1 1
+github.com/muety/wakapi/models/heartbeat.go:52.22,53.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:54.21,55.17 1 1
+github.com/muety/wakapi/models/heartbeat.go:56.23,57.19 1 1
+github.com/muety/wakapi/models/heartbeat.go:58.17,59.26 1 1
+github.com/muety/wakapi/models/heartbeat.go:60.22,61.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:64.15,66.3 1 1
+github.com/muety/wakapi/models/heartbeat.go:71.37,87.2 1 0
+github.com/muety/wakapi/models/heartbeat.go:95.41,97.16 2 0
+github.com/muety/wakapi/models/heartbeat.go:100.2,101.10 2 0
+github.com/muety/wakapi/models/heartbeat.go:97.16,99.3 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/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/shared.go:34.52,37.16 3 0
+github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
+github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
+github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
+github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
+github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
+github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
+github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
+github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
+github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
+github.com/muety/wakapi/models/shared.go:74.45,76.2 1 0
+github.com/muety/wakapi/models/shared.go:78.51,81.2 2 0
+github.com/muety/wakapi/models/shared.go:83.37,86.2 2 0
+github.com/muety/wakapi/models/shared.go:88.35,90.2 1 0
+github.com/muety/wakapi/models/shared.go:92.34,94.2 1 0
+github.com/muety/wakapi/utils/summary.go:10.71,13.18 2 0
+github.com/muety/wakapi/utils/summary.go:49.2,49.22 1 0
+github.com/muety/wakapi/utils/summary.go:14.58,15.24 1 0
+github.com/muety/wakapi/utils/summary.go:16.66,18.22 2 0
+github.com/muety/wakapi/utils/summary.go:19.64,20.23 1 0
+github.com/muety/wakapi/utils/summary.go:21.39,23.21 2 0
+github.com/muety/wakapi/utils/summary.go:24.66,25.24 1 0
+github.com/muety/wakapi/utils/summary.go:26.40,28.22 2 0
+github.com/muety/wakapi/utils/summary.go:29.31,30.23 1 0
+github.com/muety/wakapi/utils/summary.go:31.66,32.42 1 0
+github.com/muety/wakapi/utils/summary.go:33.49,35.40 2 0
+github.com/muety/wakapi/utils/summary.go:36.41,37.43 1 0
+github.com/muety/wakapi/utils/summary.go:38.68,40.42 2 0
+github.com/muety/wakapi/utils/summary.go:41.35,42.43 1 0
+github.com/muety/wakapi/utils/summary.go:43.26,44.21 1 0
+github.com/muety/wakapi/utils/summary.go:45.10,46.39 1 0
+github.com/muety/wakapi/utils/summary.go:52.73,59.56 5 0
+github.com/muety/wakapi/utils/summary.go:73.2,80.8 2 0
+github.com/muety/wakapi/utils/summary.go:59.56,61.3 1 0
+github.com/muety/wakapi/utils/summary.go:61.8,63.17 2 0
+github.com/muety/wakapi/utils/summary.go:67.3,68.17 2 0
+github.com/muety/wakapi/utils/summary.go:63.17,65.4 1 0
+github.com/muety/wakapi/utils/summary.go:68.17,70.4 1 0
+github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
+github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
+github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
+github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
+github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
+github.com/muety/wakapi/utils/template.go:17.30,19.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.54 2 0
+github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
+github.com/muety/wakapi/utils/auth.go:39.54,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/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/common.go:9.48,11.2 1 0
+github.com/muety/wakapi/utils/common.go:13.40,15.2 1 0
+github.com/muety/wakapi/utils/common.go:17.45,19.2 1 0
+github.com/muety/wakapi/utils/common.go:21.24,23.2 1 0
+github.com/muety/wakapi/utils/common.go:25.56,28.45 3 1
+github.com/muety/wakapi/utils/common.go:31.2,31.40 1 1
+github.com/muety/wakapi/utils/common.go:28.45,30.3 1 1
+github.com/muety/wakapi/utils/date.go:8.31,10.2 1 0
+github.com/muety/wakapi/utils/date.go:12.43,14.2 1 0
+github.com/muety/wakapi/utils/date.go:16.30,20.2 3 0
+github.com/muety/wakapi/utils/date.go:22.31,25.2 2 0
+github.com/muety/wakapi/utils/date.go:27.30,30.2 2 0
+github.com/muety/wakapi/utils/date.go:32.67,35.33 2 0
+github.com/muety/wakapi/utils/date.go:44.2,44.18 1 0
+github.com/muety/wakapi/utils/date.go:35.33,37.19 2 0
+github.com/muety/wakapi/utils/date.go:40.3,41.10 2 0
+github.com/muety/wakapi/utils/date.go:37.19,39.4 1 0
+github.com/muety/wakapi/utils/date.go:47.50,53.2 5 0
+github.com/muety/wakapi/utils/date.go:56.79,59.36 3 0
+github.com/muety/wakapi/utils/date.go:63.2,63.21 1 0
+github.com/muety/wakapi/utils/date.go:67.2,67.21 1 0
+github.com/muety/wakapi/utils/date.go:71.2,71.13 1 0
+github.com/muety/wakapi/utils/date.go:59.36,62.3 2 0
+github.com/muety/wakapi/utils/date.go:63.21,66.3 2 0
+github.com/muety/wakapi/utils/date.go:67.21,70.3 2 0
+github.com/muety/wakapi/utils/http.go:9.73,12.58 3 0
+github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
+github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
+github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
+github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
+github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
+github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
+github.com/muety/wakapi/middlewares/logging.go:17.79,18.43 1 0
+github.com/muety/wakapi/middlewares/logging.go:18.43,23.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:26.80,44.2 6 0
+github.com/muety/wakapi/middlewares/logging.go:46.41,48.14 2 0
+github.com/muety/wakapi/middlewares/logging.go:51.2,51.14 1 0
+github.com/muety/wakapi/middlewares/logging.go:54.2,54.11 1 0
+github.com/muety/wakapi/middlewares/logging.go:48.14,50.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:51.14,53.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:85.52,87.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:99.45,100.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:100.20,104.3 3 0
+github.com/muety/wakapi/middlewares/logging.go:106.54,109.18 3 0
+github.com/muety/wakapi/middlewares/logging.go:116.2,117.15 2 0
+github.com/muety/wakapi/middlewares/logging.go:109.18,112.17 2 0
+github.com/muety/wakapi/middlewares/logging.go:112.17,114.4 1 0
+github.com/muety/wakapi/middlewares/logging.go:119.42,120.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:120.20,122.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:124.36,126.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:127.42,129.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:130.40,132.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:133.52,135.2 1 0
github.com/muety/wakapi/middlewares/authenticate.go:20.116,26.2 1 1
github.com/muety/wakapi/middlewares/authenticate.go:28.71,29.71 1 0
github.com/muety/wakapi/middlewares/authenticate.go:29.71,31.3 1 0
@@ -150,133 +255,91 @@ github.com/muety/wakapi/middlewares/authenticate.go:84.2,85.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.18 1 0
github.com/muety/wakapi/middlewares/authenticate.go:80.16,82.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:85.16,87.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:17.79,18.43 1 0
-github.com/muety/wakapi/middlewares/logging.go:18.43,23.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:26.80,44.2 6 0
-github.com/muety/wakapi/middlewares/logging.go:46.41,48.14 2 0
-github.com/muety/wakapi/middlewares/logging.go:51.2,51.14 1 0
-github.com/muety/wakapi/middlewares/logging.go:54.2,54.11 1 0
-github.com/muety/wakapi/middlewares/logging.go:48.14,50.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:51.14,53.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:85.52,87.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:99.45,100.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:100.20,104.3 3 0
-github.com/muety/wakapi/middlewares/logging.go:106.54,109.18 3 0
-github.com/muety/wakapi/middlewares/logging.go:116.2,117.15 2 0
-github.com/muety/wakapi/middlewares/logging.go:109.18,112.17 2 0
-github.com/muety/wakapi/middlewares/logging.go:112.17,114.4 1 0
-github.com/muety/wakapi/middlewares/logging.go:119.42,120.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:120.20,122.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:124.36,126.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:127.42,129.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:130.40,132.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:133.52,135.2 1 0
+github.com/muety/wakapi/config/config.go:94.70,96.2 1 0
+github.com/muety/wakapi/config/config.go:98.65,100.2 1 0
+github.com/muety/wakapi/config/config.go:102.82,112.2 1 0
+github.com/muety/wakapi/config/config.go:114.31,116.2 1 0
+github.com/muety/wakapi/config/config.go:118.32,120.2 1 0
+github.com/muety/wakapi/config/config.go:122.74,123.19 1 0
+github.com/muety/wakapi/config/config.go:124.10,125.34 1 0
+github.com/muety/wakapi/config/config.go:125.34,134.4 8 0
+github.com/muety/wakapi/config/config.go:138.73,139.33 1 0
+github.com/muety/wakapi/config/config.go:139.33,147.17 5 0
+github.com/muety/wakapi/config/config.go:151.3,152.13 2 0
+github.com/muety/wakapi/config/config.go:147.17,149.4 1 0
+github.com/muety/wakapi/config/config.go:156.50,157.19 1 0
+github.com/muety/wakapi/config/config.go:170.2,170.12 1 0
+github.com/muety/wakapi/config/config.go:158.23,162.5 1 0
+github.com/muety/wakapi/config/config.go:163.26,166.5 1 0
+github.com/muety/wakapi/config/config.go:167.24,168.48 1 0
+github.com/muety/wakapi/config/config.go:173.53,183.2 1 1
+github.com/muety/wakapi/config/config.go:185.56,187.16 2 1
+github.com/muety/wakapi/config/config.go:191.2,198.3 1 1
+github.com/muety/wakapi/config/config.go:187.16,189.3 1 0
+github.com/muety/wakapi/config/config.go:201.54,203.2 1 1
+github.com/muety/wakapi/config/config.go:205.60,207.2 1 0
+github.com/muety/wakapi/config/config.go:209.59,211.2 1 0
+github.com/muety/wakapi/config/config.go:213.57,215.2 1 0
+github.com/muety/wakapi/config/config.go:217.53,219.2 1 0
+github.com/muety/wakapi/config/config.go:221.29,223.2 1 1
+github.com/muety/wakapi/config/config.go:225.27,227.16 2 0
+github.com/muety/wakapi/config/config.go:230.2,233.16 3 0
+github.com/muety/wakapi/config/config.go:237.2,237.41 1 0
+github.com/muety/wakapi/config/config.go:227.16,229.3 1 0
+github.com/muety/wakapi/config/config.go:233.16,235.3 1 0
+github.com/muety/wakapi/config/config.go:240.48,252.16 3 0
+github.com/muety/wakapi/config/config.go:255.2,257.16 3 0
+github.com/muety/wakapi/config/config.go:261.2,261.55 1 0
+github.com/muety/wakapi/config/config.go:265.2,265.15 1 0
+github.com/muety/wakapi/config/config.go:252.16,254.3 1 0
+github.com/muety/wakapi/config/config.go:257.16,259.3 1 0
+github.com/muety/wakapi/config/config.go:261.55,263.3 1 0
+github.com/muety/wakapi/config/config.go:268.38,269.43 1 0
+github.com/muety/wakapi/config/config.go:272.2,272.15 1 0
+github.com/muety/wakapi/config/config.go:269.43,271.3 1 0
+github.com/muety/wakapi/config/config.go:275.45,276.27 1 0
+github.com/muety/wakapi/config/config.go:279.2,279.15 1 0
+github.com/muety/wakapi/config/config.go:276.27,278.3 1 0
+github.com/muety/wakapi/config/config.go:282.26,284.2 1 0
+github.com/muety/wakapi/config/config.go:286.20,288.2 1 0
+github.com/muety/wakapi/config/config.go:290.21,295.96 3 0
+github.com/muety/wakapi/config/config.go:299.2,307.52 5 0
+github.com/muety/wakapi/config/config.go:311.2,311.47 1 0
+github.com/muety/wakapi/config/config.go:317.2,317.70 1 0
+github.com/muety/wakapi/config/config.go:321.2,321.28 1 0
+github.com/muety/wakapi/config/config.go:325.2,326.14 2 0
+github.com/muety/wakapi/config/config.go:295.96,297.3 1 0
+github.com/muety/wakapi/config/config.go:307.52,309.3 1 0
+github.com/muety/wakapi/config/config.go:311.47,312.14 1 0
+github.com/muety/wakapi/config/config.go:312.14,314.4 1 0
+github.com/muety/wakapi/config/config.go:317.70,319.3 1 0
+github.com/muety/wakapi/config/config.go:321.28,323.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:88.70,90.2 1 0
-github.com/muety/wakapi/config/config.go:92.65,94.2 1 0
-github.com/muety/wakapi/config/config.go:96.82,106.2 1 0
-github.com/muety/wakapi/config/config.go:108.31,110.2 1 0
-github.com/muety/wakapi/config/config.go:112.32,114.2 1 0
-github.com/muety/wakapi/config/config.go:116.74,117.19 1 0
-github.com/muety/wakapi/config/config.go:118.10,119.34 1 0
-github.com/muety/wakapi/config/config.go:119.34,128.4 8 0
-github.com/muety/wakapi/config/config.go:132.73,133.33 1 0
-github.com/muety/wakapi/config/config.go:133.33,141.17 5 0
-github.com/muety/wakapi/config/config.go:145.3,146.13 2 0
-github.com/muety/wakapi/config/config.go:141.17,143.4 1 0
-github.com/muety/wakapi/config/config.go:150.50,151.19 1 0
-github.com/muety/wakapi/config/config.go:164.2,164.12 1 0
-github.com/muety/wakapi/config/config.go:152.23,156.5 1 0
-github.com/muety/wakapi/config/config.go:157.26,160.5 1 0
-github.com/muety/wakapi/config/config.go:161.24,162.48 1 0
-github.com/muety/wakapi/config/config.go:167.53,177.2 1 1
-github.com/muety/wakapi/config/config.go:179.56,181.16 2 1
-github.com/muety/wakapi/config/config.go:185.2,192.3 1 1
-github.com/muety/wakapi/config/config.go:181.16,183.3 1 0
-github.com/muety/wakapi/config/config.go:195.54,197.2 1 1
-github.com/muety/wakapi/config/config.go:199.60,201.2 1 0
-github.com/muety/wakapi/config/config.go:203.59,205.2 1 0
-github.com/muety/wakapi/config/config.go:207.57,209.2 1 0
-github.com/muety/wakapi/config/config.go:211.53,213.2 1 0
-github.com/muety/wakapi/config/config.go:215.29,217.2 1 1
-github.com/muety/wakapi/config/config.go:219.27,221.16 2 0
-github.com/muety/wakapi/config/config.go:224.2,227.16 3 0
-github.com/muety/wakapi/config/config.go:231.2,231.41 1 0
-github.com/muety/wakapi/config/config.go:221.16,223.3 1 0
-github.com/muety/wakapi/config/config.go:227.16,229.3 1 0
-github.com/muety/wakapi/config/config.go:234.48,246.16 3 0
-github.com/muety/wakapi/config/config.go:249.2,251.16 3 0
-github.com/muety/wakapi/config/config.go:255.2,255.55 1 0
-github.com/muety/wakapi/config/config.go:259.2,259.15 1 0
-github.com/muety/wakapi/config/config.go:246.16,248.3 1 0
-github.com/muety/wakapi/config/config.go:251.16,253.3 1 0
-github.com/muety/wakapi/config/config.go:255.55,257.3 1 0
-github.com/muety/wakapi/config/config.go:262.38,263.43 1 0
-github.com/muety/wakapi/config/config.go:266.2,266.15 1 0
-github.com/muety/wakapi/config/config.go:263.43,265.3 1 0
-github.com/muety/wakapi/config/config.go:269.45,270.27 1 0
-github.com/muety/wakapi/config/config.go:273.2,273.15 1 0
-github.com/muety/wakapi/config/config.go:270.27,272.3 1 0
-github.com/muety/wakapi/config/config.go:276.26,278.2 1 0
-github.com/muety/wakapi/config/config.go:280.20,282.2 1 0
-github.com/muety/wakapi/config/config.go:284.21,289.96 3 0
-github.com/muety/wakapi/config/config.go:293.2,301.52 5 0
-github.com/muety/wakapi/config/config.go:305.2,305.47 1 0
-github.com/muety/wakapi/config/config.go:311.2,311.70 1 0
-github.com/muety/wakapi/config/config.go:315.2,315.28 1 0
-github.com/muety/wakapi/config/config.go:319.2,320.14 2 0
-github.com/muety/wakapi/config/config.go:289.96,291.3 1 0
-github.com/muety/wakapi/config/config.go:301.52,303.3 1 0
-github.com/muety/wakapi/config/config.go:305.47,306.14 1 0
-github.com/muety/wakapi/config/config.go:306.14,308.4 1 0
-github.com/muety/wakapi/config/config.go:311.70,313.3 1 0
-github.com/muety/wakapi/config/config.go:315.28,317.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
-github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
-github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
-github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
-github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
-github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
-github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
-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/misc.go:23.126,30.2 1 0
-github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
-github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
-github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
-github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
-github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
-github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
-github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
-github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
-github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
-github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
-github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
-github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
-github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
-github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
-github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
-github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
-github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
-github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
-github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
-github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
-github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
-github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
+github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:29.80,31.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:33.76,35.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:37.111,39.16 2 0
+github.com/muety/wakapi/services/heartbeat.go:42.2,42.43 1 0
+github.com/muety/wakapi/services/heartbeat.go:39.16,41.3 1 0
+github.com/muety/wakapi/services/heartbeat.go:45.78,47.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:49.62,51.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:53.116,55.16 2 0
+github.com/muety/wakapi/services/heartbeat.go:59.2,59.28 1 0
+github.com/muety/wakapi/services/heartbeat.go:63.2,63.24 1 0
+github.com/muety/wakapi/services/heartbeat.go:55.16,57.3 1 0
+github.com/muety/wakapi/services/heartbeat.go:59.28,61.3 1 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/summary.go:27.149,35.2 1 1
github.com/muety/wakapi/services/summary.go:39.120,42.52 2 1
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
@@ -366,57 +429,6 @@ github.com/muety/wakapi/services/summary.go:324.54,326.3 1 1
github.com/muety/wakapi/services/summary.go:331.59,333.25 2 1
github.com/muety/wakapi/services/summary.go:336.2,336.32 1 1
github.com/muety/wakapi/services/summary.go:333.25,335.3 1 1
-github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
-github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
-github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
-github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
-github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
-github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
-github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
-github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
-github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
-github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
-github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
-github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
-github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
-github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
-github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
-github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
-github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
-github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
-github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
-github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
-github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
-github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
-github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
-github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
-github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
-github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
-github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
-github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
-github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
-github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
-github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
-github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
-github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
-github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
-github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
-github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:25.80,27.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:29.111,31.16 2 0
-github.com/muety/wakapi/services/heartbeat.go:34.2,34.43 1 0
-github.com/muety/wakapi/services/heartbeat.go:31.16,33.3 1 0
-github.com/muety/wakapi/services/heartbeat.go:37.78,39.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:41.62,43.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:45.116,47.16 2 0
-github.com/muety/wakapi/services/heartbeat.go:51.2,51.28 1 0
-github.com/muety/wakapi/services/heartbeat.go:55.2,55.24 1 0
-github.com/muety/wakapi/services/heartbeat.go:47.16,49.3 1 0
-github.com/muety/wakapi/services/heartbeat.go:51.28,53.3 1 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.72,27.2 1 0
-github.com/muety/wakapi/services/key_value.go:29.60,31.2 1 0
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
@@ -457,6 +469,41 @@ github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
+github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
+github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
+github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
+github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
+github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
+github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
+github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
+github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
+github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
+github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
+github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
+github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
+github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
+github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
+github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
+github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
+github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
+github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
+github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
+github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
+github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
+github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
+github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
+github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
+github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
+github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
+github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
+github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
+github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
+github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
+github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
+github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
+github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
+github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
+github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
@@ -482,87 +529,45 @@ github.com/muety/wakapi/services/user.go:104.2,104.68 1 0
github.com/muety/wakapi/services/user.go:99.96,101.3 1 0
github.com/muety/wakapi/services/user.go:101.8,103.3 1 0
github.com/muety/wakapi/services/user.go:107.57,110.2 2 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.54 2 0
-github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
-github.com/muety/wakapi/utils/auth.go:39.54,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/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/common.go:9.48,11.2 1 0
-github.com/muety/wakapi/utils/common.go:13.40,15.2 1 0
-github.com/muety/wakapi/utils/common.go:17.45,19.2 1 0
-github.com/muety/wakapi/utils/common.go:21.24,23.2 1 0
-github.com/muety/wakapi/utils/common.go:25.56,28.45 3 1
-github.com/muety/wakapi/utils/common.go:31.2,31.40 1 1
-github.com/muety/wakapi/utils/common.go:28.45,30.3 1 1
-github.com/muety/wakapi/utils/date.go:8.31,10.2 1 0
-github.com/muety/wakapi/utils/date.go:12.43,14.2 1 0
-github.com/muety/wakapi/utils/date.go:16.30,20.2 3 0
-github.com/muety/wakapi/utils/date.go:22.31,25.2 2 0
-github.com/muety/wakapi/utils/date.go:27.30,30.2 2 0
-github.com/muety/wakapi/utils/date.go:32.67,35.33 2 0
-github.com/muety/wakapi/utils/date.go:44.2,44.18 1 0
-github.com/muety/wakapi/utils/date.go:35.33,37.19 2 0
-github.com/muety/wakapi/utils/date.go:40.3,41.10 2 0
-github.com/muety/wakapi/utils/date.go:37.19,39.4 1 0
-github.com/muety/wakapi/utils/date.go:47.50,53.2 5 0
-github.com/muety/wakapi/utils/date.go:56.79,59.36 3 0
-github.com/muety/wakapi/utils/date.go:63.2,63.21 1 0
-github.com/muety/wakapi/utils/date.go:67.2,67.21 1 0
-github.com/muety/wakapi/utils/date.go:71.2,71.13 1 0
-github.com/muety/wakapi/utils/date.go:59.36,62.3 2 0
-github.com/muety/wakapi/utils/date.go:63.21,66.3 2 0
-github.com/muety/wakapi/utils/date.go:67.21,70.3 2 0
-github.com/muety/wakapi/utils/http.go:9.73,12.58 3 0
-github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
-github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
-github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
-github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
-github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
-github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
-github.com/muety/wakapi/utils/summary.go:10.71,13.18 2 0
-github.com/muety/wakapi/utils/summary.go:49.2,49.22 1 0
-github.com/muety/wakapi/utils/summary.go:14.58,15.24 1 0
-github.com/muety/wakapi/utils/summary.go:16.66,18.22 2 0
-github.com/muety/wakapi/utils/summary.go:19.64,20.23 1 0
-github.com/muety/wakapi/utils/summary.go:21.39,23.21 2 0
-github.com/muety/wakapi/utils/summary.go:24.66,25.24 1 0
-github.com/muety/wakapi/utils/summary.go:26.40,28.22 2 0
-github.com/muety/wakapi/utils/summary.go:29.31,30.23 1 0
-github.com/muety/wakapi/utils/summary.go:31.66,32.42 1 0
-github.com/muety/wakapi/utils/summary.go:33.49,35.40 2 0
-github.com/muety/wakapi/utils/summary.go:36.41,37.43 1 0
-github.com/muety/wakapi/utils/summary.go:38.68,40.42 2 0
-github.com/muety/wakapi/utils/summary.go:41.35,42.43 1 0
-github.com/muety/wakapi/utils/summary.go:43.26,44.21 1 0
-github.com/muety/wakapi/utils/summary.go:45.10,46.39 1 0
-github.com/muety/wakapi/utils/summary.go:52.73,59.56 5 0
-github.com/muety/wakapi/utils/summary.go:73.2,80.8 2 0
-github.com/muety/wakapi/utils/summary.go:59.56,61.3 1 0
-github.com/muety/wakapi/utils/summary.go:61.8,63.17 2 0
-github.com/muety/wakapi/utils/summary.go:67.3,68.17 2 0
-github.com/muety/wakapi/utils/summary.go:63.17,65.4 1 0
-github.com/muety/wakapi/utils/summary.go:68.17,70.4 1 0
-github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
-github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
-github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
-github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
-github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
-github.com/muety/wakapi/utils/template.go:17.30,19.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
+github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
+github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
+github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
+github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
+github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
+github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
+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/misc.go:23.126,30.2 1 0
+github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
+github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
+github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
+github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
+github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
+github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
+github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
+github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
+github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
+github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
+github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
+github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
+github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
+github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
+github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
+github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
+github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
+github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
+github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
+github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
+github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
+github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
diff --git a/mocks/heartbeat_service.go b/mocks/heartbeat_service.go
index db6be27..a069e82 100644
--- a/mocks/heartbeat_service.go
+++ b/mocks/heartbeat_service.go
@@ -10,11 +10,21 @@ type HeartbeatServiceMock struct {
mock.Mock
}
+func (m *HeartbeatServiceMock) Insert(heartbeat *models.Heartbeat) error {
+ args := m.Called(heartbeat)
+ return args.Error(0)
+}
+
func (m *HeartbeatServiceMock) InsertBatch(heartbeats []*models.Heartbeat) error {
args := m.Called(heartbeats)
return args.Error(0)
}
+func (m *HeartbeatServiceMock) CountByUser(user *models.User) (int64, error) {
+ args := m.Called(user)
+ return args.Get(0).(int64), args.Error(0)
+}
+
func (m *HeartbeatServiceMock) GetAllWithin(time time.Time, time2 time.Time, user *models.User) ([]*models.Heartbeat, error) {
args := m.Called(time, time2, user)
return args.Get(0).([]*models.Heartbeat), args.Error(1)
From da3c80362c839d0edeb78bd1ff62f20587e79c90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Fri, 5 Feb 2021 21:05:19 +0100
Subject: [PATCH 03/15] docs: update readme
---
README.md | 12 ++++++++++--
config/config.go | 2 +-
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 76eb08e..aeb365f 100644
--- a/README.md
+++ b/README.md
@@ -39,10 +39,12 @@
## Table of Contents
* [User Survey](#-user-survey)
* [Features](#-features)
+* [Roadmap](#-roadmap)
* [How to use](#-how-to-use)
* [Configuration Options](#-configuration-options)
* [API Endpoints](#-api-endpoints)
-* [Prometheus Export](#-prometheus-export)
+* [Prometheus Export](#%EF%B8%8F-prometheus-export)
+* [WakaTime Integration](#%EF%B8%8F-wakatime-integration)
* [Best Practices](#-best-practices)
* [Developer Notes](#-developer-notes)
* [Support](#-support)
@@ -58,10 +60,13 @@ I'd love to get some community feedback from active Wakapi users. If you want, p
* ✅ Badges
* ✅ REST API
* ✅ Partially compatible with WakaTime
-* ✅ WakaTime relay to use both
+* ✅ WakaTime integration
* ✅ Support for [Prometheus](https://github.com/muety/wakapi#%EF%B8%8F-prometheus-export) exports
* ✅ Self-hosted
+## 🚧 Roadmap
+Plans for the near future mainly include, besides usual improvements and bug fixes, a UI redesign as well as additional types of charts and statistics (see [#101](https://github.com/muety/wakapi/issues/101), [#80](https://github.com/muety/wakapi/issues/80), [#76](https://github.com/muety/wakapi/issues/76), [#12](https://github.com/muety/wakapi/issues/12)). If you have feature requests or any kind of improvement proposals feel free to open an issue or share them in our [user survey](https://github.com/muety/wakapi/issues/82).
+
## ⌨️ How to use?
There are different options for how to use Wakapi, ranging from out hosted cloud service to self-hosting it. Regardless of which option choose, you will always have to do the [client setup](#-client-setup) in addition.
@@ -199,6 +204,9 @@ It is a standalone webserver that connects to your Wakapi instance and exposes t
Simply configure the exporter with `WAKA_SCRAPE_URI` to equal `"https://wakapi.your-server.com/api/compat/wakatime/v1"` and set your API key accordingly.
+## ⤵️ WakaTime Integration
+Wakapi plays well together with [WakaTime](https://wakatime.com). For one thing, you can **forward heartbeats** from Wakapi to WakaTime to effectively use both services simultaneously. In addition, there is the option to **import historic data** from WakaTime for consistency between both services. Both features can be enabled in the _Integrations_ section of your Wakapi instance's settings page.
+
## 👍 Best Practices
It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS).
However, if you want to expose your wakapi instance to the public anyway, you need to set `server.listen_ipv4` to `0.0.0.0` in `config.yml`
diff --git a/config/config.go b/config/config.go
index 05bcda5..cabba17 100644
--- a/config/config.go
+++ b/config/config.go
@@ -48,7 +48,7 @@ type appConfig struct {
AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
CountingTime string `yaml:"counting_time" default:"05:15" env:"WAKAPI_COUNTING_TIME"`
ImportBackoffMin int `yaml:"import_backoff_min" default:"5" env:"WAKAPI_IMPORT_BACKOFF_MIN"`
- ImportBatchSize int `yaml:"import_batch_size" default:"25" env:"WAKAPI_IMPORT_BATCH_SIZE"`
+ ImportBatchSize int `yaml:"import_batch_size" default:"100" env:"WAKAPI_IMPORT_BATCH_SIZE"`
CustomLanguages map[string]string `yaml:"custom_languages"`
Colors map[string]map[string]string `yaml:"-"`
}
From 161e375f74a0b70fc67b084033ee9c0346128789 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 00:31:30 +0100
Subject: [PATCH 04/15] chore: optimize import date range
---
README.md | 2 +-
models/heartbeat.go | 1 +
repositories/heartbeart.go | 15 +++++++++++++++
repositories/repositories.go | 1 +
routes/settings.go | 12 ++++++++++--
services/heartbeat.go | 4 ++++
services/imports/importers.go | 8 ++++++--
services/imports/wakatime.go | 21 ++++++++++++++++-----
services/services.go | 1 +
views/settings.tpl.html | 4 +++-
10 files changed, 58 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index aeb365f..2ac7ad2 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ I'd love to get some community feedback from active Wakapi users. If you want, p
Plans for the near future mainly include, besides usual improvements and bug fixes, a UI redesign as well as additional types of charts and statistics (see [#101](https://github.com/muety/wakapi/issues/101), [#80](https://github.com/muety/wakapi/issues/80), [#76](https://github.com/muety/wakapi/issues/76), [#12](https://github.com/muety/wakapi/issues/12)). If you have feature requests or any kind of improvement proposals feel free to open an issue or share them in our [user survey](https://github.com/muety/wakapi/issues/82).
## ⌨️ How to use?
-There are different options for how to use Wakapi, ranging from out hosted cloud service to self-hosting it. Regardless of which option choose, you will always have to do the [client setup](#-client-setup) in addition.
+There are different options for how to use Wakapi, ranging from our hosted cloud service to self-hosting it. Regardless of which option choose, you will always have to do the [client setup](#-client-setup) in addition.
### ☁️ Option 1: Use [wakapi.dev](https://wakapi.dev)
If you want to you out free, hosted cloud service, all you need to do is create an account and the set up your client-side tooling (see below).
diff --git a/models/heartbeat.go b/models/heartbeat.go
index dc68c7f..5d71d84 100644
--- a/models/heartbeat.go
+++ b/models/heartbeat.go
@@ -25,6 +25,7 @@ type Heartbeat struct {
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"`
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
Origin string `json:"-" hash:"ignore"`
+ OriginId string `json:"-" hash:"ignore"`
languageRegex *regexp.Regexp `hash:"ignore"`
}
diff --git a/repositories/heartbeart.go b/repositories/heartbeart.go
index 631642e..87215ef 100644
--- a/repositories/heartbeart.go
+++ b/repositories/heartbeart.go
@@ -37,6 +37,21 @@ func (r *HeartbeatRepository) CountByUser(user *models.User) (int64, error) {
return count, nil
}
+func (r *HeartbeatRepository) GetLatestByOriginAndUser(origin string, user *models.User) (*models.Heartbeat, error) {
+ var heartbeat models.Heartbeat
+ if err := r.db.
+ Model(&models.Heartbeat{}).
+ Where(&models.Heartbeat{
+ UserID: user.ID,
+ Origin: origin,
+ }).
+ Order("time desc").
+ First(&heartbeat).Error; err != nil {
+ return nil, err
+ }
+ return &heartbeat, nil
+}
+
func (r *HeartbeatRepository) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
var heartbeats []*models.Heartbeat
if err := r.db.
diff --git a/repositories/repositories.go b/repositories/repositories.go
index 588e888..7eeb080 100644
--- a/repositories/repositories.go
+++ b/repositories/repositories.go
@@ -20,6 +20,7 @@ type IHeartbeatRepository interface {
CountByUser(*models.User) (int64, error)
GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error)
GetFirstByUsers() ([]*models.TimeByUser, error)
+ GetLatestByOriginAndUser(string, *models.User) (*models.Heartbeat, error)
DeleteBefore(time.Time) error
}
diff --git a/routes/settings.go b/routes/settings.go
index f8ccfb8..2de69c8 100644
--- a/routes/settings.go
+++ b/routes/settings.go
@@ -362,10 +362,18 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
println(err)
}
+ var stream <-chan *models.Heartbeat
+ if latest, err := h.heartbeatSrvc.GetLatestByOriginAndUser(imports.OriginWakatime, user); latest == nil || err != nil {
+ stream = importer.ImportAll(user)
+ } else {
+ // if an import has happened before, only import heartbeats newer than the latest of the last import
+ stream = importer.Import(user, latest.Time.T(), time.Now())
+ }
+
count := 0
batch := make([]*models.Heartbeat, 0)
- for hb := range importer.Import(user) {
+ for hb := range stream {
count++
batch = append(batch, hb)
@@ -389,7 +397,7 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
Value: time.Now().Format(time.RFC822),
})
- return http.StatusAccepted, "Import started. This may take a few minutes.", ""
+ return http.StatusAccepted, "ImportAll started. This may take a few minutes.", ""
}
func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *http.Request) (int, string, string) {
diff --git a/services/heartbeat.go b/services/heartbeat.go
index 656fe5e..39f68e2 100644
--- a/services/heartbeat.go
+++ b/services/heartbeat.go
@@ -42,6 +42,10 @@ func (srv *HeartbeatService) GetAllWithin(from, to time.Time, user *models.User)
return srv.augmented(heartbeats, user.ID)
}
+func (srv *HeartbeatService) GetLatestByOriginAndUser(origin string, user *models.User) (*models.Heartbeat, error) {
+ return srv.repository.GetLatestByOriginAndUser(origin, user)
+}
+
func (srv *HeartbeatService) GetFirstByUsers() ([]*models.TimeByUser, error) {
return srv.repository.GetFirstByUsers()
}
diff --git a/services/imports/importers.go b/services/imports/importers.go
index 2316461..9f07b8e 100644
--- a/services/imports/importers.go
+++ b/services/imports/importers.go
@@ -1,7 +1,11 @@
package imports
-import "github.com/muety/wakapi/models"
+import (
+ "github.com/muety/wakapi/models"
+ "time"
+)
type HeartbeatImporter interface {
- Import(*models.User) <-chan *models.Heartbeat
+ Import(*models.User, time.Time, time.Time) <-chan *models.Heartbeat
+ ImportAll(*models.User) <-chan *models.Heartbeat
}
diff --git a/services/imports/wakatime.go b/services/imports/wakatime.go
index 1f3c7f2..63b5e87 100644
--- a/services/imports/wakatime.go
+++ b/services/imports/wakatime.go
@@ -17,9 +17,8 @@ import (
"time"
)
-const (
- maxWorkers = 6
-)
+const OriginWakatime = "wakatime"
+const maxWorkers = 6
type WakatimeHeartbeatImporter struct {
ApiKey string
@@ -31,7 +30,7 @@ func NewWakatimeHeartbeatImporter(apiKey string) *WakatimeHeartbeatImporter {
}
}
-func (w *WakatimeHeartbeatImporter) Import(user *models.User) <-chan *models.Heartbeat {
+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) {
@@ -41,6 +40,13 @@ func (w *WakatimeHeartbeatImporter) Import(user *models.User) <-chan *models.Hea
return
}
+ if startDate.Before(minFrom) {
+ startDate = minFrom
+ }
+ if endDate.After(maxTo) {
+ endDate = maxTo
+ }
+
userAgents, err := w.fetchUserAgents()
if err != nil {
logbuch.Error("failed to fetch user agents while importing wakatime heartbeats for user '%s' – %v", user.ID, err)
@@ -82,6 +88,10 @@ func (w *WakatimeHeartbeatImporter) Import(user *models.User) <-chan *models.Hea
return out
}
+func (w *WakatimeHeartbeatImporter) ImportAll(user *models.User) <-chan *models.Heartbeat {
+ return w.Import(user, time.Time{}, time.Now())
+}
+
// https://wakatime.com/api/v1/users/current/heartbeats?date=2021-02-05
func (w *WakatimeHeartbeatImporter) fetchHeartbeats(day string) ([]*wakatime.HeartbeatEntry, error) {
httpClient := &http.Client{Timeout: 10 * time.Second}
@@ -211,7 +221,8 @@ func mapHeartbeat(
OperatingSystem: ua.Os,
Machine: entry.MachineNameId, // TODO
Time: entry.Time,
- Origin: fmt.Sprintf("wt@%s", entry.Id),
+ Origin: OriginWakatime,
+ OriginId: entry.Id,
}).Hashed()
}
diff --git a/services/services.go b/services/services.go
index 02d73bc..9780586 100644
--- a/services/services.go
+++ b/services/services.go
@@ -31,6 +31,7 @@ type IHeartbeatService interface {
CountByUser(*models.User) (int64, error)
GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error)
GetFirstByUsers() ([]*models.TimeByUser, error)
+ GetLatestByOriginAndUser(string, *models.User) (*models.Heartbeat, error)
DeleteBefore(time.Time) error
}
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 9085bea..269bfb1 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -474,7 +474,9 @@
const btnImportWakatime = document.querySelector('#btn-import-wakatime')
const formImportWakatime = document.querySelector('#form-import-wakatime')
btnImportWakatime.addEventListener('click', () => {
- formImportWakatime.submit()
+ if (confirm('Are you sure? The import can not be undone.')) {
+ formImportWakatime.submit()
+ }
})
From 8ba3fdcaadfe316f7975b54717689adb74326283 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 00:35:35 +0100
Subject: [PATCH 05/15] fix: tests
---
coverage/coverage.out | 613 +++++++++++++++++++------------------
mocks/heartbeat_service.go | 5 +
2 files changed, 312 insertions(+), 306 deletions(-)
diff --git a/coverage/coverage.out b/coverage/coverage.out
index a364713..fc655fc 100644
--- a/coverage/coverage.out
+++ b/coverage/coverage.out
@@ -1,5 +1,77 @@
mode: set
+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/models.go:3.14,5.2 0 1
+github.com/muety/wakapi/models/shared.go:34.52,37.16 3 0
+github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
+github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
+github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
+github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
+github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
+github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
+github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
+github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
+github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
+github.com/muety/wakapi/models/shared.go:74.45,76.2 1 0
+github.com/muety/wakapi/models/shared.go:78.51,81.2 2 0
+github.com/muety/wakapi/models/shared.go:83.37,86.2 2 0
+github.com/muety/wakapi/models/shared.go:88.35,90.2 1 0
+github.com/muety/wakapi/models/shared.go:92.34,94.2 1 0
+github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
+github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
+github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
+github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
+github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
+github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
+github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
+github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
+github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
+github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
+github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
+github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
+github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
+github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
+github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
+github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
+github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
+github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
+github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
+github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
+github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
+github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
+github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:32.34,34.2 1 1
+github.com/muety/wakapi/models/heartbeat.go:36.65,37.28 1 1
+github.com/muety/wakapi/models/heartbeat.go:40.2,41.45 2 1
+github.com/muety/wakapi/models/heartbeat.go:44.2,45.44 2 1
+github.com/muety/wakapi/models/heartbeat.go:48.2,48.42 1 1
+github.com/muety/wakapi/models/heartbeat.go:37.28,39.3 1 1
+github.com/muety/wakapi/models/heartbeat.go:41.45,43.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:45.44,47.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:51.50,52.11 1 1
+github.com/muety/wakapi/models/heartbeat.go:65.2,65.15 1 1
+github.com/muety/wakapi/models/heartbeat.go:69.2,69.12 1 1
+github.com/muety/wakapi/models/heartbeat.go:53.22,54.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:55.21,56.17 1 1
+github.com/muety/wakapi/models/heartbeat.go:57.23,58.19 1 1
+github.com/muety/wakapi/models/heartbeat.go:59.17,60.26 1 1
+github.com/muety/wakapi/models/heartbeat.go:61.22,62.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:65.15,67.3 1 1
+github.com/muety/wakapi/models/heartbeat.go:72.37,88.2 1 0
+github.com/muety/wakapi/models/heartbeat.go:96.41,98.16 2 0
+github.com/muety/wakapi/models/heartbeat.go:101.2,102.10 2 0
+github.com/muety/wakapi/models/heartbeat.go:98.16,100.3 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/summary.go:41.27,45.2 1 0
github.com/muety/wakapi/models/summary.go:97.29,99.2 1 1
github.com/muety/wakapi/models/summary.go:101.37,108.2 6 1
@@ -54,78 +126,70 @@ github.com/muety/wakapi/models/user.go:35.43,38.2 1 0
github.com/muety/wakapi/models/user.go:40.33,44.2 1 0
github.com/muety/wakapi/models/user.go:46.45,48.2 1 0
github.com/muety/wakapi/models/user.go:50.45,52.2 1 0
-github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
-github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
-github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
-github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
-github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
-github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
-github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
-github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
-github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
-github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
-github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
-github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
-github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
-github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
-github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
-github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
-github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
-github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
-github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
-github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
-github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
-github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
-github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:31.34,33.2 1 1
-github.com/muety/wakapi/models/heartbeat.go:35.65,36.28 1 1
-github.com/muety/wakapi/models/heartbeat.go:39.2,40.45 2 1
-github.com/muety/wakapi/models/heartbeat.go:43.2,44.44 2 1
-github.com/muety/wakapi/models/heartbeat.go:47.2,47.42 1 1
-github.com/muety/wakapi/models/heartbeat.go:36.28,38.3 1 1
-github.com/muety/wakapi/models/heartbeat.go:40.45,42.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:44.44,46.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:50.50,51.11 1 1
-github.com/muety/wakapi/models/heartbeat.go:64.2,64.15 1 1
-github.com/muety/wakapi/models/heartbeat.go:68.2,68.12 1 1
-github.com/muety/wakapi/models/heartbeat.go:52.22,53.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:54.21,55.17 1 1
-github.com/muety/wakapi/models/heartbeat.go:56.23,57.19 1 1
-github.com/muety/wakapi/models/heartbeat.go:58.17,59.26 1 1
-github.com/muety/wakapi/models/heartbeat.go:60.22,61.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:64.15,66.3 1 1
-github.com/muety/wakapi/models/heartbeat.go:71.37,87.2 1 0
-github.com/muety/wakapi/models/heartbeat.go:95.41,97.16 2 0
-github.com/muety/wakapi/models/heartbeat.go:100.2,101.10 2 0
-github.com/muety/wakapi/models/heartbeat.go:97.16,99.3 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/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/shared.go:34.52,37.16 3 0
-github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
-github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
-github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
-github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
-github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
-github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
-github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
-github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
-github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
-github.com/muety/wakapi/models/shared.go:74.45,76.2 1 0
-github.com/muety/wakapi/models/shared.go:78.51,81.2 2 0
-github.com/muety/wakapi/models/shared.go:83.37,86.2 2 0
-github.com/muety/wakapi/models/shared.go:88.35,90.2 1 0
-github.com/muety/wakapi/models/shared.go:92.34,94.2 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:94.70,96.2 1 0
+github.com/muety/wakapi/config/config.go:98.65,100.2 1 0
+github.com/muety/wakapi/config/config.go:102.82,112.2 1 0
+github.com/muety/wakapi/config/config.go:114.31,116.2 1 0
+github.com/muety/wakapi/config/config.go:118.32,120.2 1 0
+github.com/muety/wakapi/config/config.go:122.74,123.19 1 0
+github.com/muety/wakapi/config/config.go:124.10,125.34 1 0
+github.com/muety/wakapi/config/config.go:125.34,134.4 8 0
+github.com/muety/wakapi/config/config.go:138.73,139.33 1 0
+github.com/muety/wakapi/config/config.go:139.33,147.17 5 0
+github.com/muety/wakapi/config/config.go:151.3,152.13 2 0
+github.com/muety/wakapi/config/config.go:147.17,149.4 1 0
+github.com/muety/wakapi/config/config.go:156.50,157.19 1 0
+github.com/muety/wakapi/config/config.go:170.2,170.12 1 0
+github.com/muety/wakapi/config/config.go:158.23,162.5 1 0
+github.com/muety/wakapi/config/config.go:163.26,166.5 1 0
+github.com/muety/wakapi/config/config.go:167.24,168.48 1 0
+github.com/muety/wakapi/config/config.go:173.53,183.2 1 1
+github.com/muety/wakapi/config/config.go:185.56,187.16 2 1
+github.com/muety/wakapi/config/config.go:191.2,198.3 1 1
+github.com/muety/wakapi/config/config.go:187.16,189.3 1 0
+github.com/muety/wakapi/config/config.go:201.54,203.2 1 1
+github.com/muety/wakapi/config/config.go:205.60,207.2 1 0
+github.com/muety/wakapi/config/config.go:209.59,211.2 1 0
+github.com/muety/wakapi/config/config.go:213.57,215.2 1 0
+github.com/muety/wakapi/config/config.go:217.53,219.2 1 0
+github.com/muety/wakapi/config/config.go:221.29,223.2 1 1
+github.com/muety/wakapi/config/config.go:225.27,227.16 2 0
+github.com/muety/wakapi/config/config.go:230.2,233.16 3 0
+github.com/muety/wakapi/config/config.go:237.2,237.41 1 0
+github.com/muety/wakapi/config/config.go:227.16,229.3 1 0
+github.com/muety/wakapi/config/config.go:233.16,235.3 1 0
+github.com/muety/wakapi/config/config.go:240.48,252.16 3 0
+github.com/muety/wakapi/config/config.go:255.2,257.16 3 0
+github.com/muety/wakapi/config/config.go:261.2,261.55 1 0
+github.com/muety/wakapi/config/config.go:265.2,265.15 1 0
+github.com/muety/wakapi/config/config.go:252.16,254.3 1 0
+github.com/muety/wakapi/config/config.go:257.16,259.3 1 0
+github.com/muety/wakapi/config/config.go:261.55,263.3 1 0
+github.com/muety/wakapi/config/config.go:268.38,269.43 1 0
+github.com/muety/wakapi/config/config.go:272.2,272.15 1 0
+github.com/muety/wakapi/config/config.go:269.43,271.3 1 0
+github.com/muety/wakapi/config/config.go:275.45,276.27 1 0
+github.com/muety/wakapi/config/config.go:279.2,279.15 1 0
+github.com/muety/wakapi/config/config.go:276.27,278.3 1 0
+github.com/muety/wakapi/config/config.go:282.26,284.2 1 0
+github.com/muety/wakapi/config/config.go:286.20,288.2 1 0
+github.com/muety/wakapi/config/config.go:290.21,295.96 3 0
+github.com/muety/wakapi/config/config.go:299.2,307.52 5 0
+github.com/muety/wakapi/config/config.go:311.2,311.47 1 0
+github.com/muety/wakapi/config/config.go:317.2,317.70 1 0
+github.com/muety/wakapi/config/config.go:321.2,321.28 1 0
+github.com/muety/wakapi/config/config.go:325.2,326.14 2 0
+github.com/muety/wakapi/config/config.go:295.96,297.3 1 0
+github.com/muety/wakapi/config/config.go:307.52,309.3 1 0
+github.com/muety/wakapi/config/config.go:311.47,312.14 1 0
+github.com/muety/wakapi/config/config.go:312.14,314.4 1 0
+github.com/muety/wakapi/config/config.go:317.70,319.3 1 0
+github.com/muety/wakapi/config/config.go:321.28,323.3 1 0
github.com/muety/wakapi/utils/summary.go:10.71,13.18 2 0
github.com/muety/wakapi/utils/summary.go:49.2,49.22 1 0
github.com/muety/wakapi/utils/summary.go:14.58,15.24 1 0
@@ -210,27 +274,6 @@ github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
-github.com/muety/wakapi/middlewares/logging.go:17.79,18.43 1 0
-github.com/muety/wakapi/middlewares/logging.go:18.43,23.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:26.80,44.2 6 0
-github.com/muety/wakapi/middlewares/logging.go:46.41,48.14 2 0
-github.com/muety/wakapi/middlewares/logging.go:51.2,51.14 1 0
-github.com/muety/wakapi/middlewares/logging.go:54.2,54.11 1 0
-github.com/muety/wakapi/middlewares/logging.go:48.14,50.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:51.14,53.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:85.52,87.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:99.45,100.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:100.20,104.3 3 0
-github.com/muety/wakapi/middlewares/logging.go:106.54,109.18 3 0
-github.com/muety/wakapi/middlewares/logging.go:116.2,117.15 2 0
-github.com/muety/wakapi/middlewares/logging.go:109.18,112.17 2 0
-github.com/muety/wakapi/middlewares/logging.go:112.17,114.4 1 0
-github.com/muety/wakapi/middlewares/logging.go:119.42,120.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:120.20,122.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:124.36,126.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:127.42,129.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:130.40,132.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:133.52,135.2 1 0
github.com/muety/wakapi/middlewares/authenticate.go:20.116,26.2 1 1
github.com/muety/wakapi/middlewares/authenticate.go:28.71,29.71 1 0
github.com/muety/wakapi/middlewares/authenticate.go:29.71,31.3 1 0
@@ -255,70 +298,127 @@ github.com/muety/wakapi/middlewares/authenticate.go:84.2,85.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.18 1 0
github.com/muety/wakapi/middlewares/authenticate.go:80.16,82.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:85.16,87.3 1 0
-github.com/muety/wakapi/config/config.go:94.70,96.2 1 0
-github.com/muety/wakapi/config/config.go:98.65,100.2 1 0
-github.com/muety/wakapi/config/config.go:102.82,112.2 1 0
-github.com/muety/wakapi/config/config.go:114.31,116.2 1 0
-github.com/muety/wakapi/config/config.go:118.32,120.2 1 0
-github.com/muety/wakapi/config/config.go:122.74,123.19 1 0
-github.com/muety/wakapi/config/config.go:124.10,125.34 1 0
-github.com/muety/wakapi/config/config.go:125.34,134.4 8 0
-github.com/muety/wakapi/config/config.go:138.73,139.33 1 0
-github.com/muety/wakapi/config/config.go:139.33,147.17 5 0
-github.com/muety/wakapi/config/config.go:151.3,152.13 2 0
-github.com/muety/wakapi/config/config.go:147.17,149.4 1 0
-github.com/muety/wakapi/config/config.go:156.50,157.19 1 0
-github.com/muety/wakapi/config/config.go:170.2,170.12 1 0
-github.com/muety/wakapi/config/config.go:158.23,162.5 1 0
-github.com/muety/wakapi/config/config.go:163.26,166.5 1 0
-github.com/muety/wakapi/config/config.go:167.24,168.48 1 0
-github.com/muety/wakapi/config/config.go:173.53,183.2 1 1
-github.com/muety/wakapi/config/config.go:185.56,187.16 2 1
-github.com/muety/wakapi/config/config.go:191.2,198.3 1 1
-github.com/muety/wakapi/config/config.go:187.16,189.3 1 0
-github.com/muety/wakapi/config/config.go:201.54,203.2 1 1
-github.com/muety/wakapi/config/config.go:205.60,207.2 1 0
-github.com/muety/wakapi/config/config.go:209.59,211.2 1 0
-github.com/muety/wakapi/config/config.go:213.57,215.2 1 0
-github.com/muety/wakapi/config/config.go:217.53,219.2 1 0
-github.com/muety/wakapi/config/config.go:221.29,223.2 1 1
-github.com/muety/wakapi/config/config.go:225.27,227.16 2 0
-github.com/muety/wakapi/config/config.go:230.2,233.16 3 0
-github.com/muety/wakapi/config/config.go:237.2,237.41 1 0
-github.com/muety/wakapi/config/config.go:227.16,229.3 1 0
-github.com/muety/wakapi/config/config.go:233.16,235.3 1 0
-github.com/muety/wakapi/config/config.go:240.48,252.16 3 0
-github.com/muety/wakapi/config/config.go:255.2,257.16 3 0
-github.com/muety/wakapi/config/config.go:261.2,261.55 1 0
-github.com/muety/wakapi/config/config.go:265.2,265.15 1 0
-github.com/muety/wakapi/config/config.go:252.16,254.3 1 0
-github.com/muety/wakapi/config/config.go:257.16,259.3 1 0
-github.com/muety/wakapi/config/config.go:261.55,263.3 1 0
-github.com/muety/wakapi/config/config.go:268.38,269.43 1 0
-github.com/muety/wakapi/config/config.go:272.2,272.15 1 0
-github.com/muety/wakapi/config/config.go:269.43,271.3 1 0
-github.com/muety/wakapi/config/config.go:275.45,276.27 1 0
-github.com/muety/wakapi/config/config.go:279.2,279.15 1 0
-github.com/muety/wakapi/config/config.go:276.27,278.3 1 0
-github.com/muety/wakapi/config/config.go:282.26,284.2 1 0
-github.com/muety/wakapi/config/config.go:286.20,288.2 1 0
-github.com/muety/wakapi/config/config.go:290.21,295.96 3 0
-github.com/muety/wakapi/config/config.go:299.2,307.52 5 0
-github.com/muety/wakapi/config/config.go:311.2,311.47 1 0
-github.com/muety/wakapi/config/config.go:317.2,317.70 1 0
-github.com/muety/wakapi/config/config.go:321.2,321.28 1 0
-github.com/muety/wakapi/config/config.go:325.2,326.14 2 0
-github.com/muety/wakapi/config/config.go:295.96,297.3 1 0
-github.com/muety/wakapi/config/config.go:307.52,309.3 1 0
-github.com/muety/wakapi/config/config.go:311.47,312.14 1 0
-github.com/muety/wakapi/config/config.go:312.14,314.4 1 0
-github.com/muety/wakapi/config/config.go:317.70,319.3 1 0
-github.com/muety/wakapi/config/config.go:321.28,323.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/middlewares/logging.go:17.79,18.43 1 0
+github.com/muety/wakapi/middlewares/logging.go:18.43,23.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:26.80,44.2 6 0
+github.com/muety/wakapi/middlewares/logging.go:46.41,48.14 2 0
+github.com/muety/wakapi/middlewares/logging.go:51.2,51.14 1 0
+github.com/muety/wakapi/middlewares/logging.go:54.2,54.11 1 0
+github.com/muety/wakapi/middlewares/logging.go:48.14,50.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:51.14,53.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:85.52,87.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:99.45,100.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:100.20,104.3 3 0
+github.com/muety/wakapi/middlewares/logging.go:106.54,109.18 3 0
+github.com/muety/wakapi/middlewares/logging.go:116.2,117.15 2 0
+github.com/muety/wakapi/middlewares/logging.go:109.18,112.17 2 0
+github.com/muety/wakapi/middlewares/logging.go:112.17,114.4 1 0
+github.com/muety/wakapi/middlewares/logging.go:119.42,120.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:120.20,122.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:124.36,126.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:127.42,129.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:130.40,132.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:133.52,135.2 1 0
+github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
+github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
+github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
+github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
+github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
+github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
+github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
+github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
+github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
+github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
+github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
+github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
+github.com/muety/wakapi/services/user.go:59.88,66.93 2 0
+github.com/muety/wakapi/services/user.go:72.2,72.38 1 0
+github.com/muety/wakapi/services/user.go:66.93,68.3 1 0
+github.com/muety/wakapi/services/user.go:68.8,70.3 1 0
+github.com/muety/wakapi/services/user.go:75.73,78.2 2 0
+github.com/muety/wakapi/services/user.go:80.78,84.2 3 0
+github.com/muety/wakapi/services/user.go:86.79,89.2 2 0
+github.com/muety/wakapi/services/user.go:91.99,94.2 2 0
+github.com/muety/wakapi/services/user.go:96.106,99.96 3 0
+github.com/muety/wakapi/services/user.go:104.2,104.68 1 0
+github.com/muety/wakapi/services/user.go:99.96,101.3 1 0
+github.com/muety/wakapi/services/user.go:101.8,103.3 1 0
+github.com/muety/wakapi/services/user.go:107.57,110.2 2 0
+github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
+github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
+github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
+github.com/muety/wakapi/services/aggregation.go:42.37,44.3 1 0
+github.com/muety/wakapi/services/aggregation.go:51.67,55.40 3 0
+github.com/muety/wakapi/services/aggregation.go:59.2,59.50 1 0
+github.com/muety/wakapi/services/aggregation.go:64.2,64.60 1 0
+github.com/muety/wakapi/services/aggregation.go:70.2,70.35 1 0
+github.com/muety/wakapi/services/aggregation.go:55.40,57.3 1 0
+github.com/muety/wakapi/services/aggregation.go:59.50,61.3 1 0
+github.com/muety/wakapi/services/aggregation.go:64.60,68.3 3 0
+github.com/muety/wakapi/services/aggregation.go:73.109,74.24 1 0
+github.com/muety/wakapi/services/aggregation.go:74.24,75.111 1 0
+github.com/muety/wakapi/services/aggregation.go:75.111,77.4 1 0
+github.com/muety/wakapi/services/aggregation.go:77.9,80.4 2 0
+github.com/muety/wakapi/services/aggregation.go:84.80,85.33 1 0
+github.com/muety/wakapi/services/aggregation.go:85.33,86.60 1 0
+github.com/muety/wakapi/services/aggregation.go:86.60,88.4 1 0
+github.com/muety/wakapi/services/aggregation.go:92.100,96.59 3 0
+github.com/muety/wakapi/services/aggregation.go:111.2,112.16 2 0
+github.com/muety/wakapi/services/aggregation.go:118.2,119.16 2 0
+github.com/muety/wakapi/services/aggregation.go:125.2,126.44 2 0
+github.com/muety/wakapi/services/aggregation.go:131.2,131.41 1 0
+github.com/muety/wakapi/services/aggregation.go:145.2,145.12 1 0
+github.com/muety/wakapi/services/aggregation.go:96.59,99.3 2 0
+github.com/muety/wakapi/services/aggregation.go:99.8,99.47 1 0
+github.com/muety/wakapi/services/aggregation.go:99.47,101.30 2 0
+github.com/muety/wakapi/services/aggregation.go:101.30,102.43 1 0
+github.com/muety/wakapi/services/aggregation.go:102.43,104.5 1 0
+github.com/muety/wakapi/services/aggregation.go:106.8,108.3 1 0
+github.com/muety/wakapi/services/aggregation.go:112.16,115.3 2 0
+github.com/muety/wakapi/services/aggregation.go:119.16,122.3 2 0
+github.com/muety/wakapi/services/aggregation.go:126.44,128.3 1 0
+github.com/muety/wakapi/services/aggregation.go:131.41,132.21 1 0
+github.com/muety/wakapi/services/aggregation.go:132.21,136.4 1 0
+github.com/muety/wakapi/services/aggregation.go:136.9,136.62 1 0
+github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
+github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
+github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
+github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
+github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
+github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
+github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
+github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
+github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
+github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
+github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
+github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
+github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
+github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
+github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
+github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
+github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
+github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
+github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
+github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
+github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
+github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
+github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
+github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
+github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
+github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
+github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
+github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
+github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
+github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
+github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
+github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
+github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
+github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
+github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
+github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
+github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
+github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
+github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
github.com/muety/wakapi/services/heartbeat.go:29.80,31.2 1 0
@@ -326,13 +426,33 @@ github.com/muety/wakapi/services/heartbeat.go:33.76,35.2 1 0
github.com/muety/wakapi/services/heartbeat.go:37.111,39.16 2 0
github.com/muety/wakapi/services/heartbeat.go:42.2,42.43 1 0
github.com/muety/wakapi/services/heartbeat.go:39.16,41.3 1 0
-github.com/muety/wakapi/services/heartbeat.go:45.78,47.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:49.62,51.2 1 0
-github.com/muety/wakapi/services/heartbeat.go:53.116,55.16 2 0
-github.com/muety/wakapi/services/heartbeat.go:59.2,59.28 1 0
-github.com/muety/wakapi/services/heartbeat.go:63.2,63.24 1 0
-github.com/muety/wakapi/services/heartbeat.go:55.16,57.3 1 0
-github.com/muety/wakapi/services/heartbeat.go:59.28,61.3 1 0
+github.com/muety/wakapi/services/heartbeat.go:45.116,47.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:49.78,51.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:53.62,55.2 1 0
+github.com/muety/wakapi/services/heartbeat.go:57.116,59.16 2 0
+github.com/muety/wakapi/services/heartbeat.go:63.2,63.28 1 0
+github.com/muety/wakapi/services/heartbeat.go:67.2,67.24 1 0
+github.com/muety/wakapi/services/heartbeat.go:59.16,61.3 1 0
+github.com/muety/wakapi/services/heartbeat.go:63.28,65.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
+github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
+github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
+github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
+github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
+github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
+github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
+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/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
@@ -340,6 +460,29 @@ 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:23.126,30.2 1 0
+github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
+github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
+github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
+github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
+github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
+github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
+github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
+github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
+github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
+github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
+github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
+github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
+github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
+github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
+github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
+github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
+github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
+github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
+github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
+github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
+github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
+github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
github.com/muety/wakapi/services/summary.go:27.149,35.2 1 1
github.com/muety/wakapi/services/summary.go:39.120,42.52 2 1
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
@@ -429,145 +572,3 @@ github.com/muety/wakapi/services/summary.go:324.54,326.3 1 1
github.com/muety/wakapi/services/summary.go:331.59,333.25 2 1
github.com/muety/wakapi/services/summary.go:336.2,336.32 1 1
github.com/muety/wakapi/services/summary.go:333.25,335.3 1 1
-github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
-github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
-github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
-github.com/muety/wakapi/services/aggregation.go:42.37,44.3 1 0
-github.com/muety/wakapi/services/aggregation.go:51.67,55.40 3 0
-github.com/muety/wakapi/services/aggregation.go:59.2,59.50 1 0
-github.com/muety/wakapi/services/aggregation.go:64.2,64.60 1 0
-github.com/muety/wakapi/services/aggregation.go:70.2,70.35 1 0
-github.com/muety/wakapi/services/aggregation.go:55.40,57.3 1 0
-github.com/muety/wakapi/services/aggregation.go:59.50,61.3 1 0
-github.com/muety/wakapi/services/aggregation.go:64.60,68.3 3 0
-github.com/muety/wakapi/services/aggregation.go:73.109,74.24 1 0
-github.com/muety/wakapi/services/aggregation.go:74.24,75.111 1 0
-github.com/muety/wakapi/services/aggregation.go:75.111,77.4 1 0
-github.com/muety/wakapi/services/aggregation.go:77.9,80.4 2 0
-github.com/muety/wakapi/services/aggregation.go:84.80,85.33 1 0
-github.com/muety/wakapi/services/aggregation.go:85.33,86.60 1 0
-github.com/muety/wakapi/services/aggregation.go:86.60,88.4 1 0
-github.com/muety/wakapi/services/aggregation.go:92.100,96.59 3 0
-github.com/muety/wakapi/services/aggregation.go:111.2,112.16 2 0
-github.com/muety/wakapi/services/aggregation.go:118.2,119.16 2 0
-github.com/muety/wakapi/services/aggregation.go:125.2,126.44 2 0
-github.com/muety/wakapi/services/aggregation.go:131.2,131.41 1 0
-github.com/muety/wakapi/services/aggregation.go:145.2,145.12 1 0
-github.com/muety/wakapi/services/aggregation.go:96.59,99.3 2 0
-github.com/muety/wakapi/services/aggregation.go:99.8,99.47 1 0
-github.com/muety/wakapi/services/aggregation.go:99.47,101.30 2 0
-github.com/muety/wakapi/services/aggregation.go:101.30,102.43 1 0
-github.com/muety/wakapi/services/aggregation.go:102.43,104.5 1 0
-github.com/muety/wakapi/services/aggregation.go:106.8,108.3 1 0
-github.com/muety/wakapi/services/aggregation.go:112.16,115.3 2 0
-github.com/muety/wakapi/services/aggregation.go:119.16,122.3 2 0
-github.com/muety/wakapi/services/aggregation.go:126.44,128.3 1 0
-github.com/muety/wakapi/services/aggregation.go:131.41,132.21 1 0
-github.com/muety/wakapi/services/aggregation.go:132.21,136.4 1 0
-github.com/muety/wakapi/services/aggregation.go:136.9,136.62 1 0
-github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
-github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
-github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
-github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
-github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
-github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
-github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
-github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
-github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
-github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
-github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
-github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
-github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
-github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
-github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
-github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
-github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
-github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
-github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
-github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
-github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
-github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
-github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
-github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
-github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
-github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
-github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
-github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
-github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
-github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
-github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
-github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
-github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
-github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
-github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
-github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
-github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
-github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
-github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
-github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
-github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
-github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
-github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
-github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
-github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
-github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
-github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
-github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
-github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
-github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
-github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
-github.com/muety/wakapi/services/user.go:59.88,66.93 2 0
-github.com/muety/wakapi/services/user.go:72.2,72.38 1 0
-github.com/muety/wakapi/services/user.go:66.93,68.3 1 0
-github.com/muety/wakapi/services/user.go:68.8,70.3 1 0
-github.com/muety/wakapi/services/user.go:75.73,78.2 2 0
-github.com/muety/wakapi/services/user.go:80.78,84.2 3 0
-github.com/muety/wakapi/services/user.go:86.79,89.2 2 0
-github.com/muety/wakapi/services/user.go:91.99,94.2 2 0
-github.com/muety/wakapi/services/user.go:96.106,99.96 3 0
-github.com/muety/wakapi/services/user.go:104.2,104.68 1 0
-github.com/muety/wakapi/services/user.go:99.96,101.3 1 0
-github.com/muety/wakapi/services/user.go:101.8,103.3 1 0
-github.com/muety/wakapi/services/user.go:107.57,110.2 2 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
-github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
-github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
-github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
-github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
-github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
-github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
-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/misc.go:23.126,30.2 1 0
-github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
-github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
-github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
-github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
-github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
-github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
-github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
-github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
-github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
-github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
-github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
-github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
-github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
-github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
-github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
-github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
-github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
-github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
-github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
-github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
-github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
-github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
diff --git a/mocks/heartbeat_service.go b/mocks/heartbeat_service.go
index a069e82..e03afc4 100644
--- a/mocks/heartbeat_service.go
+++ b/mocks/heartbeat_service.go
@@ -35,6 +35,11 @@ func (m *HeartbeatServiceMock) GetFirstByUsers() ([]*models.TimeByUser, error) {
return args.Get(0).([]*models.TimeByUser), args.Error(1)
}
+func (m *HeartbeatServiceMock) GetLatestByOriginAndUser(s string, user *models.User) (*models.Heartbeat, error) {
+ args := m.Called(s, user)
+ return args.Get(0).(*models.Heartbeat), args.Error(1)
+}
+
func (m *HeartbeatServiceMock) DeleteBefore(time time.Time) error {
args := m.Called(time)
return args.Error(0)
From 9ff35b85d0953862d20685123634b2ee9bd06253 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 16:05:34 +0100
Subject: [PATCH 06/15] feat: implement stats endpoint (resolve #114)
---
main.go | 2 +
models/compat/wakatime/v1/heartbeat.go | 24 +++----
models/compat/wakatime/v1/stats.go | 78 +++++++++++++++++++++++
models/compat/wakatime/v1/user_agent.go | 8 +--
models/summary.go | 8 +--
routes/compat/wakatime/v1/stats.go | 83 +++++++++++++++++++++++++
views/index.tpl.html | 2 +-
views/summary.tpl.html | 8 +--
8 files changed, 188 insertions(+), 25 deletions(-)
create mode 100644 models/compat/wakatime/v1/stats.go
create mode 100644 routes/compat/wakatime/v1/stats.go
diff --git a/main.go b/main.go
index 0d5ce1b..effd6b5 100644
--- a/main.go
+++ b/main.go
@@ -132,6 +132,7 @@ func main() {
// Compat Handlers
wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(summaryService)
wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(summaryService)
+ wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(summaryService)
shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService)
// MVC Handlers
@@ -172,6 +173,7 @@ func main() {
// Compat route registrations
wakatimeV1AllHandler.RegisterRoutes(compatApiRouter)
wakatimeV1SummariesHandler.RegisterRoutes(compatApiRouter)
+ wakatimeV1StatsHandler.RegisterRoutes(compatApiRouter)
shieldV1BadgeHandler.RegisterRoutes(compatApiRouter)
// Static Routes
diff --git a/models/compat/wakatime/v1/heartbeat.go b/models/compat/wakatime/v1/heartbeat.go
index 8461db4..b192456 100644
--- a/models/compat/wakatime/v1/heartbeat.go
+++ b/models/compat/wakatime/v1/heartbeat.go
@@ -10,16 +10,16 @@ type HeartbeatsViewModel struct {
// that is actually required for the import
type HeartbeatEntry struct {
- Id string
- Branch string
- Category string
- Entity string
- IsWrite bool `json:"is_write"`
- Language string
- Project string
- Time models.CustomTime
- Type string
- UserId string `json:"user_id"`
- MachineNameId string `json:"machine_name_id"`
- UserAgentId string `json:"user_agent_id"`
+ Id string `json:"id"`
+ Branch string `json:"branch"`
+ Category string `json:"category"`
+ Entity string `json:"entity"`
+ IsWrite bool `json:"is_write"`
+ Language string `json:"language"`
+ Project string `json:"project"`
+ Time models.CustomTime `json:"time"`
+ Type string `json:"type"`
+ UserId string `json:"user_id"`
+ MachineNameId string `json:"machine_name_id"`
+ UserAgentId string `json:"user_agent_id"`
}
diff --git a/models/compat/wakatime/v1/stats.go b/models/compat/wakatime/v1/stats.go
new file mode 100644
index 0000000..3690316
--- /dev/null
+++ b/models/compat/wakatime/v1/stats.go
@@ -0,0 +1,78 @@
+package v1
+
+import (
+ "github.com/muety/wakapi/models"
+ "time"
+)
+
+// https://wakatime.com/api/v1/users/current/stats/last_7_days
+// https://pastr.de/p/f2fxg6ragj7z5e7fhsow9rb6
+
+type StatsViewModel struct {
+ Data *StatsData `json:"data"`
+}
+
+type StatsData struct {
+ Username string `json:"username"`
+ UserId string `json:"user_id"`
+ Start time.Time `json:"start"`
+ End time.Time `json:"end"`
+ TotalSeconds float64 `json:"total_seconds"`
+ DailyAverage float64 `json:"daily_average"`
+ DaysIncludingHolidays int `json:"days_including_holidays"`
+ Editors []*SummariesEntry `json:"editors"`
+ Languages []*SummariesEntry `json:"languages"`
+ Machines []*SummariesEntry `json:"machines"`
+ Projects []*SummariesEntry `json:"projects"`
+ OperatingSystems []*SummariesEntry `json:"operating_systems"`
+}
+
+func NewStatsFrom(summary *models.Summary, filters *models.Filters) *StatsViewModel {
+ totalTime := summary.TotalTime()
+ numDays := int(summary.ToTime.T().Sub(summary.FromTime.T()).Hours() / 24)
+
+ data := &StatsData{
+ Username: summary.UserID,
+ UserId: summary.UserID,
+ Start: summary.FromTime.T(),
+ End: summary.ToTime.T(),
+ TotalSeconds: totalTime.Seconds(),
+ DailyAverage: totalTime.Seconds() / float64(numDays),
+ DaysIncludingHolidays: numDays,
+ }
+
+ editors := make([]*SummariesEntry, len(summary.Editors))
+ for i, e := range summary.Editors {
+ editors[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryEditor))
+ }
+
+ languages := make([]*SummariesEntry, len(summary.Languages))
+ for i, e := range summary.Languages {
+ languages[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryLanguage))
+ }
+
+ machines := make([]*SummariesEntry, len(summary.Machines))
+ for i, e := range summary.Machines {
+ machines[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryMachine))
+ }
+
+ projects := make([]*SummariesEntry, len(summary.Projects))
+ for i, e := range summary.Projects {
+ projects[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryProject))
+ }
+
+ oss := make([]*SummariesEntry, len(summary.OperatingSystems))
+ for i, e := range summary.OperatingSystems {
+ oss[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryOS))
+ }
+
+ data.Editors = editors
+ data.Languages = languages
+ data.Machines = machines
+ data.Projects = projects
+ data.OperatingSystems = oss
+
+ return &StatsViewModel{
+ Data: data,
+ }
+}
diff --git a/models/compat/wakatime/v1/user_agent.go b/models/compat/wakatime/v1/user_agent.go
index a73a755..af427bd 100644
--- a/models/compat/wakatime/v1/user_agent.go
+++ b/models/compat/wakatime/v1/user_agent.go
@@ -5,8 +5,8 @@ type UserAgentsViewModel struct {
}
type UserAgentEntry struct {
- Id string
- Editor string
- Os string
- Value string
+ Id string `json:"id"`
+ Editor string `json:"editor"`
+ Os string `json:"os"`
+ Value string `json:"value"`
}
diff --git a/models/summary.go b/models/summary.go
index 03b7323..2d8a723 100644
--- a/models/summary.go
+++ b/models/summary.go
@@ -16,13 +16,13 @@ const (
const (
IntervalToday string = "today"
- IntervalYesterday string = "day"
+ IntervalYesterday string = "yesterday"
IntervalThisWeek string = "week"
IntervalThisMonth string = "month"
IntervalThisYear string = "year"
- IntervalPast7Days string = "7_days"
- IntervalPast30Days string = "30_days"
- IntervalPast12Months string = "12_months"
+ IntervalPast7Days string = "last_7_days"
+ IntervalPast30Days string = "last_30_days"
+ IntervalPast12Months string = "last_12_months"
IntervalAny string = "any"
// https://wakatime.com/developers/#summaries
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
new file mode 100644
index 0000000..c8d931f
--- /dev/null
+++ b/routes/compat/wakatime/v1/stats.go
@@ -0,0 +1,83 @@
+package v1
+
+import (
+ "errors"
+ "github.com/gorilla/mux"
+ conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/models"
+ v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
+ "github.com/muety/wakapi/services"
+ "github.com/muety/wakapi/utils"
+ "net/http"
+ "time"
+)
+
+type StatsHandler struct {
+ config *conf.Config
+ summarySrvc services.ISummaryService
+}
+
+func NewStatsHandler(summaryService services.ISummaryService) *StatsHandler {
+ return &StatsHandler{
+ summarySrvc: summaryService,
+ config: conf.Get(),
+ }
+}
+
+func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
+ router.Path("/wakatime/v1/users/{user}/stats/{range}").Methods(http.MethodGet).HandlerFunc(h.Get)
+}
+
+// TODO: support filtering (requires https://github.com/muety/wakapi/issues/108)
+
+func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ requestedUser := vars["user"]
+ requestedRange := vars["range"]
+
+ user := r.Context().Value(models.UserKey).(*models.User)
+
+ if requestedUser != user.ID && requestedUser != "current" {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ summary, err, status := h.loadUserSummary(user, requestedRange)
+ if err != nil {
+ w.WriteHeader(status)
+ w.Write([]byte(err.Error()))
+ return
+ }
+
+ filters := &models.Filters{}
+ if projectQuery := r.URL.Query().Get("project"); projectQuery != "" {
+ filters.Project = projectQuery
+ }
+
+ vm := v1.NewStatsFrom(summary, filters)
+ utils.RespondJSON(w, http.StatusOK, vm)
+}
+
+func (h *StatsHandler) loadUserSummary(user *models.User, rangeKey string) (*models.Summary, error, int) {
+ var start, end time.Time
+
+ if err, parsedFrom, parsedTo := utils.ResolveInterval(rangeKey); err == nil {
+ start, end = parsedFrom, parsedTo
+ } else {
+ return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
+ }
+
+ overallParams := &models.SummaryParams{
+ From: start,
+ To: end,
+ User: user,
+ Recompute: false,
+ }
+
+ summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve)
+ if err != nil {
+ return nil, err, http.StatusInternalServerError
+ }
+
+ return summary, nil, http.StatusOK
+}
diff --git a/views/index.tpl.html b/views/index.tpl.html
index ffb1a71..f4073e5 100644
--- a/views/index.tpl.html
+++ b/views/index.tpl.html
@@ -22,7 +22,7 @@
class="text-green-700">Your Coding Time 🕓
Wakapi is an open-source tool that helps you keep track of the
time you have spent coding on different projects in different programming languages and more. Ideal for
- statistics freaks any anyone else.
+ statistics freaks and anyone else.
💡 The system has tracked a total of
diff --git a/views/summary.tpl.html b/views/summary.tpl.html
index b724a3a..313847a 100644
--- a/views/summary.tpl.html
+++ b/views/summary.tpl.html
@@ -41,13 +41,13 @@
From 8fed606e9bacf28d79818fea4985bc9b086fcbc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 19:52:50 +0100
Subject: [PATCH 07/15] refactor: make intervals be string lists of aliases
---
models/interval.go | 12 ++++++
models/summary.go | 56 ++++++++++++++------------
routes/compat/shields/v1/badge.go | 6 ++-
routes/compat/wakatime/v1/stats.go | 2 +-
routes/compat/wakatime/v1/summaries.go | 4 +-
utils/summary.go | 41 +++++++++++++------
6 files changed, 78 insertions(+), 43 deletions(-)
create mode 100644 models/interval.go
diff --git a/models/interval.go b/models/interval.go
new file mode 100644
index 0000000..51a8e91
--- /dev/null
+++ b/models/interval.go
@@ -0,0 +1,12 @@
+package models
+
+type IntervalKey []string
+
+func (k *IntervalKey) HasAlias(s string) bool {
+ for _, e := range *k {
+ if e == s {
+ return true
+ }
+ }
+ return false
+}
diff --git a/models/summary.go b/models/summary.go
index 2d8a723..7cae400 100644
--- a/models/summary.go
+++ b/models/summary.go
@@ -14,34 +14,38 @@ const (
SummaryMachine uint8 = 4
)
-const (
- IntervalToday string = "today"
- IntervalYesterday string = "yesterday"
- IntervalThisWeek string = "week"
- IntervalThisMonth string = "month"
- IntervalThisYear string = "year"
- IntervalPast7Days string = "last_7_days"
- IntervalPast30Days string = "last_30_days"
- IntervalPast12Months string = "last_12_months"
- IntervalAny string = "any"
-
- // https://wakatime.com/developers/#summaries
- IntervalWakatimeToday string = "Today"
- IntervalWakatimeYesterday string = "Yesterday"
- IntervalWakatimeLast7Days string = "Last 7 Days"
- IntervalWakatimeLast7DaysYesterday string = "Last 7 Days from Yesterday"
- IntervalWakatimeLast14Days string = "Last 14 Days"
- IntervalWakatimeLast30Days string = "Last 30 Days"
- IntervalWakatimeThisWeek string = "This Week"
- IntervalWakatimeLastWeek string = "Last Week"
- IntervalWakatimeThisMonth string = "This Month"
- IntervalWakatimeLastMonth string = "Last Month"
+// Support Wakapi and WakaTime range / interval identifiers
+// See https://wakatime.com/developers/#summaries
+var (
+ IntervalToday = &IntervalKey{"today", "Today"}
+ IntervalYesterday = &IntervalKey{"day", "yesterday", "Yesterday"}
+ IntervalThisWeek = &IntervalKey{"week", "This Week"}
+ IntervalLastWeek = &IntervalKey{"Last Week"}
+ IntervalThisMonth = &IntervalKey{"month", "This Month"}
+ IntervalLastMonth = &IntervalKey{"Last Month"}
+ IntervalThisYear = &IntervalKey{"year"}
+ IntervalPast7Days = &IntervalKey{"7_days", "last_7_days", "Last 7 Days"}
+ IntervalPast7DaysYesterday = &IntervalKey{"Last 7 Days from Yesterday"}
+ IntervalPast14Days = &IntervalKey{"Last 14 Days"}
+ IntervalPast30Days = &IntervalKey{"30_days", "last_30_days", "Last 30 Days"}
+ IntervalPast12Months = &IntervalKey{"12_months", "last_12_months"}
+ IntervalAny = &IntervalKey{"any"}
)
-func Intervals() []string {
- return []string{
- IntervalToday, IntervalYesterday, IntervalThisWeek, IntervalThisMonth, IntervalThisYear, IntervalPast7Days, IntervalPast30Days, IntervalPast12Months, IntervalAny,
- }
+var AllIntervals = []*IntervalKey{
+ IntervalToday,
+ IntervalYesterday,
+ IntervalThisWeek,
+ IntervalLastWeek,
+ IntervalThisMonth,
+ IntervalLastMonth,
+ IntervalThisYear,
+ IntervalPast7Days,
+ IntervalPast7DaysYesterday,
+ IntervalPast14Days,
+ IntervalPast30Days,
+ IntervalPast12Months,
+ IntervalAny,
}
const UnknownSummaryKey = "unknown"
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index 9c615da..16431b4 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -59,7 +59,9 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
var interval = models.IntervalPast30Days
if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 {
- interval = groups[1]
+ if i, err := utils.ParseInterval(groups[1]); err == nil {
+ interval = i
+ }
}
var filters *models.Filters
@@ -89,7 +91,7 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
utils.RespondJSON(w, http.StatusOK, vm)
}
-func (h *BadgeHandler) loadUserSummary(user *models.User, interval string) (*models.Summary, error, int) {
+func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey) (*models.Summary, error, int) {
err, from, to := utils.ResolveInterval(interval)
if err != nil {
return nil, err, http.StatusBadRequest
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
index c8d931f..0d38875 100644
--- a/routes/compat/wakatime/v1/stats.go
+++ b/routes/compat/wakatime/v1/stats.go
@@ -61,7 +61,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
func (h *StatsHandler) loadUserSummary(user *models.User, rangeKey string) (*models.Summary, error, int) {
var start, end time.Time
- if err, parsedFrom, parsedTo := utils.ResolveInterval(rangeKey); err == nil {
+ if err, parsedFrom, parsedTo := utils.ResolveIntervalRaw(rangeKey); err == nil {
start, end = parsedFrom, parsedTo
} else {
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go
index a27e9fe..34bc800 100644
--- a/routes/compat/wakatime/v1/summaries.go
+++ b/routes/compat/wakatime/v1/summaries.go
@@ -68,12 +68,12 @@ 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.ResolveInterval(rangeParam); err == nil {
+ if err, parsedFrom, parsedTo := utils.ResolveIntervalRaw(rangeParam); err == nil {
start, end = parsedFrom, parsedTo
} else {
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
}
- } else if err, parsedFrom, parsedTo := utils.ResolveInterval(startParam); err == nil && startParam == endParam {
+ } else if err, parsedFrom, parsedTo := utils.ResolveIntervalRaw(startParam); err == nil && startParam == endParam {
// also accept start param to be a range param
start, end = parsedFrom, parsedTo
} else {
diff --git a/utils/summary.go b/utils/summary.go
index f035d7b..3b99c8a 100644
--- a/utils/summary.go
+++ b/utils/summary.go
@@ -7,35 +7,52 @@ import (
"time"
)
-func ResolveInterval(interval string) (err error, from, to time.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 ResolveIntervalRaw(interval string) (err error, from, to time.Time) {
+ parsed, err := ParseInterval(interval)
+ if err != nil {
+ return err, time.Time{}, time.Time{}
+ }
+ return ResolveInterval(parsed)
+}
+
+func ResolveInterval(interval *models.IntervalKey) (err error, from, to time.Time) {
to = time.Now()
switch interval {
- case models.IntervalToday, models.IntervalWakatimeToday:
+ case models.IntervalToday:
from = StartOfToday()
- case models.IntervalYesterday, models.IntervalWakatimeYesterday:
+ case models.IntervalYesterday:
from = StartOfToday().Add(-24 * time.Hour)
to = StartOfToday()
- case models.IntervalThisWeek, models.IntervalWakatimeThisWeek:
+ case models.IntervalThisWeek:
from = StartOfWeek()
- case models.IntervalWakatimeLastWeek:
+ case models.IntervalLastWeek:
from = StartOfWeek().AddDate(0, 0, -7)
to = StartOfWeek()
- case models.IntervalThisMonth, models.IntervalWakatimeThisMonth:
+ case models.IntervalThisMonth:
from = StartOfMonth()
- case models.IntervalWakatimeLastMonth:
+ case models.IntervalLastMonth:
from = StartOfMonth().AddDate(0, -1, 0)
to = StartOfMonth()
case models.IntervalThisYear:
from = StartOfYear()
- case models.IntervalPast7Days, models.IntervalWakatimeLast7Days:
+ case models.IntervalPast7Days:
from = StartOfToday().AddDate(0, 0, -7)
- case models.IntervalWakatimeLast7DaysYesterday:
+ case models.IntervalPast7DaysYesterday:
from = StartOfToday().AddDate(0, 0, -1).AddDate(0, 0, -7)
to = StartOfToday().AddDate(0, 0, -1)
- case models.IntervalWakatimeLast14Days:
+ case models.IntervalPast14Days:
from = StartOfToday().AddDate(0, 0, -14)
- case models.IntervalPast30Days, models.IntervalWakatimeLast30Days:
+ case models.IntervalPast30Days:
from = StartOfToday().AddDate(0, 0, -30)
case models.IntervalPast12Months:
from = StartOfToday().AddDate(0, -12, 0)
@@ -56,7 +73,7 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
var from, to time.Time
if interval := params.Get("interval"); interval != "" {
- err, from, to = ResolveInterval(interval)
+ err, from, to = ResolveIntervalRaw(interval)
} else {
from, err = ParseDate(params.Get("from"))
if err != nil {
From d1dc73b5e6af8d3632b7c759f51aea50a9279bcd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 20:09:08 +0100
Subject: [PATCH 08/15] refactor: make each router handler register middleware
on its own
---
main.go | 13 ++++----
middlewares/authenticate.go | 41 ++++++++++++++++----------
routes/api/heartbeat.go | 6 +++-
routes/api/summary.go | 8 ++++-
routes/compat/shields/v1/badge.go | 1 +
routes/compat/wakatime/v1/all_time.go | 11 +++++--
routes/compat/wakatime/v1/stats.go | 11 +++++--
routes/compat/wakatime/v1/summaries.go | 11 +++++--
routes/settings.go | 2 +-
routes/summary.go | 2 +-
10 files changed, 74 insertions(+), 32 deletions(-)
diff --git a/main.go b/main.go
index effd6b5..cb0d013 100644
--- a/main.go
+++ b/main.go
@@ -126,13 +126,13 @@ func main() {
// API Handlers
healthApiHandler := api.NewHealthApiHandler(db)
- heartbeatApiHandler := api.NewHeartbeatApiHandler(heartbeatService, languageMappingService)
- summaryApiHandler := api.NewSummaryApiHandler(summaryService)
+ heartbeatApiHandler := api.NewHeartbeatApiHandler(userService, heartbeatService, languageMappingService)
+ summaryApiHandler := api.NewSummaryApiHandler(userService, summaryService)
// Compat Handlers
- wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(summaryService)
- wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(summaryService)
- wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(summaryService)
+ wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(userService, summaryService)
+ wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(userService, summaryService)
+ wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(userService, summaryService)
shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService)
// MVC Handlers
@@ -152,11 +152,10 @@ func main() {
recoveryMiddleware := handlers.RecoveryHandler()
loggingMiddleware := middlewares.NewLoggingMiddleware(log.New(os.Stdout, "", log.LstdFlags))
corsMiddleware := handlers.CORS()
- authenticateMiddleware := middlewares.NewAuthenticateMiddleware(userService, []string{"/api/health", "/api/compat/shields/v1"}).Handler
// Router configs
router.Use(loggingMiddleware, recoveryMiddleware)
- apiRouter.Use(corsMiddleware, authenticateMiddleware)
+ apiRouter.Use(corsMiddleware)
// Route registrations
homeHandler.RegisterRoutes(rootRouter)
diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go
index 4939e63..311db11 100644
--- a/middlewares/authenticate.go
+++ b/middlewares/authenticate.go
@@ -12,19 +12,23 @@ import (
)
type AuthenticateMiddleware struct {
- config *conf.Config
- userSrvc services.IUserService
- whitelistPaths []string
+ config *conf.Config
+ userSrvc services.IUserService
+ optionalForPaths []string
}
-func NewAuthenticateMiddleware(userService services.IUserService, whitelistPaths []string) *AuthenticateMiddleware {
+func NewAuthenticateMiddleware(userService services.IUserService) *AuthenticateMiddleware {
return &AuthenticateMiddleware{
- config: conf.Get(),
- userSrvc: userService,
- whitelistPaths: whitelistPaths,
+ config: conf.Get(),
+ userSrvc: userService,
+ optionalForPaths: []string{},
}
}
+func (m *AuthenticateMiddleware) WithOptionalFor(paths []string) {
+ m.optionalForPaths = paths
+}
+
func (m *AuthenticateMiddleware) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.ServeHTTP(w, r, h.ServeHTTP)
@@ -32,13 +36,6 @@ func (m *AuthenticateMiddleware) Handler(h http.Handler) http.Handler {
}
func (m *AuthenticateMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
- for _, p := range m.whitelistPaths {
- if strings.HasPrefix(r.URL.Path, p) || r.URL.Path == p {
- next(w, r)
- return
- }
- }
-
var user *models.User
user, err := m.tryGetUserByCookie(r)
@@ -46,7 +43,12 @@ func (m *AuthenticateMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
user, err = m.tryGetUserByApiKey(r)
}
- if err != nil {
+ if err != nil || user == nil {
+ if m.isOptional(r.URL.Path) {
+ next(w, r)
+ return
+ }
+
if strings.HasPrefix(r.URL.Path, "/api") {
w.WriteHeader(http.StatusUnauthorized)
} else {
@@ -60,6 +62,15 @@ func (m *AuthenticateMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
next(w, r.WithContext(ctx))
}
+func (m *AuthenticateMiddleware) isOptional(requestPath string) bool {
+ for _, p := range m.optionalForPaths {
+ if strings.HasPrefix(requestPath, p) || requestPath == p {
+ return true
+ }
+ }
+ return false
+}
+
func (m *AuthenticateMiddleware) tryGetUserByApiKey(r *http.Request) (*models.User, error) {
key, err := utils.ExtractBearerAuth(r)
if err != nil {
diff --git a/routes/api/heartbeat.go b/routes/api/heartbeat.go
index f44088b..56eaa44 100644
--- a/routes/api/heartbeat.go
+++ b/routes/api/heartbeat.go
@@ -5,6 +5,7 @@ import (
"github.com/emvi/logbuch"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/middlewares"
customMiddleware "github.com/muety/wakapi/middlewares/custom"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
@@ -15,13 +16,15 @@ import (
type HeartbeatApiHandler struct {
config *conf.Config
+ userSrvc services.IUserService
heartbeatSrvc services.IHeartbeatService
languageMappingSrvc services.ILanguageMappingService
}
-func NewHeartbeatApiHandler(heartbeatService services.IHeartbeatService, languageMappingService services.ILanguageMappingService) *HeartbeatApiHandler {
+func NewHeartbeatApiHandler(userService services.IUserService, heartbeatService services.IHeartbeatService, languageMappingService services.ILanguageMappingService) *HeartbeatApiHandler {
return &HeartbeatApiHandler{
config: conf.Get(),
+ userSrvc: userService,
heartbeatSrvc: heartbeatService,
languageMappingSrvc: languageMappingService,
}
@@ -34,6 +37,7 @@ type heartbeatResponseVm struct {
func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/heartbeat").Subrouter()
r.Use(
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
customMiddleware.NewWakatimeRelayMiddleware().Handler,
)
r.Methods(http.MethodPost).HandlerFunc(h.Post)
diff --git a/routes/api/summary.go b/routes/api/summary.go
index 8b39970..0e3c927 100644
--- a/routes/api/summary.go
+++ b/routes/api/summary.go
@@ -3,6 +3,7 @@ package api
import (
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/middlewares"
su "github.com/muety/wakapi/routes/utils"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
@@ -11,18 +12,23 @@ import (
type SummaryApiHandler struct {
config *conf.Config
+ userSrvc services.IUserService
summarySrvc services.ISummaryService
}
-func NewSummaryApiHandler(summaryService services.ISummaryService) *SummaryApiHandler {
+func NewSummaryApiHandler(userService services.IUserService, summaryService services.ISummaryService) *SummaryApiHandler {
return &SummaryApiHandler{
summarySrvc: summaryService,
+ userSrvc: userService,
config: conf.Get(),
}
}
func (h *SummaryApiHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/summary").Subrouter()
+ r.Use(
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
+ )
r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index 16431b4..6e43ab2 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -32,6 +32,7 @@ func NewBadgeHandler(summaryService services.ISummaryService, userService servic
}
func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
+ // no auth middleware here, handler itself resolves the user
r := router.PathPrefix("/shields/v1/{user}").Subrouter()
r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
diff --git a/routes/compat/wakatime/v1/all_time.go b/routes/compat/wakatime/v1/all_time.go
index d78e68e..89b605d 100644
--- a/routes/compat/wakatime/v1/all_time.go
+++ b/routes/compat/wakatime/v1/all_time.go
@@ -3,6 +3,7 @@ package v1
import (
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/services"
@@ -14,18 +15,24 @@ import (
type AllTimeHandler struct {
config *conf.Config
+ userSrvc services.IUserService
summarySrvc services.ISummaryService
}
-func NewAllTimeHandler(summaryService services.ISummaryService) *AllTimeHandler {
+func NewAllTimeHandler(userService services.IUserService, summaryService services.ISummaryService) *AllTimeHandler {
return &AllTimeHandler{
+ userSrvc: userService,
summarySrvc: summaryService,
config: conf.Get(),
}
}
func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) {
- router.Path("/wakatime/v1/users/{user}/all_time_since_today").Methods(http.MethodGet).HandlerFunc(h.Get)
+ r := router.PathPrefix("/wakatime/v1/users/{user}/all_time_since_today").Subrouter()
+ r.Use(
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
+ )
+ r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
index 0d38875..02925cb 100644
--- a/routes/compat/wakatime/v1/stats.go
+++ b/routes/compat/wakatime/v1/stats.go
@@ -4,6 +4,7 @@ import (
"errors"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/services"
@@ -14,18 +15,24 @@ import (
type StatsHandler struct {
config *conf.Config
+ userSrvc services.IUserService
summarySrvc services.ISummaryService
}
-func NewStatsHandler(summaryService services.ISummaryService) *StatsHandler {
+func NewStatsHandler(userService services.IUserService, summaryService services.ISummaryService) *StatsHandler {
return &StatsHandler{
+ userSrvc: userService,
summarySrvc: summaryService,
config: conf.Get(),
}
}
func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
- router.Path("/wakatime/v1/users/{user}/stats/{range}").Methods(http.MethodGet).HandlerFunc(h.Get)
+ r := router.PathPrefix("/wakatime/v1/users/{user}/stats/{range}").Subrouter()
+ r.Use(
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
+ )
+ r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
// TODO: support filtering (requires https://github.com/muety/wakapi/issues/108)
diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go
index 34bc800..5a331f9 100644
--- a/routes/compat/wakatime/v1/summaries.go
+++ b/routes/compat/wakatime/v1/summaries.go
@@ -4,6 +4,7 @@ import (
"errors"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/services"
@@ -15,18 +16,24 @@ import (
type SummariesHandler struct {
config *conf.Config
+ userSrvc services.IUserService
summarySrvc services.ISummaryService
}
-func NewSummariesHandler(summaryService services.ISummaryService) *SummariesHandler {
+func NewSummariesHandler(userService services.IUserService, summaryService services.ISummaryService) *SummariesHandler {
return &SummariesHandler{
+ userSrvc: userService,
summarySrvc: summaryService,
config: conf.Get(),
}
}
func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
- router.Path("/wakatime/v1/users/{user}/summaries").Methods(http.MethodGet).HandlerFunc(h.Get)
+ r := router.PathPrefix("/wakatime/v1/users/{user}/summaries").Subrouter()
+ r.Use(
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
+ )
+ r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
// TODO: Support parameters: project, branches, timeout, writes_only, timezone
diff --git a/routes/settings.go b/routes/settings.go
index 2de69c8..b3a6f2a 100644
--- a/routes/settings.go
+++ b/routes/settings.go
@@ -57,7 +57,7 @@ func NewSettingsHandler(
func (h *SettingsHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/settings").Subrouter()
r.Use(
- middlewares.NewAuthenticateMiddleware(h.userSrvc, []string{}).Handler,
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
)
r.Methods(http.MethodGet).HandlerFunc(h.GetIndex)
r.Methods(http.MethodPost).HandlerFunc(h.PostIndex)
diff --git a/routes/summary.go b/routes/summary.go
index 42ec1f3..fcf8dd5 100644
--- a/routes/summary.go
+++ b/routes/summary.go
@@ -29,7 +29,7 @@ func NewSummaryHandler(summaryService services.ISummaryService, userService serv
func (h *SummaryHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/summary").Subrouter()
r.Use(
- middlewares.NewAuthenticateMiddleware(h.userSrvc, []string{}).Handler,
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
)
r.Methods(http.MethodGet).HandlerFunc(h.GetIndex)
}
From fca12f522fc317c2adace344c007023150774e6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 22:32:03 +0100
Subject: [PATCH 09/15] feat: option to publicly share stats data (resolve #36)
---
middlewares/authenticate.go | 3 +-
middlewares/authenticate_test.go | 4 +-
...06_drop_badges_column_add_sharing_flags.go | 51 ++
mocks/user_service.go | 4 +
models/user.go | 19 +-
repositories/user.go | 15 +-
routes/compat/shields/v1/badge.go | 23 +-
routes/compat/wakatime/v1/stats.go | 72 +-
routes/settings.go | 50 +-
services/services.go | 2 +-
services/user.go | 9 +-
views/settings.tpl.html | 767 ++++++++++--------
12 files changed, 605 insertions(+), 414 deletions(-)
create mode 100644 migrations/20210206_drop_badges_column_add_sharing_flags.go
diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go
index 311db11..2e77d29 100644
--- a/middlewares/authenticate.go
+++ b/middlewares/authenticate.go
@@ -25,8 +25,9 @@ func NewAuthenticateMiddleware(userService services.IUserService) *AuthenticateM
}
}
-func (m *AuthenticateMiddleware) WithOptionalFor(paths []string) {
+func (m *AuthenticateMiddleware) WithOptionalFor(paths []string) *AuthenticateMiddleware {
m.optionalForPaths = paths
+ return m
}
func (m *AuthenticateMiddleware) Handler(h http.Handler) http.Handler {
diff --git a/middlewares/authenticate_test.go b/middlewares/authenticate_test.go
index 960d269..2d4201f 100644
--- a/middlewares/authenticate_test.go
+++ b/middlewares/authenticate_test.go
@@ -24,7 +24,7 @@ func TestAuthenticateMiddleware_tryGetUserByApiKey_Success(t *testing.T) {
userServiceMock := new(mocks.UserServiceMock)
userServiceMock.On("GetUserByKey", testApiKey).Return(testUser, nil)
- sut := NewAuthenticateMiddleware(userServiceMock, []string{})
+ sut := NewAuthenticateMiddleware(userServiceMock)
result, err := sut.tryGetUserByApiKey(mockRequest)
@@ -45,7 +45,7 @@ func TestAuthenticateMiddleware_tryGetUserByApiKey_InvalidHeader(t *testing.T) {
userServiceMock := new(mocks.UserServiceMock)
- sut := NewAuthenticateMiddleware(userServiceMock, []string{})
+ sut := NewAuthenticateMiddleware(userServiceMock)
result, err := sut.tryGetUserByApiKey(mockRequest)
diff --git a/migrations/20210206_drop_badges_column_add_sharing_flags.go b/migrations/20210206_drop_badges_column_add_sharing_flags.go
new file mode 100644
index 0000000..aa1008a
--- /dev/null
+++ b/migrations/20210206_drop_badges_column_add_sharing_flags.go
@@ -0,0 +1,51 @@
+package migrations
+
+import (
+ "github.com/emvi/logbuch"
+ "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/models"
+ "gorm.io/gorm"
+)
+
+func init() {
+ f := migrationFunc{
+ name: "20210206_drop_badges_column_add_sharing_flags",
+ f: func(db *gorm.DB, cfg *config.Config) error {
+ migrator := db.Migrator()
+
+ if !migrator.HasColumn(&models.User{}, "badges_enabled") {
+ // empty database, nothing to migrate
+ return nil
+ }
+
+ if err := db.Exec("UPDATE users SET share_data_max_days = 30 WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+ if err := db.Exec("UPDATE users SET share_editors = TRUE WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+ if err := db.Exec("UPDATE users SET share_languages = TRUE WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+ if err := db.Exec("UPDATE users SET share_projects = TRUE WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+ if err := db.Exec("UPDATE users SET share_oss = TRUE WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+ if err := db.Exec("UPDATE users SET share_machines = TRUE WHERE badges_enabled = TRUE").Error; err != nil {
+ return err
+ }
+
+ if err := migrator.DropColumn(&models.User{}, "badges_enabled"); err != nil {
+ return err
+ } else {
+ logbuch.Info("dropped column 'badges_enabled' after substituting it by sharing indicators")
+ }
+
+ return nil
+ },
+ }
+
+ registerPostMigration(f)
+}
diff --git a/mocks/user_service.go b/mocks/user_service.go
index a8ccd2d..c6bca7e 100644
--- a/mocks/user_service.go
+++ b/mocks/user_service.go
@@ -58,3 +58,7 @@ func (m *UserServiceMock) MigrateMd5Password(user *models.User, login *models.Lo
args := m.Called(user, login)
return args.Get(0).(*models.User), args.Error(1)
}
+
+func (m *UserServiceMock) FlushCache() {
+ m.Called()
+}
diff --git a/models/user.go b/models/user.go
index 50aff5f..fa37fe1 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1,13 +1,18 @@
package models
type User struct {
- ID string `json:"id" gorm:"primary_key"`
- ApiKey string `json:"api_key" gorm:"unique"`
- Password string `json:"-"`
- CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
- LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
- BadgesEnabled bool `json:"-" gorm:"default:false; type:bool"`
- WakatimeApiKey string `json:"-"`
+ ID string `json:"id" gorm:"primary_key"`
+ ApiKey string `json:"api_key" gorm:"unique"`
+ Password string `json:"-"`
+ CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
+ LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
+ ShareDataMaxDays uint `json:"-" gorm:"default:0"`
+ ShareEditors bool `json:"-" gorm:"default:false; type:bool"`
+ ShareLanguages bool `json:"-" gorm:"default:false; type:bool"`
+ ShareProjects bool `json:"-" gorm:"default:false; type:bool"`
+ ShareOSs bool `json:"-" gorm:"default:false; type:bool; column:share_oss"`
+ ShareMachines bool `json:"-" gorm:"default:false; type:bool"`
+ WakatimeApiKey string `json:"-"`
}
type Login struct {
diff --git a/repositories/user.go b/repositories/user.go
index a210802..43e080d 100644
--- a/repositories/user.go
+++ b/repositories/user.go
@@ -54,7 +54,20 @@ func (r *UserRepository) InsertOrGet(user *models.User) (*models.User, bool, err
}
func (r *UserRepository) Update(user *models.User) (*models.User, error) {
- result := r.db.Model(user).Updates(user)
+ updateMap := map[string]interface{}{
+ "api_key": user.ApiKey,
+ "password": user.Password,
+ "last_logged_in_at": user.LastLoggedInAt,
+ "share_data_max_days": user.ShareDataMaxDays,
+ "share_editors": user.ShareEditors,
+ "share_languages": user.ShareLanguages,
+ "share_oss": user.ShareOSs,
+ "share_projects": user.ShareProjects,
+ "share_machines": user.ShareMachines,
+ "wakatime_api_key": user.WakatimeApiKey,
+ }
+
+ result := r.db.Model(user).Updates(updateMap)
if err := result.Error; err != nil {
return nil, err
}
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index 6e43ab2..8591d4e 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -10,6 +10,7 @@ import (
"net/http"
"regexp"
"strings"
+ "time"
)
const (
@@ -46,13 +47,6 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
return
}
- requestedUserId := mux.Vars(r)["user"]
- user, err := h.userSrvc.GetUserById(requestedUserId)
- if err != nil || !user.BadgesEnabled {
- w.WriteHeader(http.StatusUnauthorized)
- return
- }
-
var filterEntity, filterKey string
if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
filterEntity, filterKey = groups[1], groups[2]
@@ -65,6 +59,21 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
}
}
+ requestedUserId := mux.Vars(r)["user"]
+ user, err := h.userSrvc.GetUserById(requestedUserId)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ _, rangeFrom, rangeTo := utils.ResolveInterval(interval)
+ minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(user.ShareDataMaxDays)))
+ if rangeFrom.Before(minStart) {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("requested time range too broad"))
+ return
+ }
+
var filters *models.Filters
switch filterEntity {
case "project":
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
index 02925cb..3d0335d 100644
--- a/routes/compat/wakatime/v1/stats.go
+++ b/routes/compat/wakatime/v1/stats.go
@@ -1,7 +1,6 @@
package v1
import (
- "errors"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/middlewares"
@@ -30,7 +29,7 @@ func NewStatsHandler(userService services.IUserService, summaryService services.
func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/wakatime/v1/users/{user}/stats/{range}").Subrouter()
r.Use(
- middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
+ middlewares.NewAuthenticateMiddleware(h.userSrvc).WithOptionalFor([]string{"/"}).Handler,
)
r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
@@ -38,42 +37,69 @@ func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
// TODO: support filtering (requires https://github.com/muety/wakapi/issues/108)
func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- requestedUser := vars["user"]
- requestedRange := vars["range"]
+ var vars = mux.Vars(r)
+ var authorizedUser, requestedUser *models.User
- user := r.Context().Value(models.UserKey).(*models.User)
+ if u := r.Context().Value(models.UserKey); u != nil {
+ authorizedUser = u.(*models.User)
+ }
- if requestedUser != user.ID && requestedUser != "current" {
- w.WriteHeader(http.StatusForbidden)
+ if authorizedUser != nil && vars["user"] == "current" {
+ vars["user"] = authorizedUser.ID
+ }
+
+ requestedUser, err := h.userSrvc.GetUserById(vars["user"])
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ w.Write([]byte("user not found"))
return
}
- summary, err, status := h.loadUserSummary(user, requestedRange)
+ err, rangeFrom, rangeTo := utils.ResolveIntervalRaw(vars["range"])
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("invalid range"))
+ return
+ }
+
+ minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(requestedUser.ShareDataMaxDays)))
+ if (authorizedUser == nil || requestedUser.ID != authorizedUser.ID) &&
+ (requestedUser.ShareDataMaxDays == 0 || rangeFrom.Before(minStart)) {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("requested time range too broad"))
+ return
+ }
+
+ summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo)
if err != nil {
w.WriteHeader(status)
w.Write([]byte(err.Error()))
return
}
- filters := &models.Filters{}
- if projectQuery := r.URL.Query().Get("project"); projectQuery != "" {
- filters.Project = projectQuery
+ stats := v1.NewStatsFrom(summary, &models.Filters{})
+
+ // post filter stats according to user's given sharing permissions
+ if !requestedUser.ShareEditors {
+ stats.Data.Editors = nil
+ }
+ if !requestedUser.ShareLanguages {
+ stats.Data.Languages = nil
+ }
+ if !requestedUser.ShareProjects {
+ stats.Data.Projects = nil
+ }
+ if !requestedUser.ShareOSs {
+ stats.Data.OperatingSystems = nil
+ }
+ if !requestedUser.ShareMachines {
+ stats.Data.Machines = nil
}
- vm := v1.NewStatsFrom(summary, filters)
- utils.RespondJSON(w, http.StatusOK, vm)
+ utils.RespondJSON(w, http.StatusOK, stats)
}
-func (h *StatsHandler) loadUserSummary(user *models.User, rangeKey string) (*models.Summary, error, int) {
- var start, end time.Time
-
- if err, parsedFrom, parsedTo := utils.ResolveIntervalRaw(rangeKey); err == nil {
- start, end = parsedFrom, parsedTo
- } else {
- return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
- }
-
+func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time) (*models.Summary, error, int) {
overallParams := &models.SummaryParams{
From: start,
To: end,
diff --git a/routes/settings.go b/routes/settings.go
index b3a6f2a..ca7f771 100644
--- a/routes/settings.go
+++ b/routes/settings.go
@@ -2,6 +2,7 @@ package routes
import (
"encoding/base64"
+ "errors"
"fmt"
"github.com/emvi/logbuch"
"github.com/gorilla/mux"
@@ -127,8 +128,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
return h.actionDeleteLanguageMapping
case "add_mapping":
return h.actionAddLanguageMapping
- case "toggle_badges":
- return h.actionToggleBadges
+ case "update_sharing":
+ return h.actionUpdateSharing
case "toggle_wakatime":
return h.actionSetWakatimeApiKey
case "import_wakatime":
@@ -202,6 +203,38 @@ func (h *SettingsHandler) actionResetApiKey(w http.ResponseWriter, r *http.Reque
return http.StatusOK, msg, ""
}
+func (h *SettingsHandler) actionUpdateSharing(w http.ResponseWriter, r *http.Request) (int, string, string) {
+ if h.config.IsDev() {
+ loadTemplates()
+ }
+
+ var err error
+ user := r.Context().Value(models.UserKey).(*models.User)
+
+ defer h.userSrvc.FlushCache()
+
+ user.ShareProjects, err = strconv.ParseBool(r.PostFormValue("share_projects"))
+ user.ShareLanguages, err = strconv.ParseBool(r.PostFormValue("share_languages"))
+ user.ShareEditors, err = strconv.ParseBool(r.PostFormValue("share_editors"))
+ user.ShareOSs, err = strconv.ParseBool(r.PostFormValue("share_oss"))
+ user.ShareMachines, err = strconv.ParseBool(r.PostFormValue("share_machines"))
+ if v, e := strconv.Atoi(r.PostFormValue("max_days")); e == nil && v >= 0 {
+ user.ShareDataMaxDays = uint(v)
+ } else {
+ err = errors.New("")
+ }
+
+ if err != nil {
+ return http.StatusBadRequest, "", "invalid input"
+ }
+
+ if _, err := h.userSrvc.Update(user); err != nil {
+ return http.StatusInternalServerError, "", "internal sever error"
+ }
+
+ return http.StatusOK, "settings updated", ""
+}
+
func (h *SettingsHandler) actionDeleteAlias(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
@@ -299,19 +332,6 @@ func (h *SettingsHandler) actionAddLanguageMapping(w http.ResponseWriter, r *htt
return http.StatusOK, "mapping added successfully", ""
}
-func (h *SettingsHandler) actionToggleBadges(w http.ResponseWriter, r *http.Request) (int, string, string) {
- if h.config.IsDev() {
- loadTemplates()
- }
-
- user := r.Context().Value(models.UserKey).(*models.User)
- if _, err := h.userSrvc.ToggleBadges(user); err != nil {
- return http.StatusInternalServerError, "", "internal server error"
- }
-
- return http.StatusOK, "", ""
-}
-
func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
diff --git a/services/services.go b/services/services.go
index 9780586..38d9941 100644
--- a/services/services.go
+++ b/services/services.go
@@ -67,7 +67,7 @@ type IUserService interface {
Update(*models.User) (*models.User, error)
Delete(*models.User) error
ResetApiKey(*models.User) (*models.User, error)
- ToggleBadges(*models.User) (*models.User, error)
SetWakatimeApiKey(*models.User, string) (*models.User, error)
MigrateMd5Password(*models.User, *models.Login) (*models.User, error)
+ FlushCache()
}
diff --git a/services/user.go b/services/user.go
index be96baf..e7f3f9f 100644
--- a/services/user.go
+++ b/services/user.go
@@ -83,11 +83,6 @@ func (srv *UserService) ResetApiKey(user *models.User) (*models.User, error) {
return srv.Update(user)
}
-func (srv *UserService) ToggleBadges(user *models.User) (*models.User, error) {
- srv.cache.Flush()
- return srv.repository.UpdateField(user, "badges_enabled", !user.BadgesEnabled)
-}
-
func (srv *UserService) SetWakatimeApiKey(user *models.User, apiKey string) (*models.User, error) {
srv.cache.Flush()
return srv.repository.UpdateField(user, "wakatime_api_key", apiKey)
@@ -108,3 +103,7 @@ func (srv *UserService) Delete(user *models.User) error {
srv.cache.Flush()
return srv.repository.Delete(user)
}
+
+func (srv *UserService) FlushCache() {
+ srv.cache.Flush()
+}
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 269bfb1..8667160 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -33,237 +33,355 @@
-
-
-
- Change Password
-
-
-
-
-
-
-
- Reset API Key
-
-
-
-
-
-
-
- Aliases
-
-
-
- You can specify aliases for any type of entity. For instance, you can define a rule, that both myapp-frontend and myapp-backend are combined under a
- project called myapp .
-
-
- {{ if .Aliases }}
-
Rules
- {{ range $i, $alias := .Aliases }}
-
-
- ▸ All {{ $alias.Type | typeName }}s named
- {{ range $j, $value := $alias.Values }}
- {{- $value -}}
- {{ if lt $j (add (len $alias.Values) -2) }}
- {{- ", " | capitalize -}}
- {{ else if lt $j (add (len $alias.Values) -1) }}
- {{- "or" -}}
- {{ end }}
- {{ end }}
- are mapped to {{ $alias.Type | typeName }} {{ $alias.Key }} .
-
-
-
-
-
Map
-
- {{ range $i, $t := entityTypes }}
- {{ $t | typeName | capitalize }}
+
+
+
+ Aliases
+
+
+
+
+ You can specify aliases for any type of entity. For instance, you can define a rule, that both myapp-frontend and myapp-backend are combined under a
+ project called myapp .
+
+
+ {{ if .Aliases }}
+
Rules
+ {{ range $i, $alias := .Aliases }}
+
+
+ ▸ All
{{ $alias.Type | typeName }}s named
+ {{ range $j, $value := $alias.Values }}
+
{{- $value -}}
+ {{ if lt $j (add (len $alias.Values) -2) }}
+
{{- ", " | capitalize -}}
+ {{ else if lt $j (add (len $alias.Values) -1) }}
+
{{- "or" -}}
{{ end }}
-
-
named
-
-
to
-
-
-
- Add
-
+ {{ end }}
+ are mapped to {{ $alias.Type | typeName }} {{ $alias.Key }} .
+
+
+
+
+
+ ✕
+
+
-
-
+ {{end}}
+
+ {{end}}
-
-
- Languages & File Extensions
-
-
-
- You can specify custom mapping from file extensions to programming languages, for instance a .jsx file could be mapped to the React language.
-
-
- {{ if .LanguageMappings }}
-
Rules
- {{ range $i, $mapping := .LanguageMappings }}
-
-
- ▸ When filename ends in {{ $mapping.Extension }}
- then change the language to {{ $mapping.Language }}
-
-
-
-
-
- ✕
-
+ Add Rule
+
+
+
+
Map
+
+ {{ range $i, $t := entityTypes }}
+ {{ $t | typeName | capitalize }}
+ {{ end }}
+
+
named
+
+
to
+
+
+
+ Add
+
+
+
- {{end}}
-
- {{end}}
+
-
Add Rule
-
-
-
-
When filename ends in
-
-
change language to
-
-
-
- Add
-
-
+
+
+
+ Custom Mappings
+
+
+
+
+ You can specify custom mapping from file extensions to programming languages, for instance a .jsx file could be mapped to the React language.
-
-
-
-
- Badges
-
-
-
-
-
- {{ if .User.BadgesEnabled }}
-
Badges are currently enabled. You can disable the feature by deactivating the respective API
- endpoint.
-
-
-
GET /api/compat/shields/v1
+ {{ if .LanguageMappings }}
+
Rules
+ {{ range $i, $mapping := .LanguageMappings }}
+
+
+ ▸ When filename ends in {{ $mapping.Extension }}
+ then change the language to {{ $mapping.Language }}
+
+
+
+
- Status: public
+ class="py-1 px-3 rounded border border-red-500 hover:border-red-600 text-gray-400 text-sm">
+ ✕
+
+
+ {{end}}
+
+ {{end}}
+
+
Add Rule
+
+
+
+
When filename ends in
+
+
change language to
+
+
+
+ Add
+
+
+
+
+
+
+
+
+
+
+ Public Data
+
+
+
+
+
Some features require public access to your data without authentication. This mainly includes Badges and the integration with GitHub Readme Stats , corresponding to these API endpoints:
+
+ /api/compat/shields/v1/{user}
+ /api/v1/users/{user}/stats/{range}
+
+
+
+
+
+
Publicly accessible data range:(in days; 0 = not public)
+
+
+
+
+
+
+ Share projects:
+
+
+
+ No
+ Yes
+
+
+
+
+
+ Share languages:
+
+
+
+ No
+ Yes
+
+
+
+
+
+ Share editors:
+
+
+
+ No
+ Yes
+
+
+
+
+
+ Share operating systems:
+
+
+
+ No
+ Yes
+
+
+
+
+
+ Share machines:
+
+
+
+ No
+ Yes
+
+
- Examples
+
+
+ Save
+
+
+
+
+
+
+
+
+
+ Integrations
+
+
+
+
+
+ WakaTime
+
+
+
+
+
You can connect Wakapi with the official WakaTime in a
+ way
+ that all heartbeats sent to Wakapi are relayed. This way, you can use both services
+ at
+ the same time. To get started, get your API key and paste it here.
+
+
+
+
+
+
+ {{ $placeholderText := "Paste your WakaTime API key here ..." }}
+ {{ if .User.WakatimeApiKey }}
+ {{ $placeholderText = "********" }}
+ {{ end }}
+
+
+
API Key:
+
+
+ {{ if not .User.WakatimeApiKey }}
+
+ Connect
+
+ {{ else }}
+ Disconnect
+
+ {{ end }}
+
+
+
+ {{ if .User.WakatimeApiKey }}
+
+
+ ⤵ Import Data
+
+
+ {{ end }}
+
+
+
+
+
+
+
+ 👉 Please note:
+ When enabling this feature, the operators of this server will, in theory (!), have unlimited access to your data stored in WakaTime. If you are concerned about your privacy, please do not enable this integration or wait for OAuth 2 authentication (#94 ) to be implemented.
+
+
+
+
+
+ Badges (Shields.io)
+
+
+ {{ if gt .User.ShareDataMaxDays 0 }}
+
Examples
@@ -295,152 +413,97 @@
You have the ability to create badges from your coding statistics using Shields.io . To do so, you need to grant public, unauthorized
- access to the respective endpoint.
-
-
GET /api/compat/shields/v1
-
- Status: protected
+ access to the respective endpoint. See Public Data setting.
+ {{ end }}
+
+
+
+
+
+
+
+ ⚠️ Danger Zone
+
+
+
+
+
+ Regenerate summaries
+
+
+ Wakapi improves its efficiency and speed by automatically aggregating individual heartbeats to
+ summaries on a per-day basis.
+ That is, historic summaries, i.e. such from past days, are generated once and only fetched from
+ the
+ database in a static fashion afterwards, unless you pass &recompute=true
+ with your request.
+
+
+ If, for some reason, these aggregated summaries are faulty or preconditions have change (e.g.
+ you
+ modified language mappings retrospectively), you may want to re-generate them from raw
+ heartbeats.
+
+
+ Note: Only run this action if you know what you are doing. Data might be lost
+ is
+ case heartbeats were deleted after the respective summaries had been generated.
+
+
+
+
+
+
+ Clear & Regenerate
-
- {{ end }}
-
-
-
-
-
-
- Integrations
-
-
-
-
- WakaTime
-
-
-
-
-
You can connect Wakapi with the official WakaTime in a way
- that all heartbeats sent to Wakapi are relayed. This way, you can use both services
- at
- the same time. To get started, get your API key and paste it here.
+
-
-
+
+
+ Reset API Key
+
- {{ $placeholderText := "Paste your WakaTime API key here ..." }}
- {{ if .User.WakatimeApiKey }}
- {{ $placeholderText = "********" }}
- {{ end }}
-
-
-
API Key:
-
-
- {{ if not .User.WakatimeApiKey }}
-
- Connect
-
- {{ else }}
-
Disconnect
-
- {{ end }}
+
+
+
+ ⚠️ Caution: Resetting your API key requires you to update your .wakatime.cfg files on all of your computers to make the
+ WakaTime
+ client send heartbeats again.
-
- {{ if .User.WakatimeApiKey }}
-
-
- ⤵ Import Data
+
+
+ Reset API Key
+
+
+
+
+
+
+
+ Delete Account
+
+
+ Deleting your account will cause all data, including all your heartbeats, to be erased from the
+ server immediately. This action is irreversible. Be careful!
+
+
+
+
+
+
+ Delete my Account
-
- {{ end }}
-
-
-
-
-
-
-
- 👉 Please note:
- When enabling this feature, the operators of this server will, in theory (!), have unlimited access to your data stored in WakaTime. If you are concerned about your privacy, please do not enable this integration or wait for OAuth 2 authentication (#94 ) to be implemented.
-
+
+
-
-
-
-
-
- ⚠️ Danger Zone
-
-
-
- Regenerate summaries
-
-
- Wakapi improves its efficiency and speed by automatically aggregating individual heartbeats to
- summaries on a per-day basis.
- That is, historic summaries, i.e. such from past days, are generated once and only fetched from the
- database in a static fashion afterwards, unless you pass &recompute=true
- with your request.
-
-
- If, for some reason, these aggregated summaries are faulty or preconditions have change (e.g. you
- modified language mappings retrospectively), you may want to re-generate them from raw heartbeats.
-
-
- Note: Only run this action if you know what you are doing. Data might be lost is
- case heartbeats were deleted after the respective summaries had been generated.
-
-
-
-
-
-
- Clear & Regenerate
-
-
-
-
-
-
- Delete Account
-
-
- Deleting your account will cause all data, including all your heartbeats, to be erased from the
- server immediately. This action is irreversible. Be careful!
-
-
-
-
-
-
- Delete my Account
-
-
-
-
+
From 6b1f1c1360791457cc7cb7c3ea8194f6cd6db5fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 22:40:54 +0100
Subject: [PATCH 10/15] feat: add endpoint compatible to github readme stats
(resolve #65)
---
coverage/coverage.out | 598 +++++++++++++------------
main.go | 11 +-
routes/compat/shields/v1/badge.go | 2 +-
routes/compat/wakatime/v1/all_time.go | 2 +-
routes/compat/wakatime/v1/stats.go | 5 +-
routes/compat/wakatime/v1/summaries.go | 2 +-
6 files changed, 316 insertions(+), 304 deletions(-)
diff --git a/coverage/coverage.out b/coverage/coverage.out
index fc655fc..7b5624d 100644
--- a/coverage/coverage.out
+++ b/coverage/coverage.out
@@ -1,7 +1,18 @@
mode: set
-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/interval.go:5.47,6.23 1 0
+github.com/muety/wakapi/models/interval.go:11.2,11.14 1 0
+github.com/muety/wakapi/models/interval.go:6.23,7.13 1 0
+github.com/muety/wakapi/models/interval.go:7.13,9.4 1 0
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
github.com/muety/wakapi/models/shared.go:34.52,37.16 3 0
github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
@@ -18,11 +29,6 @@ github.com/muety/wakapi/models/shared.go:78.51,81.2 2 0
github.com/muety/wakapi/models/shared.go:83.37,86.2 2 0
github.com/muety/wakapi/models/shared.go:88.35,90.2 1 0
github.com/muety/wakapi/models/shared.go:92.34,94.2 1 0
-github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
-github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
-github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
-github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
-github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
@@ -62,157 +68,67 @@ github.com/muety/wakapi/models/heartbeat.go:72.37,88.2 1 0
github.com/muety/wakapi/models/heartbeat.go:96.41,98.16 2 0
github.com/muety/wakapi/models/heartbeat.go:101.2,102.10 2 0
github.com/muety/wakapi/models/heartbeat.go:98.16,100.3 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/summary.go:41.27,45.2 1 0
-github.com/muety/wakapi/models/summary.go:97.29,99.2 1 1
-github.com/muety/wakapi/models/summary.go:101.37,108.2 6 1
-github.com/muety/wakapi/models/summary.go:110.35,112.2 1 1
-github.com/muety/wakapi/models/summary.go:114.57,122.2 1 1
-github.com/muety/wakapi/models/summary.go:135.33,140.26 4 1
-github.com/muety/wakapi/models/summary.go:147.2,147.37 1 1
-github.com/muety/wakapi/models/summary.go:151.2,154.33 2 1
-github.com/muety/wakapi/models/summary.go:140.26,141.30 1 1
-github.com/muety/wakapi/models/summary.go:141.30,143.4 1 1
-github.com/muety/wakapi/models/summary.go:147.37,149.3 1 0
-github.com/muety/wakapi/models/summary.go:154.33,160.3 1 1
-github.com/muety/wakapi/models/summary.go:163.45,168.30 3 1
-github.com/muety/wakapi/models/summary.go:177.2,177.30 1 1
-github.com/muety/wakapi/models/summary.go:168.30,169.47 1 1
-github.com/muety/wakapi/models/summary.go:169.47,170.32 1 1
-github.com/muety/wakapi/models/summary.go:173.4,173.9 1 1
-github.com/muety/wakapi/models/summary.go:170.32,172.5 1 1
-github.com/muety/wakapi/models/summary.go:180.73,182.55 2 1
-github.com/muety/wakapi/models/summary.go:187.2,187.16 1 1
-github.com/muety/wakapi/models/summary.go:182.55,183.31 1 1
-github.com/muety/wakapi/models/summary.go:183.31,185.4 1 1
-github.com/muety/wakapi/models/summary.go:190.88,192.55 2 1
-github.com/muety/wakapi/models/summary.go:200.2,200.16 1 1
-github.com/muety/wakapi/models/summary.go:192.55,193.31 1 1
-github.com/muety/wakapi/models/summary.go:193.31,194.23 1 1
-github.com/muety/wakapi/models/summary.go:197.4,197.46 1 1
-github.com/muety/wakapi/models/summary.go:194.23,195.13 1 1
-github.com/muety/wakapi/models/summary.go:203.70,205.8 2 1
-github.com/muety/wakapi/models/summary.go:208.2,208.10 1 1
-github.com/muety/wakapi/models/summary.go:205.8,207.3 1 1
-github.com/muety/wakapi/models/summary.go:211.71,212.63 1 1
-github.com/muety/wakapi/models/summary.go:252.2,258.10 6 1
-github.com/muety/wakapi/models/summary.go:212.63,215.45 2 1
-github.com/muety/wakapi/models/summary.go:224.3,224.31 1 1
-github.com/muety/wakapi/models/summary.go:231.3,231.31 1 1
-github.com/muety/wakapi/models/summary.go:248.3,248.16 1 1
-github.com/muety/wakapi/models/summary.go:215.45,216.32 1 1
-github.com/muety/wakapi/models/summary.go:221.4,221.14 1 1
-github.com/muety/wakapi/models/summary.go:216.32,217.24 1 1
-github.com/muety/wakapi/models/summary.go:217.24,219.6 1 1
-github.com/muety/wakapi/models/summary.go:224.31,226.60 1 1
-github.com/muety/wakapi/models/summary.go:226.60,228.5 1 1
-github.com/muety/wakapi/models/summary.go:231.31,233.60 1 1
-github.com/muety/wakapi/models/summary.go:233.60,234.55 1 1
-github.com/muety/wakapi/models/summary.go:234.55,236.6 1 1
-github.com/muety/wakapi/models/summary.go:236.11,244.6 1 1
-github.com/muety/wakapi/models/summary.go:261.33,263.2 1 1
-github.com/muety/wakapi/models/summary.go:265.43,267.2 1 1
-github.com/muety/wakapi/models/summary.go:269.38,271.2 1 1
-github.com/muety/wakapi/models/user.go:35.43,38.2 1 0
-github.com/muety/wakapi/models/user.go:40.33,44.2 1 0
-github.com/muety/wakapi/models/user.go:46.45,48.2 1 0
-github.com/muety/wakapi/models/user.go:50.45,52.2 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:94.70,96.2 1 0
-github.com/muety/wakapi/config/config.go:98.65,100.2 1 0
-github.com/muety/wakapi/config/config.go:102.82,112.2 1 0
-github.com/muety/wakapi/config/config.go:114.31,116.2 1 0
-github.com/muety/wakapi/config/config.go:118.32,120.2 1 0
-github.com/muety/wakapi/config/config.go:122.74,123.19 1 0
-github.com/muety/wakapi/config/config.go:124.10,125.34 1 0
-github.com/muety/wakapi/config/config.go:125.34,134.4 8 0
-github.com/muety/wakapi/config/config.go:138.73,139.33 1 0
-github.com/muety/wakapi/config/config.go:139.33,147.17 5 0
-github.com/muety/wakapi/config/config.go:151.3,152.13 2 0
-github.com/muety/wakapi/config/config.go:147.17,149.4 1 0
-github.com/muety/wakapi/config/config.go:156.50,157.19 1 0
-github.com/muety/wakapi/config/config.go:170.2,170.12 1 0
-github.com/muety/wakapi/config/config.go:158.23,162.5 1 0
-github.com/muety/wakapi/config/config.go:163.26,166.5 1 0
-github.com/muety/wakapi/config/config.go:167.24,168.48 1 0
-github.com/muety/wakapi/config/config.go:173.53,183.2 1 1
-github.com/muety/wakapi/config/config.go:185.56,187.16 2 1
-github.com/muety/wakapi/config/config.go:191.2,198.3 1 1
-github.com/muety/wakapi/config/config.go:187.16,189.3 1 0
-github.com/muety/wakapi/config/config.go:201.54,203.2 1 1
-github.com/muety/wakapi/config/config.go:205.60,207.2 1 0
-github.com/muety/wakapi/config/config.go:209.59,211.2 1 0
-github.com/muety/wakapi/config/config.go:213.57,215.2 1 0
-github.com/muety/wakapi/config/config.go:217.53,219.2 1 0
-github.com/muety/wakapi/config/config.go:221.29,223.2 1 1
-github.com/muety/wakapi/config/config.go:225.27,227.16 2 0
-github.com/muety/wakapi/config/config.go:230.2,233.16 3 0
-github.com/muety/wakapi/config/config.go:237.2,237.41 1 0
-github.com/muety/wakapi/config/config.go:227.16,229.3 1 0
-github.com/muety/wakapi/config/config.go:233.16,235.3 1 0
-github.com/muety/wakapi/config/config.go:240.48,252.16 3 0
-github.com/muety/wakapi/config/config.go:255.2,257.16 3 0
-github.com/muety/wakapi/config/config.go:261.2,261.55 1 0
-github.com/muety/wakapi/config/config.go:265.2,265.15 1 0
-github.com/muety/wakapi/config/config.go:252.16,254.3 1 0
-github.com/muety/wakapi/config/config.go:257.16,259.3 1 0
-github.com/muety/wakapi/config/config.go:261.55,263.3 1 0
-github.com/muety/wakapi/config/config.go:268.38,269.43 1 0
-github.com/muety/wakapi/config/config.go:272.2,272.15 1 0
-github.com/muety/wakapi/config/config.go:269.43,271.3 1 0
-github.com/muety/wakapi/config/config.go:275.45,276.27 1 0
-github.com/muety/wakapi/config/config.go:279.2,279.15 1 0
-github.com/muety/wakapi/config/config.go:276.27,278.3 1 0
-github.com/muety/wakapi/config/config.go:282.26,284.2 1 0
-github.com/muety/wakapi/config/config.go:286.20,288.2 1 0
-github.com/muety/wakapi/config/config.go:290.21,295.96 3 0
-github.com/muety/wakapi/config/config.go:299.2,307.52 5 0
-github.com/muety/wakapi/config/config.go:311.2,311.47 1 0
-github.com/muety/wakapi/config/config.go:317.2,317.70 1 0
-github.com/muety/wakapi/config/config.go:321.2,321.28 1 0
-github.com/muety/wakapi/config/config.go:325.2,326.14 2 0
-github.com/muety/wakapi/config/config.go:295.96,297.3 1 0
-github.com/muety/wakapi/config/config.go:307.52,309.3 1 0
-github.com/muety/wakapi/config/config.go:311.47,312.14 1 0
-github.com/muety/wakapi/config/config.go:312.14,314.4 1 0
-github.com/muety/wakapi/config/config.go:317.70,319.3 1 0
-github.com/muety/wakapi/config/config.go:321.28,323.3 1 0
-github.com/muety/wakapi/utils/summary.go:10.71,13.18 2 0
-github.com/muety/wakapi/utils/summary.go:49.2,49.22 1 0
-github.com/muety/wakapi/utils/summary.go:14.58,15.24 1 0
-github.com/muety/wakapi/utils/summary.go:16.66,18.22 2 0
-github.com/muety/wakapi/utils/summary.go:19.64,20.23 1 0
-github.com/muety/wakapi/utils/summary.go:21.39,23.21 2 0
-github.com/muety/wakapi/utils/summary.go:24.66,25.24 1 0
-github.com/muety/wakapi/utils/summary.go:26.40,28.22 2 0
-github.com/muety/wakapi/utils/summary.go:29.31,30.23 1 0
-github.com/muety/wakapi/utils/summary.go:31.66,32.42 1 0
-github.com/muety/wakapi/utils/summary.go:33.49,35.40 2 0
-github.com/muety/wakapi/utils/summary.go:36.41,37.43 1 0
-github.com/muety/wakapi/utils/summary.go:38.68,40.42 2 0
-github.com/muety/wakapi/utils/summary.go:41.35,42.43 1 0
-github.com/muety/wakapi/utils/summary.go:43.26,44.21 1 0
-github.com/muety/wakapi/utils/summary.go:45.10,46.39 1 0
-github.com/muety/wakapi/utils/summary.go:52.73,59.56 5 0
-github.com/muety/wakapi/utils/summary.go:73.2,80.8 2 0
-github.com/muety/wakapi/utils/summary.go:59.56,61.3 1 0
-github.com/muety/wakapi/utils/summary.go:61.8,63.17 2 0
-github.com/muety/wakapi/utils/summary.go:67.3,68.17 2 0
-github.com/muety/wakapi/utils/summary.go:63.17,65.4 1 0
-github.com/muety/wakapi/utils/summary.go:68.17,70.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/summary.go:101.29,103.2 1 1
+github.com/muety/wakapi/models/summary.go:105.37,112.2 6 1
+github.com/muety/wakapi/models/summary.go:114.35,116.2 1 1
+github.com/muety/wakapi/models/summary.go:118.57,126.2 1 1
+github.com/muety/wakapi/models/summary.go:139.33,144.26 4 1
+github.com/muety/wakapi/models/summary.go:151.2,151.37 1 1
+github.com/muety/wakapi/models/summary.go:155.2,158.33 2 1
+github.com/muety/wakapi/models/summary.go:144.26,145.30 1 1
+github.com/muety/wakapi/models/summary.go:145.30,147.4 1 1
+github.com/muety/wakapi/models/summary.go:151.37,153.3 1 0
+github.com/muety/wakapi/models/summary.go:158.33,164.3 1 1
+github.com/muety/wakapi/models/summary.go:167.45,172.30 3 1
+github.com/muety/wakapi/models/summary.go:181.2,181.30 1 1
+github.com/muety/wakapi/models/summary.go:172.30,173.47 1 1
+github.com/muety/wakapi/models/summary.go:173.47,174.32 1 1
+github.com/muety/wakapi/models/summary.go:177.4,177.9 1 1
+github.com/muety/wakapi/models/summary.go:174.32,176.5 1 1
+github.com/muety/wakapi/models/summary.go:184.73,186.55 2 1
+github.com/muety/wakapi/models/summary.go:191.2,191.16 1 1
+github.com/muety/wakapi/models/summary.go:186.55,187.31 1 1
+github.com/muety/wakapi/models/summary.go:187.31,189.4 1 1
+github.com/muety/wakapi/models/summary.go:194.88,196.55 2 1
+github.com/muety/wakapi/models/summary.go:204.2,204.16 1 1
+github.com/muety/wakapi/models/summary.go:196.55,197.31 1 1
+github.com/muety/wakapi/models/summary.go:197.31,198.23 1 1
+github.com/muety/wakapi/models/summary.go:201.4,201.46 1 1
+github.com/muety/wakapi/models/summary.go:198.23,199.13 1 1
+github.com/muety/wakapi/models/summary.go:207.70,209.8 2 1
+github.com/muety/wakapi/models/summary.go:212.2,212.10 1 1
+github.com/muety/wakapi/models/summary.go:209.8,211.3 1 1
+github.com/muety/wakapi/models/summary.go:215.71,216.63 1 1
+github.com/muety/wakapi/models/summary.go:256.2,262.10 6 1
+github.com/muety/wakapi/models/summary.go:216.63,219.45 2 1
+github.com/muety/wakapi/models/summary.go:228.3,228.31 1 1
+github.com/muety/wakapi/models/summary.go:235.3,235.31 1 1
+github.com/muety/wakapi/models/summary.go:252.3,252.16 1 1
+github.com/muety/wakapi/models/summary.go:219.45,220.32 1 1
+github.com/muety/wakapi/models/summary.go:225.4,225.14 1 1
+github.com/muety/wakapi/models/summary.go:220.32,221.24 1 1
+github.com/muety/wakapi/models/summary.go:221.24,223.6 1 1
+github.com/muety/wakapi/models/summary.go:228.31,230.60 1 1
+github.com/muety/wakapi/models/summary.go:230.60,232.5 1 1
+github.com/muety/wakapi/models/summary.go:235.31,237.60 1 1
+github.com/muety/wakapi/models/summary.go:237.60,238.55 1 1
+github.com/muety/wakapi/models/summary.go:238.55,240.6 1 1
+github.com/muety/wakapi/models/summary.go:240.11,248.6 1 1
+github.com/muety/wakapi/models/summary.go:265.33,267.2 1 1
+github.com/muety/wakapi/models/summary.go:269.43,271.2 1 1
+github.com/muety/wakapi/models/summary.go:273.38,275.2 1 1
+github.com/muety/wakapi/models/user.go:40.43,43.2 1 0
+github.com/muety/wakapi/models/user.go:45.33,49.2 1 0
+github.com/muety/wakapi/models/user.go:51.45,53.2 1 0
+github.com/muety/wakapi/models/user.go:55.45,57.2 1 0
+github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
+github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
+github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
+github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
+github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
@@ -274,30 +190,64 @@ github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:20.116,26.2 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:28.71,29.71 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:29.71,31.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:34.107,35.37 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:42.2,45.16 3 0
-github.com/muety/wakapi/middlewares/authenticate.go:49.2,49.16 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:59.2,60.29 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:35.37,36.58 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:36.58,39.4 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:45.16,47.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:49.16,50.44 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:56.3,56.9 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:50.44,52.4 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:52.9,55.4 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:63.92,65.16 2 1
-github.com/muety/wakapi/middlewares/authenticate.go:69.2,72.16 4 1
-github.com/muety/wakapi/middlewares/authenticate.go:75.2,75.18 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:65.16,67.3 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:72.16,74.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:78.92,80.16 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:84.2,85.16 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.18 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:80.16,82.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:85.16,87.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.74,21.16 2 0
+github.com/muety/wakapi/utils/summary.go:24.2,24.32 1 0
+github.com/muety/wakapi/utils/summary.go:21.16,23.3 1 0
+github.com/muety/wakapi/utils/summary.go:27.84,30.18 2 0
+github.com/muety/wakapi/utils/summary.go:65.2,65.22 1 0
+github.com/muety/wakapi/utils/summary.go:31.28,32.24 1 0
+github.com/muety/wakapi/utils/summary.go:33.32,35.22 2 0
+github.com/muety/wakapi/utils/summary.go:36.31,37.23 1 0
+github.com/muety/wakapi/utils/summary.go:38.31,40.21 2 0
+github.com/muety/wakapi/utils/summary.go:41.32,42.24 1 0
+github.com/muety/wakapi/utils/summary.go:43.32,45.22 2 0
+github.com/muety/wakapi/utils/summary.go:46.31,47.23 1 0
+github.com/muety/wakapi/utils/summary.go:48.32,49.42 1 0
+github.com/muety/wakapi/utils/summary.go:50.41,52.40 2 0
+github.com/muety/wakapi/utils/summary.go:53.33,54.43 1 0
+github.com/muety/wakapi/utils/summary.go:55.33,56.43 1 0
+github.com/muety/wakapi/utils/summary.go:57.35,58.43 1 0
+github.com/muety/wakapi/utils/summary.go:59.26,60.21 1 0
+github.com/muety/wakapi/utils/summary.go:61.10,62.39 1 0
+github.com/muety/wakapi/utils/summary.go:68.73,75.56 5 0
+github.com/muety/wakapi/utils/summary.go:89.2,96.8 2 0
+github.com/muety/wakapi/utils/summary.go:75.56,77.3 1 0
+github.com/muety/wakapi/utils/summary.go:77.8,79.17 2 0
+github.com/muety/wakapi/utils/summary.go:83.3,84.17 2 0
+github.com/muety/wakapi/utils/summary.go:79.17,81.4 1 0
+github.com/muety/wakapi/utils/summary.go:84.17,86.4 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:20.91,26.2 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:28.90,31.2 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:33.71,34.71 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:34.71,36.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:39.107,43.16 3 0
+github.com/muety/wakapi/middlewares/authenticate.go:47.2,47.31 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:62.2,63.29 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:43.16,45.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:47.31,48.31 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:53.3,53.44 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:59.3,59.9 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:48.31,51.4 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:53.44,55.4 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:55.9,58.4 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:66.70,67.39 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:72.2,72.14 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:67.39,68.60 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:68.60,70.4 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:75.92,77.16 2 1
+github.com/muety/wakapi/middlewares/authenticate.go:81.2,84.16 4 1
+github.com/muety/wakapi/middlewares/authenticate.go:87.2,87.18 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:77.16,79.3 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:84.16,86.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:90.92,92.16 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:96.2,97.16 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:104.2,104.18 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:92.16,94.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:97.16,99.3 1 0
github.com/muety/wakapi/middlewares/logging.go:17.79,18.43 1 0
github.com/muety/wakapi/middlewares/logging.go:18.43,23.3 1 0
github.com/muety/wakapi/middlewares/logging.go:26.80,44.2 6 0
@@ -319,31 +269,112 @@ github.com/muety/wakapi/middlewares/logging.go:124.36,126.2 1 0
github.com/muety/wakapi/middlewares/logging.go:127.42,129.2 1 0
github.com/muety/wakapi/middlewares/logging.go:130.40,132.2 1 0
github.com/muety/wakapi/middlewares/logging.go:133.52,135.2 1 0
-github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
-github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
-github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
-github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
-github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
-github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
-github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
-github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
-github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
-github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
-github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
-github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
-github.com/muety/wakapi/services/user.go:59.88,66.93 2 0
-github.com/muety/wakapi/services/user.go:72.2,72.38 1 0
-github.com/muety/wakapi/services/user.go:66.93,68.3 1 0
-github.com/muety/wakapi/services/user.go:68.8,70.3 1 0
-github.com/muety/wakapi/services/user.go:75.73,78.2 2 0
-github.com/muety/wakapi/services/user.go:80.78,84.2 3 0
-github.com/muety/wakapi/services/user.go:86.79,89.2 2 0
-github.com/muety/wakapi/services/user.go:91.99,94.2 2 0
-github.com/muety/wakapi/services/user.go:96.106,99.96 3 0
-github.com/muety/wakapi/services/user.go:104.2,104.68 1 0
-github.com/muety/wakapi/services/user.go:99.96,101.3 1 0
-github.com/muety/wakapi/services/user.go:101.8,103.3 1 0
-github.com/muety/wakapi/services/user.go:107.57,110.2 2 0
+github.com/muety/wakapi/config/config.go:95.70,97.2 1 0
+github.com/muety/wakapi/config/config.go:99.65,101.2 1 0
+github.com/muety/wakapi/config/config.go:103.82,113.2 1 0
+github.com/muety/wakapi/config/config.go:115.31,117.2 1 0
+github.com/muety/wakapi/config/config.go:119.32,121.2 1 0
+github.com/muety/wakapi/config/config.go:123.74,124.19 1 0
+github.com/muety/wakapi/config/config.go:125.10,126.34 1 0
+github.com/muety/wakapi/config/config.go:126.34,135.4 8 0
+github.com/muety/wakapi/config/config.go:139.73,140.33 1 0
+github.com/muety/wakapi/config/config.go:140.33,148.17 5 0
+github.com/muety/wakapi/config/config.go:152.3,153.13 2 0
+github.com/muety/wakapi/config/config.go:148.17,150.4 1 0
+github.com/muety/wakapi/config/config.go:157.50,158.19 1 0
+github.com/muety/wakapi/config/config.go:171.2,171.12 1 0
+github.com/muety/wakapi/config/config.go:159.23,163.5 1 0
+github.com/muety/wakapi/config/config.go:164.26,167.5 1 0
+github.com/muety/wakapi/config/config.go:168.24,169.48 1 0
+github.com/muety/wakapi/config/config.go:174.53,184.2 1 1
+github.com/muety/wakapi/config/config.go:186.56,188.16 2 1
+github.com/muety/wakapi/config/config.go:192.2,199.3 1 1
+github.com/muety/wakapi/config/config.go:188.16,190.3 1 0
+github.com/muety/wakapi/config/config.go:202.54,204.2 1 1
+github.com/muety/wakapi/config/config.go:206.60,208.2 1 0
+github.com/muety/wakapi/config/config.go:210.59,212.2 1 0
+github.com/muety/wakapi/config/config.go:214.57,216.2 1 0
+github.com/muety/wakapi/config/config.go:218.53,220.2 1 0
+github.com/muety/wakapi/config/config.go:222.29,224.2 1 1
+github.com/muety/wakapi/config/config.go:226.27,228.16 2 0
+github.com/muety/wakapi/config/config.go:231.2,234.16 3 0
+github.com/muety/wakapi/config/config.go:238.2,238.41 1 0
+github.com/muety/wakapi/config/config.go:228.16,230.3 1 0
+github.com/muety/wakapi/config/config.go:234.16,236.3 1 0
+github.com/muety/wakapi/config/config.go:241.48,253.16 3 0
+github.com/muety/wakapi/config/config.go:256.2,258.16 3 0
+github.com/muety/wakapi/config/config.go:262.2,262.55 1 0
+github.com/muety/wakapi/config/config.go:266.2,266.15 1 0
+github.com/muety/wakapi/config/config.go:253.16,255.3 1 0
+github.com/muety/wakapi/config/config.go:258.16,260.3 1 0
+github.com/muety/wakapi/config/config.go:262.55,264.3 1 0
+github.com/muety/wakapi/config/config.go:269.38,270.43 1 0
+github.com/muety/wakapi/config/config.go:273.2,273.15 1 0
+github.com/muety/wakapi/config/config.go:270.43,272.3 1 0
+github.com/muety/wakapi/config/config.go:276.45,277.27 1 0
+github.com/muety/wakapi/config/config.go:280.2,280.15 1 0
+github.com/muety/wakapi/config/config.go:277.27,279.3 1 0
+github.com/muety/wakapi/config/config.go:283.26,285.2 1 0
+github.com/muety/wakapi/config/config.go:287.20,289.2 1 0
+github.com/muety/wakapi/config/config.go:291.21,296.96 3 0
+github.com/muety/wakapi/config/config.go:300.2,308.52 5 0
+github.com/muety/wakapi/config/config.go:312.2,312.47 1 0
+github.com/muety/wakapi/config/config.go:318.2,318.70 1 0
+github.com/muety/wakapi/config/config.go:322.2,322.28 1 0
+github.com/muety/wakapi/config/config.go:326.2,327.14 2 0
+github.com/muety/wakapi/config/config.go:296.96,298.3 1 0
+github.com/muety/wakapi/config/config.go:308.52,310.3 1 0
+github.com/muety/wakapi/config/config.go:312.47,313.14 1 0
+github.com/muety/wakapi/config/config.go:313.14,315.4 1 0
+github.com/muety/wakapi/config/config.go:318.70,320.3 1 0
+github.com/muety/wakapi/config/config.go:322.28,324.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/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
+github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
+github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
+github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
+github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
+github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
+github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
+github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
+github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
+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/misc.go:23.126,30.2 1 0
+github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
+github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
+github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
+github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
+github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
+github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
+github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
+github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
+github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
+github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
+github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
+github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
+github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
+github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
+github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
+github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
+github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
+github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
+github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
+github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
+github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
+github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
@@ -384,41 +415,6 @@ github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
-github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
-github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
-github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
-github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
-github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
-github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
-github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
-github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
-github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
-github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
-github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
-github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
-github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
-github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
-github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
-github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
-github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
-github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
-github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
-github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
-github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
-github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
-github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
-github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
-github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
-github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
-github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
-github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
-github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
-github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
-github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
-github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
-github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
-github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
-github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
github.com/muety/wakapi/services/heartbeat.go:29.80,31.2 1 0
@@ -434,55 +430,6 @@ github.com/muety/wakapi/services/heartbeat.go:63.2,63.28 1 0
github.com/muety/wakapi/services/heartbeat.go:67.2,67.24 1 0
github.com/muety/wakapi/services/heartbeat.go:59.16,61.3 1 0
github.com/muety/wakapi/services/heartbeat.go:63.28,65.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
-github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
-github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
-github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
-github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
-github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
-github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
-github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
-github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
-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/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:23.126,30.2 1 0
-github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
-github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
-github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
-github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
-github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
-github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
-github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
-github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
-github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
-github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
-github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
-github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
-github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
-github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
-github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
-github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
-github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
-github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
-github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
-github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
-github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
-github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
github.com/muety/wakapi/services/summary.go:27.149,35.2 1 1
github.com/muety/wakapi/services/summary.go:39.120,42.52 2 1
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
@@ -572,3 +519,70 @@ github.com/muety/wakapi/services/summary.go:324.54,326.3 1 1
github.com/muety/wakapi/services/summary.go:331.59,333.25 2 1
github.com/muety/wakapi/services/summary.go:336.2,336.32 1 1
github.com/muety/wakapi/services/summary.go:333.25,335.3 1 1
+github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
+github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
+github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
+github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
+github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
+github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
+github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
+github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
+github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
+github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
+github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
+github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
+github.com/muety/wakapi/services/user.go:59.88,66.93 2 0
+github.com/muety/wakapi/services/user.go:72.2,72.38 1 0
+github.com/muety/wakapi/services/user.go:66.93,68.3 1 0
+github.com/muety/wakapi/services/user.go:68.8,70.3 1 0
+github.com/muety/wakapi/services/user.go:75.73,78.2 2 0
+github.com/muety/wakapi/services/user.go:80.78,84.2 3 0
+github.com/muety/wakapi/services/user.go:86.99,89.2 2 0
+github.com/muety/wakapi/services/user.go:91.106,94.96 3 0
+github.com/muety/wakapi/services/user.go:99.2,99.68 1 0
+github.com/muety/wakapi/services/user.go:94.96,96.3 1 0
+github.com/muety/wakapi/services/user.go:96.8,98.3 1 0
+github.com/muety/wakapi/services/user.go:102.57,105.2 2 0
+github.com/muety/wakapi/services/user.go:107.38,109.2 1 0
+github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
+github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
+github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
+github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
+github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
+github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
+github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
+github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
+github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
+github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
+github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
+github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
+github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
+github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
+github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
+github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
+github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
+github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
+github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
+github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
+github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
+github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
+github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
+github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
+github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
+github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
+github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
+github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
+github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
+github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
+github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
+github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
+github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
+github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
+github.com/muety/wakapi/services/alias.go:112.51,114.3 1 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
diff --git a/main.go b/main.go
index cb0d013..dde7b7d 100644
--- a/main.go
+++ b/main.go
@@ -146,7 +146,6 @@ func main() {
router := mux.NewRouter()
rootRouter := router.PathPrefix("/").Subrouter()
apiRouter := router.PathPrefix("/api").Subrouter()
- compatApiRouter := apiRouter.PathPrefix("/compat").Subrouter()
// Globally used middlewares
recoveryMiddleware := handlers.RecoveryHandler()
@@ -168,12 +167,10 @@ func main() {
summaryApiHandler.RegisterRoutes(apiRouter)
healthApiHandler.RegisterRoutes(apiRouter)
heartbeatApiHandler.RegisterRoutes(apiRouter)
-
- // Compat route registrations
- wakatimeV1AllHandler.RegisterRoutes(compatApiRouter)
- wakatimeV1SummariesHandler.RegisterRoutes(compatApiRouter)
- wakatimeV1StatsHandler.RegisterRoutes(compatApiRouter)
- shieldV1BadgeHandler.RegisterRoutes(compatApiRouter)
+ wakatimeV1AllHandler.RegisterRoutes(apiRouter)
+ wakatimeV1SummariesHandler.RegisterRoutes(apiRouter)
+ wakatimeV1StatsHandler.RegisterRoutes(apiRouter)
+ shieldV1BadgeHandler.RegisterRoutes(apiRouter)
// Static Routes
router.PathPrefix("/assets").Handler(http.FileServer(pkger.Dir("/static")))
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index 8591d4e..6d31939 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -34,7 +34,7 @@ func NewBadgeHandler(summaryService services.ISummaryService, userService servic
func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
// no auth middleware here, handler itself resolves the user
- r := router.PathPrefix("/shields/v1/{user}").Subrouter()
+ r := router.PathPrefix("/compat/shields/v1/{user}").Subrouter()
r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
diff --git a/routes/compat/wakatime/v1/all_time.go b/routes/compat/wakatime/v1/all_time.go
index 89b605d..9a41670 100644
--- a/routes/compat/wakatime/v1/all_time.go
+++ b/routes/compat/wakatime/v1/all_time.go
@@ -28,7 +28,7 @@ func NewAllTimeHandler(userService services.IUserService, summaryService service
}
func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) {
- r := router.PathPrefix("/wakatime/v1/users/{user}/all_time_since_today").Subrouter()
+ r := router.PathPrefix("/compat/wakatime/v1/users/{user}/all_time_since_today").Subrouter()
r.Use(
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
)
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
index 3d0335d..493b239 100644
--- a/routes/compat/wakatime/v1/stats.go
+++ b/routes/compat/wakatime/v1/stats.go
@@ -27,11 +27,12 @@ func NewStatsHandler(userService services.IUserService, summaryService services.
}
func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
- r := router.PathPrefix("/wakatime/v1/users/{user}/stats/{range}").Subrouter()
+ r := router.PathPrefix("").Subrouter()
r.Use(
middlewares.NewAuthenticateMiddleware(h.userSrvc).WithOptionalFor([]string{"/"}).Handler,
)
- r.Methods(http.MethodGet).HandlerFunc(h.Get)
+ r.Path("/v1/users/{user}/stats/{range}").Methods(http.MethodGet).HandlerFunc(h.Get)
+ r.Path("/compat/wakatime/v1/users/{user}/stats/{range}").Methods(http.MethodGet).HandlerFunc(h.Get)
}
// TODO: support filtering (requires https://github.com/muety/wakapi/issues/108)
diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go
index 5a331f9..73d5803 100644
--- a/routes/compat/wakatime/v1/summaries.go
+++ b/routes/compat/wakatime/v1/summaries.go
@@ -29,7 +29,7 @@ func NewSummariesHandler(userService services.IUserService, summaryService servi
}
func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
- r := router.PathPrefix("/wakatime/v1/users/{user}/summaries").Subrouter()
+ r := router.PathPrefix("/compat/wakatime/v1/users/{user}/summaries").Subrouter()
r.Use(
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
)
From 77050f23f2840e512561ffa5c14945afd9ba555b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 23:02:29 +0100
Subject: [PATCH 11/15] chore: settings interface for github readme stats
---
README.md | 13 +++++++++----
views/settings.tpl.html | 28 ++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 2ac7ad2..23f93cb 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@
* [How to use](#-how-to-use)
* [Configuration Options](#-configuration-options)
* [API Endpoints](#-api-endpoints)
-* [Prometheus Export](#%EF%B8%8F-prometheus-export)
+* [Integrations](#-integrations)
* [WakaTime Integration](#%EF%B8%8F-wakatime-integration)
* [Best Practices](#-best-practices)
* [Developer Notes](#-developer-notes)
@@ -61,7 +61,7 @@ I'd love to get some community feedback from active Wakapi users. If you want, p
* ✅ REST API
* ✅ Partially compatible with WakaTime
* ✅ WakaTime integration
-* ✅ Support for [Prometheus](https://github.com/muety/wakapi#%EF%B8%8F-prometheus-export) exports
+* ✅ Support for Prometheus exports
* ✅ Self-hosted
## 🚧 Roadmap
@@ -195,7 +195,8 @@ The following API endpoints are available. A more detailed Swagger documentation
* `GET /api/compat/wakatime/v1/users/current/summaries` (see [Wakatime API docs](https://wakatime.com/developers#summaries))
* `GET /api/health`
-## ⤴️ Prometheus Export
+## 🤝 Integrations
+### Prometheus Export
If you want to export your Wakapi statistics to Prometheus to view them in a Grafana dashboard or so please refer to an excellent tool called **[wakatime_exporter](https://github.com/MacroPower/wakatime_exporter)**.
[](https://github.com/MacroPower/wakatime_exporter)
@@ -204,9 +205,13 @@ It is a standalone webserver that connects to your Wakapi instance and exposes t
Simply configure the exporter with `WAKA_SCRAPE_URI` to equal `"https://wakapi.your-server.com/api/compat/wakatime/v1"` and set your API key accordingly.
-## ⤵️ WakaTime Integration
+### WakaTime Integration
Wakapi plays well together with [WakaTime](https://wakatime.com). For one thing, you can **forward heartbeats** from Wakapi to WakaTime to effectively use both services simultaneously. In addition, there is the option to **import historic data** from WakaTime for consistency between both services. Both features can be enabled in the _Integrations_ section of your Wakapi instance's settings page.
+### GitHub Readme Stats Integrations
+Wakapi also integrates with [GitHub Readme Stats](https://github.com/anuraghazra/github-readme-stats#wakatime-week-stats) to generate fancy cards for you. Here is an example.
+
+
## 👍 Best Practices
It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS).
However, if you want to expose your wakapi instance to the public anyway, you need to set `server.listen_ipv4` to `0.0.0.0` in `config.yml`
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 8667160..a933ce6 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -416,6 +416,24 @@
access to the respective endpoint. See Public Data setting.
{{ end }}
+
+
+
+ GitHub Readme Stats
+
+
+
Wakapi intregrates with GitHub Readme Stats to generate fancy cards for you.
+
+ {{ if gt .User.ShareDataMaxDays 0 }}
+
Example
+
+
+
Source URL:
+ https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&layout=compact&api_domain=%s
+
+
+ {{ end }}
+
@@ -513,10 +531,20 @@
e.setAttribute('src', e.getAttribute('src').replace('%s', baseUrl))
e.classList.remove('hidden')
})
+ document.querySelectorAll('.with-url-src-no-scheme').forEach(e => {
+ const strippedUrl = baseUrl.replace(/https?:\/\//, '')
+ e.setAttribute('src', e.getAttribute('src').replace('%s', strippedUrl))
+ e.classList.remove('hidden')
+ })
document.querySelectorAll('.with-url-inner').forEach(e => {
e.innerHTML = e.innerHTML.replace('%s', baseUrl)
e.classList.remove('hidden')
})
+ document.querySelectorAll('.with-url-inner-no-scheme').forEach(e => {
+ const strippedUrl = baseUrl.replace(/https?:\/\//, '')
+ e.innerHTML = e.innerHTML.replace('%s', strippedUrl)
+ e.classList.remove('hidden')
+ })
const btnRegenerate = document.querySelector('#btn-regenerate-summaries')
const formRegenerate = document.querySelector('#form-regenerate-summaries')
From 2f5973cfa316afe7ef001f6d743b08262726f97a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 23:08:22 +0100
Subject: [PATCH 12/15] chore: add localhost notice to examples
---
views/settings.tpl.html | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index a933ce6..581a8f6 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -381,8 +381,11 @@
{{ if gt .User.ShareDataMaxDays 0 }}
- Examples
-
+
+
Examples:
+ (Only available on public instances, not on localhost)
+
+
Wakapi intregrates with
GitHub Readme Stats to generate fancy cards for you.
{{ if gt .User.ShareDataMaxDays 0 }}
-
Example
-
+
+
Example:
+ (Only available on public instances, not on localhost)
+
+
Source URL:
https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&layout=compact&api_domain=%s
From 6d2697ec37b9c9f9e04734bff14f0f35d1b53d1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 23:23:26 +0100
Subject: [PATCH 13/15] feat: allow unlimited date ranges
---
models/user.go | 2 +-
routes/compat/shields/v1/badge.go | 3 ++-
routes/compat/wakatime/v1/stats.go | 2 +-
routes/settings.go | 7 +------
views/settings.tpl.html | 4 ++--
5 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/models/user.go b/models/user.go
index fa37fe1..bc85eeb 100644
--- a/models/user.go
+++ b/models/user.go
@@ -6,7 +6,7 @@ type User struct {
Password string `json:"-"`
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
- ShareDataMaxDays uint `json:"-" gorm:"default:0"`
+ ShareDataMaxDays int `json:"-" gorm:"default:0"`
ShareEditors bool `json:"-" gorm:"default:false; type:bool"`
ShareLanguages bool `json:"-" gorm:"default:false; type:bool"`
ShareProjects bool `json:"-" gorm:"default:false; type:bool"`
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index 6d31939..7c9ba99 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -68,7 +68,8 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
_, rangeFrom, rangeTo := utils.ResolveInterval(interval)
minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(user.ShareDataMaxDays)))
- if rangeFrom.Before(minStart) {
+ // negative value means no limit
+ if rangeFrom.Before(minStart) && user.ShareDataMaxDays >= 0 {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("requested time range too broad"))
return
diff --git a/routes/compat/wakatime/v1/stats.go b/routes/compat/wakatime/v1/stats.go
index 493b239..d74b488 100644
--- a/routes/compat/wakatime/v1/stats.go
+++ b/routes/compat/wakatime/v1/stats.go
@@ -65,7 +65,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(requestedUser.ShareDataMaxDays)))
if (authorizedUser == nil || requestedUser.ID != authorizedUser.ID) &&
- (requestedUser.ShareDataMaxDays == 0 || rangeFrom.Before(minStart)) {
+ rangeFrom.Before(minStart) && requestedUser.ShareDataMaxDays >= 0 {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("requested time range too broad"))
return
diff --git a/routes/settings.go b/routes/settings.go
index ca7f771..81f0981 100644
--- a/routes/settings.go
+++ b/routes/settings.go
@@ -2,7 +2,6 @@ package routes
import (
"encoding/base64"
- "errors"
"fmt"
"github.com/emvi/logbuch"
"github.com/gorilla/mux"
@@ -218,11 +217,7 @@ func (h *SettingsHandler) actionUpdateSharing(w http.ResponseWriter, r *http.Req
user.ShareEditors, err = strconv.ParseBool(r.PostFormValue("share_editors"))
user.ShareOSs, err = strconv.ParseBool(r.PostFormValue("share_oss"))
user.ShareMachines, err = strconv.ParseBool(r.PostFormValue("share_machines"))
- if v, e := strconv.Atoi(r.PostFormValue("max_days")); e == nil && v >= 0 {
- user.ShareDataMaxDays = uint(v)
- } else {
- err = errors.New("")
- }
+ user.ShareDataMaxDays, err = strconv.Atoi(r.PostFormValue("max_days"))
if err != nil {
return http.StatusBadRequest, "", "invalid input"
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 581a8f6..20bbcc2 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -223,10 +223,10 @@
-
Publicly accessible data range:(in days; 0 = not public)
+
Publicly accessible data range:(in days; 0 = not public, -1 = unlimited)
+ style="width: 70px;" type="number" id="max_days" name="max_days" min="-1" required value="{{ .User.ShareDataMaxDays }}">
From 11fbce58d4b70882360bdfa0d45b9f4513e4145c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 23:35:15 +0100
Subject: [PATCH 14/15] fix: consider negative sharing intervals
---
views/settings.tpl.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 20bbcc2..4cc13db 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -380,7 +380,7 @@
Badges (Shields.io)
- {{ if gt .User.ShareDataMaxDays 0 }}
+ {{ if ne .User.ShareDataMaxDays 0 }}
Examples:
(Only available on public instances, not on localhost)
@@ -427,7 +427,7 @@
Wakapi intregrates with GitHub Readme Stats to generate fancy cards for you.
- {{ if gt .User.ShareDataMaxDays 0 }}
+ {{ if ne .User.ShareDataMaxDays 0 }}
Example:
(Only available on public instances, not on localhost)
From 6f30272b8c7d59be68f7e113c3d7a84cc6cce895 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?=
Date: Sat, 6 Feb 2021 23:44:11 +0100
Subject: [PATCH 15/15] style: card theming
---
README.md | 5 +++--
views/settings.tpl.html | 6 ++++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 23f93cb..c54a8e1 100644
--- a/README.md
+++ b/README.md
@@ -199,7 +199,7 @@ The following API endpoints are available. A more detailed Swagger documentation
### Prometheus Export
If you want to export your Wakapi statistics to Prometheus to view them in a Grafana dashboard or so please refer to an excellent tool called **[wakatime_exporter](https://github.com/MacroPower/wakatime_exporter)**.
-[](https://github.com/MacroPower/wakatime_exporter)
+[](https://github.com/MacroPower/wakatime_exporter)
It is a standalone webserver that connects to your Wakapi instance and exposes the data as Prometheus metrics. Although originally developed to scrape data from WakaTime, it will mostly for with Wakapi as well, as the APIs are partially compatible.
@@ -210,7 +210,8 @@ Wakapi plays well together with [WakaTime](https://wakatime.com). For one thing,
### GitHub Readme Stats Integrations
Wakapi also integrates with [GitHub Readme Stats](https://github.com/anuraghazra/github-readme-stats#wakatime-week-stats) to generate fancy cards for you. Here is an example.
-
+
+
## 👍 Best Practices
It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS).
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 4cc13db..dec7b45 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -433,9 +433,11 @@
(Only available on public instances, not on localhost)
-
+
Source URL:
- https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&layout=compact&api_domain=%s
+
+ https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&api_domain=%s&bg_color=2D3748&title_color=2F855A&icon_color=2F855A&text_color=ffffff&custom_title=Wakapi%20Week%20Stats&layout=compact
+
{{ end }}