diff --git a/README.md b/README.md
index e4cafa7..baaa66d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-
+
@@ -154,7 +154,7 @@ You can specify configuration options either via a config file (default: `config
| `server.tls_cert_path` / `WAKAPI_TLS_CERT_PATH` | - | Path of SSL server certificate (leave blank to not use HTTPS) |
| `server.tls_key_path` / `WAKAPI_TLS_KEY_PATH` | - | Path of SSL server private key (leave blank to not use HTTPS) |
| `server.base_path` / `WAKAPI_BASE_PATH` | `/` | Web base path (change when running behind a proxy under a sub-path) |
-| `server.public_url` / `WAKAPI_PUBLIC_URL` | `http://localhost:3000` | Public web URL for Wakapi, required for links (e.g. password reset) in e-mail |
+| `server.public_url` / `WAKAPI_PUBLIC_URL` | `http://localhost:3000` | URL at which your Wakapi instance can be found publicly |
| `security.password_salt` / `WAKAPI_PASSWORD_SALT` | - | Pepper to use for password hashing |
| `security.insecure_cookies` / `WAKAPI_INSECURE_COOKIES` | `false` | Whether or not to allow cookies over HTTP |
| `security.cookie_max_age` / `WAKAPI_COOKIE_MAX_AGE` | `172800` | Lifetime of authentication cookies in seconds or `0` to use [Session](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Define_the_lifetime_of_a_cookie) cookies |
diff --git a/config.default.yml b/config.default.yml
index 97e19b4..7a003c2 100644
--- a/config.default.yml
+++ b/config.default.yml
@@ -73,3 +73,4 @@ mail:
client_secret:
quick_start: false # whether to skip initial tasks on application startup, like summary generation
+skip_migrations: false # whether to intentionally not run database migrations, only use for dev purposes
diff --git a/config/config.go b/config/config.go
index b80e71a..72b6b7d 100644
--- a/config/config.go
+++ b/config/config.go
@@ -142,16 +142,17 @@ type SMTPMailConfig struct {
}
type Config struct {
- Env string `default:"dev" env:"ENVIRONMENT"`
- Version string `yaml:"-"`
- QuickStart bool `yaml:"quick_start" env:"WAKAPI_QUICK_START"`
- InstanceId string `yaml:"-"` // only temporary, changes between runs
- App appConfig
- Security securityConfig
- Db dbConfig
- Server serverConfig
- Sentry sentryConfig
- Mail mailConfig
+ Env string `default:"dev" env:"ENVIRONMENT"`
+ Version string `yaml:"-"`
+ QuickStart bool `yaml:"quick_start" env:"WAKAPI_QUICK_START"`
+ SkipMigrations bool `yaml:"skip_migrations" env:"WAKAPI_SKIP_MIGRATIONS"`
+ InstanceId string `yaml:"-"` // only temporary, changes between runs
+ App appConfig
+ Security securityConfig
+ Db dbConfig
+ Server serverConfig
+ Sentry sentryConfig
+ Mail mailConfig
}
func (c *Config) CreateCookie(name, value string) *http.Cookie {
diff --git a/config/sentry.go b/config/sentry.go
index 2a0ad51..d0df388 100644
--- a/config/sentry.go
+++ b/config/sentry.go
@@ -109,8 +109,9 @@ var excludedRoutes = []string{
func initSentry(config sentryConfig, debug bool) {
if err := sentry.Init(sentry.ClientOptions{
- Dsn: config.Dsn,
- Debug: debug,
+ Dsn: config.Dsn,
+ Debug: debug,
+ AttachStacktrace: true,
TracesSampler: sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) sentry.Sampled {
if !config.EnableTracing {
return sentry.SampledFalse
diff --git a/coverage/coverage.out b/coverage/coverage.out
index 6a79144..a073b0c 100644
--- a/coverage/coverage.out
+++ b/coverage/coverage.out
@@ -1,4 +1,62 @@
mode: set
+github.com/muety/wakapi/models/duration.go:24.55,38.2 2 0
+github.com/muety/wakapi/models/duration.go:40.39,42.16 2 0
+github.com/muety/wakapi/models/duration.go:45.2,46.10 2 0
+github.com/muety/wakapi/models/duration.go:42.16,44.3 1 0
+github.com/muety/wakapi/models/duration.go:49.49,50.11 1 0
+github.com/muety/wakapi/models/duration.go:65.2,65.15 1 0
+github.com/muety/wakapi/models/duration.go:69.2,69.12 1 0
+github.com/muety/wakapi/models/duration.go:51.22,52.18 1 0
+github.com/muety/wakapi/models/duration.go:53.21,54.17 1 0
+github.com/muety/wakapi/models/duration.go:55.23,56.19 1 0
+github.com/muety/wakapi/models/duration.go:57.17,58.26 1 0
+github.com/muety/wakapi/models/duration.go:59.22,60.18 1 0
+github.com/muety/wakapi/models/duration.go:61.21,62.17 1 0
+github.com/muety/wakapi/models/duration.go:65.15,67.3 1 0
+github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0
+github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0
+github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0
+github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0
+github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0
+github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0
+github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
+github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
+github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
+github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
+github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1
+github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0
+github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 1
+github.com/muety/wakapi/models/mail_address.go:36.2,36.11 1 1
+github.com/muety/wakapi/models/mail_address.go:30.21,31.21 1 1
+github.com/muety/wakapi/models/mail_address.go:34.3,34.18 1 1
+github.com/muety/wakapi/models/mail_address.go:31.21,33.4 1 1
+github.com/muety/wakapi/models/mail_address.go:39.35,41.2 1 1
+github.com/muety/wakapi/models/mail_address.go:43.43,45.22 2 0
+github.com/muety/wakapi/models/mail_address.go:48.2,48.12 1 0
+github.com/muety/wakapi/models/mail_address.go:45.22,47.3 1 0
+github.com/muety/wakapi/models/mail_address.go:51.46,53.22 2 1
+github.com/muety/wakapi/models/mail_address.go:56.2,56.12 1 1
+github.com/muety/wakapi/models/mail_address.go:53.22,55.3 1 1
+github.com/muety/wakapi/models/mail_address.go:59.40,60.22 1 1
+github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 1
+github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 1
+github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 1
+github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
+github.com/muety/wakapi/models/shared.go:40.52,42.2 1 0
+github.com/muety/wakapi/models/shared.go:44.52,47.16 3 0
+github.com/muety/wakapi/models/shared.go:50.2,52.12 3 0
+github.com/muety/wakapi/models/shared.go:47.16,49.3 1 0
+github.com/muety/wakapi/models/shared.go:55.52,61.22 2 0
+github.com/muety/wakapi/models/shared.go:76.2,79.12 3 0
+github.com/muety/wakapi/models/shared.go:62.14,66.17 2 0
+github.com/muety/wakapi/models/shared.go:69.17,71.8 2 0
+github.com/muety/wakapi/models/shared.go:72.10,73.64 1 0
+github.com/muety/wakapi/models/shared.go:66.17,68.4 1 0
+github.com/muety/wakapi/models/shared.go:82.51,85.2 2 0
+github.com/muety/wakapi/models/shared.go:87.45,89.2 1 0
+github.com/muety/wakapi/models/shared.go:91.37,93.2 1 0
+github.com/muety/wakapi/models/shared.go:95.35,97.2 1 0
+github.com/muety/wakapi/models/shared.go:99.34,101.2 1 0
github.com/muety/wakapi/models/alias.go:18.32,20.2 1 0
github.com/muety/wakapi/models/alias.go:22.37,23.35 1 0
github.com/muety/wakapi/models/alias.go:28.2,28.14 1 0
@@ -91,109 +149,9 @@ github.com/muety/wakapi/models/filters.go:219.2,219.28 1 1
github.com/muety/wakapi/models/filters.go:222.2,222.10 1 1
github.com/muety/wakapi/models/filters.go:216.41,218.3 1 0
github.com/muety/wakapi/models/filters.go:219.28,221.3 1 1
-github.com/muety/wakapi/models/interval.go:39.47,40.23 1 0
-github.com/muety/wakapi/models/interval.go:45.2,45.14 1 0
-github.com/muety/wakapi/models/interval.go:40.23,41.13 1 0
-github.com/muety/wakapi/models/interval.go:41.13,43.4 1 0
-github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1
-github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0
-github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 1
-github.com/muety/wakapi/models/mail_address.go:36.2,36.11 1 1
-github.com/muety/wakapi/models/mail_address.go:30.21,31.21 1 1
-github.com/muety/wakapi/models/mail_address.go:34.3,34.18 1 1
-github.com/muety/wakapi/models/mail_address.go:31.21,33.4 1 1
-github.com/muety/wakapi/models/mail_address.go:39.35,41.2 1 1
-github.com/muety/wakapi/models/mail_address.go:43.43,45.22 2 0
-github.com/muety/wakapi/models/mail_address.go:48.2,48.12 1 0
-github.com/muety/wakapi/models/mail_address.go:45.22,47.3 1 0
-github.com/muety/wakapi/models/mail_address.go:51.46,53.22 2 1
-github.com/muety/wakapi/models/mail_address.go:56.2,56.12 1 1
-github.com/muety/wakapi/models/mail_address.go:53.22,55.3 1 1
-github.com/muety/wakapi/models/mail_address.go:59.40,60.22 1 1
-github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 1
-github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 1
-github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 1
-github.com/muety/wakapi/models/shared.go:35.52,37.2 1 0
-github.com/muety/wakapi/models/shared.go:39.52,42.16 3 0
-github.com/muety/wakapi/models/shared.go:45.2,47.12 3 0
-github.com/muety/wakapi/models/shared.go:42.16,44.3 1 0
-github.com/muety/wakapi/models/shared.go:50.52,56.22 2 0
-github.com/muety/wakapi/models/shared.go:71.2,74.12 3 0
-github.com/muety/wakapi/models/shared.go:57.14,61.17 2 0
-github.com/muety/wakapi/models/shared.go:64.17,66.8 2 0
-github.com/muety/wakapi/models/shared.go:67.10,68.64 1 0
-github.com/muety/wakapi/models/shared.go:61.17,63.4 1 0
-github.com/muety/wakapi/models/shared.go:77.51,80.2 2 0
-github.com/muety/wakapi/models/shared.go:82.45,84.2 1 0
-github.com/muety/wakapi/models/shared.go:86.37,88.2 1 0
-github.com/muety/wakapi/models/shared.go:90.35,92.2 1 0
-github.com/muety/wakapi/models/shared.go:94.34,96.2 1 0
-github.com/muety/wakapi/models/heartbeat.go:33.34,35.2 1 1
-github.com/muety/wakapi/models/heartbeat.go:37.55,40.2 2 0
-github.com/muety/wakapi/models/heartbeat.go:42.65,44.46 2 1
-github.com/muety/wakapi/models/heartbeat.go:44.46,45.108 1 1
-github.com/muety/wakapi/models/heartbeat.go:45.108,48.4 2 1
-github.com/muety/wakapi/models/heartbeat.go:52.50,53.11 1 1
-github.com/muety/wakapi/models/heartbeat.go:68.2,68.15 1 1
-github.com/muety/wakapi/models/heartbeat.go:72.2,72.12 1 1
-github.com/muety/wakapi/models/heartbeat.go:54.22,55.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:56.21,57.17 1 1
-github.com/muety/wakapi/models/heartbeat.go:58.23,59.19 1 1
-github.com/muety/wakapi/models/heartbeat.go:60.17,61.26 1 1
-github.com/muety/wakapi/models/heartbeat.go:62.22,63.18 1 1
-github.com/muety/wakapi/models/heartbeat.go:64.21,65.17 1 0
-github.com/muety/wakapi/models/heartbeat.go:68.15,70.3 1 1
-github.com/muety/wakapi/models/heartbeat.go:75.37,91.2 1 0
-github.com/muety/wakapi/models/heartbeat.go:99.41,101.16 2 0
-github.com/muety/wakapi/models/heartbeat.go:104.2,105.10 2 0
-github.com/muety/wakapi/models/heartbeat.go:101.16,103.3 1 0
-github.com/muety/wakapi/models/heartbeat.go:108.38,118.2 1 0
github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0
github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0
-github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
-github.com/muety/wakapi/models/project_label.go:14.39,16.2 1 0
-github.com/muety/wakapi/models/duration.go:24.55,38.2 2 0
-github.com/muety/wakapi/models/duration.go:40.39,42.16 2 0
-github.com/muety/wakapi/models/duration.go:45.2,46.10 2 0
-github.com/muety/wakapi/models/duration.go:42.16,44.3 1 0
-github.com/muety/wakapi/models/duration.go:49.49,50.11 1 0
-github.com/muety/wakapi/models/duration.go:65.2,65.15 1 0
-github.com/muety/wakapi/models/duration.go:69.2,69.12 1 0
-github.com/muety/wakapi/models/duration.go:51.22,52.18 1 0
-github.com/muety/wakapi/models/duration.go:53.21,54.17 1 0
-github.com/muety/wakapi/models/duration.go:55.23,56.19 1 0
-github.com/muety/wakapi/models/duration.go:57.17,58.26 1 0
-github.com/muety/wakapi/models/duration.go:59.22,60.18 1 0
-github.com/muety/wakapi/models/duration.go:61.21,62.17 1 0
-github.com/muety/wakapi/models/duration.go:65.15,67.3 1 0
-github.com/muety/wakapi/models/durations.go:7.30,9.2 1 0
-github.com/muety/wakapi/models/durations.go:11.40,13.2 1 0
-github.com/muety/wakapi/models/durations.go:15.35,17.2 1 0
-github.com/muety/wakapi/models/durations.go:19.45,21.22 2 0
-github.com/muety/wakapi/models/durations.go:24.2,24.14 1 0
-github.com/muety/wakapi/models/durations.go:21.22,23.3 1 0
-github.com/muety/wakapi/models/durations.go:27.39,30.2 2 0
-github.com/muety/wakapi/models/durations.go:32.39,34.18 1 0
-github.com/muety/wakapi/models/durations.go:37.2,37.16 1 0
-github.com/muety/wakapi/models/durations.go:34.18,36.3 1 0
-github.com/muety/wakapi/models/durations.go:40.38,42.18 1 0
-github.com/muety/wakapi/models/durations.go:45.2,45.24 1 0
-github.com/muety/wakapi/models/durations.go:42.18,44.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/mail.go:19.44,23.2 3 0
-github.com/muety/wakapi/models/mail.go:25.44,29.2 3 0
-github.com/muety/wakapi/models/mail.go:31.32,44.2 1 0
-github.com/muety/wakapi/models/mail.go:46.41,48.2 1 0
github.com/muety/wakapi/models/summary.go:64.29,66.2 1 1
github.com/muety/wakapi/models/summary.go:68.35,70.2 1 0
github.com/muety/wakapi/models/summary.go:72.38,74.2 1 0
@@ -315,6 +273,161 @@ github.com/muety/wakapi/models/user.go:142.45,144.2 1 0
github.com/muety/wakapi/models/user.go:146.45,148.2 1 0
github.com/muety/wakapi/models/user.go:150.39,152.2 1 0
github.com/muety/wakapi/models/user.go:154.39,157.2 2 0
+github.com/muety/wakapi/models/durations.go:7.30,9.2 1 0
+github.com/muety/wakapi/models/durations.go:11.40,13.2 1 0
+github.com/muety/wakapi/models/durations.go:15.35,17.2 1 0
+github.com/muety/wakapi/models/durations.go:19.45,21.22 2 0
+github.com/muety/wakapi/models/durations.go:24.2,24.14 1 0
+github.com/muety/wakapi/models/durations.go:21.22,23.3 1 0
+github.com/muety/wakapi/models/durations.go:27.39,30.2 2 0
+github.com/muety/wakapi/models/durations.go:32.39,34.18 1 0
+github.com/muety/wakapi/models/durations.go:37.2,37.16 1 0
+github.com/muety/wakapi/models/durations.go:34.18,36.3 1 0
+github.com/muety/wakapi/models/durations.go:40.38,42.18 1 0
+github.com/muety/wakapi/models/durations.go:45.2,45.24 1 0
+github.com/muety/wakapi/models/durations.go:42.18,44.3 1 0
+github.com/muety/wakapi/models/mail.go:19.44,23.2 3 0
+github.com/muety/wakapi/models/mail.go:25.44,29.2 3 0
+github.com/muety/wakapi/models/mail.go:31.32,44.2 1 0
+github.com/muety/wakapi/models/mail.go:46.41,48.2 1 0
+github.com/muety/wakapi/models/project_label.go:14.39,16.2 1 0
+github.com/muety/wakapi/models/heartbeat.go:33.34,35.2 1 1
+github.com/muety/wakapi/models/heartbeat.go:37.55,40.2 2 0
+github.com/muety/wakapi/models/heartbeat.go:42.65,44.46 2 1
+github.com/muety/wakapi/models/heartbeat.go:44.46,45.108 1 1
+github.com/muety/wakapi/models/heartbeat.go:45.108,48.4 2 1
+github.com/muety/wakapi/models/heartbeat.go:52.50,53.11 1 1
+github.com/muety/wakapi/models/heartbeat.go:68.2,68.15 1 1
+github.com/muety/wakapi/models/heartbeat.go:72.2,72.12 1 1
+github.com/muety/wakapi/models/heartbeat.go:54.22,55.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:56.21,57.17 1 1
+github.com/muety/wakapi/models/heartbeat.go:58.23,59.19 1 1
+github.com/muety/wakapi/models/heartbeat.go:60.17,61.26 1 1
+github.com/muety/wakapi/models/heartbeat.go:62.22,63.18 1 1
+github.com/muety/wakapi/models/heartbeat.go:64.21,65.17 1 0
+github.com/muety/wakapi/models/heartbeat.go:68.15,70.3 1 1
+github.com/muety/wakapi/models/heartbeat.go:75.37,91.2 1 0
+github.com/muety/wakapi/models/heartbeat.go:99.41,101.16 2 0
+github.com/muety/wakapi/models/heartbeat.go:104.2,105.10 2 0
+github.com/muety/wakapi/models/heartbeat.go:101.16,103.3 1 0
+github.com/muety/wakapi/models/heartbeat.go:108.38,118.2 1 0
+github.com/muety/wakapi/models/interval.go:39.47,40.23 1 0
+github.com/muety/wakapi/models/interval.go:45.2,45.14 1 0
+github.com/muety/wakapi/models/interval.go:40.23,41.13 1 0
+github.com/muety/wakapi/models/interval.go:41.13,43.4 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:158.64,160.2 1 0
+github.com/muety/wakapi/config/config.go:162.59,164.2 1 0
+github.com/muety/wakapi/config/config.go:166.82,176.2 1 0
+github.com/muety/wakapi/config/config.go:178.31,180.2 1 0
+github.com/muety/wakapi/config/config.go:182.32,184.2 1 0
+github.com/muety/wakapi/config/config.go:186.74,187.19 1 0
+github.com/muety/wakapi/config/config.go:188.10,189.34 1 0
+github.com/muety/wakapi/config/config.go:189.34,190.90 1 0
+github.com/muety/wakapi/config/config.go:193.4,193.100 1 0
+github.com/muety/wakapi/config/config.go:196.4,196.91 1 0
+github.com/muety/wakapi/config/config.go:199.4,199.95 1 0
+github.com/muety/wakapi/config/config.go:202.4,202.93 1 0
+github.com/muety/wakapi/config/config.go:205.4,205.97 1 0
+github.com/muety/wakapi/config/config.go:208.4,208.101 1 0
+github.com/muety/wakapi/config/config.go:211.4,211.98 1 0
+github.com/muety/wakapi/config/config.go:214.4,214.97 1 0
+github.com/muety/wakapi/config/config.go:217.4,217.14 1 0
+github.com/muety/wakapi/config/config.go:190.90,192.5 1 0
+github.com/muety/wakapi/config/config.go:193.100,195.5 1 0
+github.com/muety/wakapi/config/config.go:196.91,198.5 1 0
+github.com/muety/wakapi/config/config.go:199.95,201.5 1 0
+github.com/muety/wakapi/config/config.go:202.93,204.5 1 0
+github.com/muety/wakapi/config/config.go:205.97,207.5 1 0
+github.com/muety/wakapi/config/config.go:208.101,210.5 1 0
+github.com/muety/wakapi/config/config.go:211.98,213.5 1 0
+github.com/muety/wakapi/config/config.go:214.97,216.5 1 0
+github.com/muety/wakapi/config/config.go:222.60,224.2 1 0
+github.com/muety/wakapi/config/config.go:226.59,228.2 1 0
+github.com/muety/wakapi/config/config.go:230.57,232.2 1 0
+github.com/muety/wakapi/config/config.go:234.53,236.2 1 0
+github.com/muety/wakapi/config/config.go:238.55,241.2 2 0
+github.com/muety/wakapi/config/config.go:243.50,245.2 1 0
+github.com/muety/wakapi/config/config.go:247.54,250.2 2 0
+github.com/muety/wakapi/config/config.go:252.36,254.2 1 0
+github.com/muety/wakapi/config/config.go:256.35,258.2 1 0
+github.com/muety/wakapi/config/config.go:260.38,262.2 1 0
+github.com/muety/wakapi/config/config.go:264.46,266.2 1 0
+github.com/muety/wakapi/config/config.go:268.43,270.2 1 0
+github.com/muety/wakapi/config/config.go:272.29,274.2 1 1
+github.com/muety/wakapi/config/config.go:276.48,287.16 2 0
+github.com/muety/wakapi/config/config.go:291.2,292.53 2 0
+github.com/muety/wakapi/config/config.go:296.2,296.15 1 0
+github.com/muety/wakapi/config/config.go:287.16,289.3 1 0
+github.com/muety/wakapi/config/config.go:292.53,294.3 1 0
+github.com/muety/wakapi/config/config.go:299.38,300.43 1 0
+github.com/muety/wakapi/config/config.go:303.2,303.15 1 0
+github.com/muety/wakapi/config/config.go:300.43,302.3 1 0
+github.com/muety/wakapi/config/config.go:306.45,307.27 1 0
+github.com/muety/wakapi/config/config.go:310.2,310.24 1 0
+github.com/muety/wakapi/config/config.go:313.2,313.25 1 0
+github.com/muety/wakapi/config/config.go:316.2,316.15 1 0
+github.com/muety/wakapi/config/config.go:307.27,309.3 1 0
+github.com/muety/wakapi/config/config.go:310.24,312.3 1 0
+github.com/muety/wakapi/config/config.go:313.25,315.3 1 0
+github.com/muety/wakapi/config/config.go:319.77,320.29 1 0
+github.com/muety/wakapi/config/config.go:325.2,325.19 1 0
+github.com/muety/wakapi/config/config.go:320.29,321.18 1 0
+github.com/muety/wakapi/config/config.go:321.18,323.4 1 0
+github.com/muety/wakapi/config/config.go:328.42,329.28 1 0
+github.com/muety/wakapi/config/config.go:345.2,345.20 1 0
+github.com/muety/wakapi/config/config.go:330.52,331.21 1 0
+github.com/muety/wakapi/config/config.go:332.53,333.22 1 0
+github.com/muety/wakapi/config/config.go:334.55,335.24 1 0
+github.com/muety/wakapi/config/config.go:336.54,337.23 1 0
+github.com/muety/wakapi/config/config.go:338.52,339.21 1 0
+github.com/muety/wakapi/config/config.go:340.54,341.23 1 0
+github.com/muety/wakapi/config/config.go:342.52,343.21 1 0
+github.com/muety/wakapi/config/config.go:348.26,350.2 1 0
+github.com/muety/wakapi/config/config.go:352.20,354.2 1 0
+github.com/muety/wakapi/config/config.go:356.35,361.96 3 0
+github.com/muety/wakapi/config/config.go:365.2,375.52 7 0
+github.com/muety/wakapi/config/config.go:379.2,379.47 1 0
+github.com/muety/wakapi/config/config.go:385.2,385.29 1 0
+github.com/muety/wakapi/config/config.go:391.2,391.106 1 0
+github.com/muety/wakapi/config/config.go:394.2,394.28 1 0
+github.com/muety/wakapi/config/config.go:397.2,397.51 1 0
+github.com/muety/wakapi/config/config.go:401.2,401.94 1 0
+github.com/muety/wakapi/config/config.go:404.2,404.81 1 0
+github.com/muety/wakapi/config/config.go:407.2,407.75 1 0
+github.com/muety/wakapi/config/config.go:410.2,410.74 1 0
+github.com/muety/wakapi/config/config.go:414.2,415.14 2 0
+github.com/muety/wakapi/config/config.go:361.96,363.3 1 0
+github.com/muety/wakapi/config/config.go:375.52,377.3 1 0
+github.com/muety/wakapi/config/config.go:379.47,380.14 1 0
+github.com/muety/wakapi/config/config.go:380.14,382.4 1 0
+github.com/muety/wakapi/config/config.go:385.29,388.3 2 0
+github.com/muety/wakapi/config/config.go:391.106,393.3 1 0
+github.com/muety/wakapi/config/config.go:394.28,396.3 1 0
+github.com/muety/wakapi/config/config.go:397.51,400.3 2 0
+github.com/muety/wakapi/config/config.go:401.94,403.3 1 0
+github.com/muety/wakapi/config/config.go:404.81,406.3 1 0
+github.com/muety/wakapi/config/config.go:407.75,409.3 1 0
+github.com/muety/wakapi/config/config.go:410.74,412.3 1 0
+github.com/muety/wakapi/config/db.go:39.50,40.19 1 0
+github.com/muety/wakapi/config/db.go:53.2,53.12 1 0
+github.com/muety/wakapi/config/db.go:41.23,45.5 1 0
+github.com/muety/wakapi/config/db.go:46.26,49.5 1 0
+github.com/muety/wakapi/config/db.go:50.24,51.48 1 0
+github.com/muety/wakapi/config/db.go:56.53,66.2 1 1
+github.com/muety/wakapi/config/db.go:68.56,70.16 2 1
+github.com/muety/wakapi/config/db.go:74.2,81.3 1 1
+github.com/muety/wakapi/config/db.go:70.16,72.3 1 0
+github.com/muety/wakapi/config/db.go:84.54,86.2 1 1
+github.com/muety/wakapi/config/eventbus.go:26.13,28.2 1 1
+github.com/muety/wakapi/config/eventbus.go:30.26,32.2 1 0
+github.com/muety/wakapi/config/fs.go:9.56,10.19 1 0
+github.com/muety/wakapi/config/fs.go:13.2,13.19 1 0
+github.com/muety/wakapi/config/fs.go:10.19,12.3 1 0
github.com/muety/wakapi/config/sentry.go:22.35,24.2 1 0
github.com/muety/wakapi/config/sentry.go:26.62,29.2 2 0
github.com/muety/wakapi/config/sentry.go:39.33,46.2 2 0
@@ -348,276 +461,76 @@ github.com/muety/wakapi/config/sentry.go:142.17,144.3 1 0
github.com/muety/wakapi/config/sentry.go:147.49,151.51 2 0
github.com/muety/wakapi/config/sentry.go:154.2,154.12 1 0
github.com/muety/wakapi/config/sentry.go:151.51,153.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:157.64,159.2 1 0
-github.com/muety/wakapi/config/config.go:161.59,163.2 1 0
-github.com/muety/wakapi/config/config.go:165.82,175.2 1 0
-github.com/muety/wakapi/config/config.go:177.31,179.2 1 0
-github.com/muety/wakapi/config/config.go:181.32,183.2 1 0
-github.com/muety/wakapi/config/config.go:185.74,186.19 1 0
-github.com/muety/wakapi/config/config.go:187.10,188.34 1 0
-github.com/muety/wakapi/config/config.go:188.34,189.90 1 0
-github.com/muety/wakapi/config/config.go:192.4,192.100 1 0
-github.com/muety/wakapi/config/config.go:195.4,195.91 1 0
-github.com/muety/wakapi/config/config.go:198.4,198.95 1 0
-github.com/muety/wakapi/config/config.go:201.4,201.93 1 0
-github.com/muety/wakapi/config/config.go:204.4,204.97 1 0
-github.com/muety/wakapi/config/config.go:207.4,207.101 1 0
-github.com/muety/wakapi/config/config.go:210.4,210.98 1 0
-github.com/muety/wakapi/config/config.go:213.4,213.97 1 0
-github.com/muety/wakapi/config/config.go:216.4,216.14 1 0
-github.com/muety/wakapi/config/config.go:189.90,191.5 1 0
-github.com/muety/wakapi/config/config.go:192.100,194.5 1 0
-github.com/muety/wakapi/config/config.go:195.91,197.5 1 0
-github.com/muety/wakapi/config/config.go:198.95,200.5 1 0
-github.com/muety/wakapi/config/config.go:201.93,203.5 1 0
-github.com/muety/wakapi/config/config.go:204.97,206.5 1 0
-github.com/muety/wakapi/config/config.go:207.101,209.5 1 0
-github.com/muety/wakapi/config/config.go:210.98,212.5 1 0
-github.com/muety/wakapi/config/config.go:213.97,215.5 1 0
-github.com/muety/wakapi/config/config.go:221.60,223.2 1 0
-github.com/muety/wakapi/config/config.go:225.59,227.2 1 0
-github.com/muety/wakapi/config/config.go:229.57,231.2 1 0
-github.com/muety/wakapi/config/config.go:233.53,235.2 1 0
-github.com/muety/wakapi/config/config.go:237.55,240.2 2 0
-github.com/muety/wakapi/config/config.go:242.50,244.2 1 0
-github.com/muety/wakapi/config/config.go:246.54,249.2 2 0
-github.com/muety/wakapi/config/config.go:251.36,253.2 1 0
-github.com/muety/wakapi/config/config.go:255.35,257.2 1 0
-github.com/muety/wakapi/config/config.go:259.38,261.2 1 0
-github.com/muety/wakapi/config/config.go:263.46,265.2 1 0
-github.com/muety/wakapi/config/config.go:267.43,269.2 1 0
-github.com/muety/wakapi/config/config.go:271.29,273.2 1 1
-github.com/muety/wakapi/config/config.go:275.48,286.16 2 0
-github.com/muety/wakapi/config/config.go:290.2,291.53 2 0
-github.com/muety/wakapi/config/config.go:295.2,295.15 1 0
-github.com/muety/wakapi/config/config.go:286.16,288.3 1 0
-github.com/muety/wakapi/config/config.go:291.53,293.3 1 0
-github.com/muety/wakapi/config/config.go:298.38,299.43 1 0
-github.com/muety/wakapi/config/config.go:302.2,302.15 1 0
-github.com/muety/wakapi/config/config.go:299.43,301.3 1 0
-github.com/muety/wakapi/config/config.go:305.45,306.27 1 0
-github.com/muety/wakapi/config/config.go:309.2,309.24 1 0
-github.com/muety/wakapi/config/config.go:312.2,312.25 1 0
-github.com/muety/wakapi/config/config.go:315.2,315.15 1 0
-github.com/muety/wakapi/config/config.go:306.27,308.3 1 0
-github.com/muety/wakapi/config/config.go:309.24,311.3 1 0
-github.com/muety/wakapi/config/config.go:312.25,314.3 1 0
-github.com/muety/wakapi/config/config.go:318.77,319.29 1 0
-github.com/muety/wakapi/config/config.go:324.2,324.19 1 0
-github.com/muety/wakapi/config/config.go:319.29,320.18 1 0
-github.com/muety/wakapi/config/config.go:320.18,322.4 1 0
-github.com/muety/wakapi/config/config.go:327.42,328.28 1 0
-github.com/muety/wakapi/config/config.go:344.2,344.20 1 0
-github.com/muety/wakapi/config/config.go:329.52,330.21 1 0
-github.com/muety/wakapi/config/config.go:331.53,332.22 1 0
-github.com/muety/wakapi/config/config.go:333.55,334.24 1 0
-github.com/muety/wakapi/config/config.go:335.54,336.23 1 0
-github.com/muety/wakapi/config/config.go:337.52,338.21 1 0
-github.com/muety/wakapi/config/config.go:339.54,340.23 1 0
-github.com/muety/wakapi/config/config.go:341.52,342.21 1 0
-github.com/muety/wakapi/config/config.go:347.26,349.2 1 0
-github.com/muety/wakapi/config/config.go:351.20,353.2 1 0
-github.com/muety/wakapi/config/config.go:355.35,360.96 3 0
-github.com/muety/wakapi/config/config.go:364.2,374.52 7 0
-github.com/muety/wakapi/config/config.go:378.2,378.47 1 0
-github.com/muety/wakapi/config/config.go:384.2,384.29 1 0
-github.com/muety/wakapi/config/config.go:390.2,390.106 1 0
-github.com/muety/wakapi/config/config.go:393.2,393.28 1 0
-github.com/muety/wakapi/config/config.go:396.2,396.51 1 0
-github.com/muety/wakapi/config/config.go:400.2,400.94 1 0
-github.com/muety/wakapi/config/config.go:403.2,403.81 1 0
-github.com/muety/wakapi/config/config.go:406.2,406.75 1 0
-github.com/muety/wakapi/config/config.go:409.2,409.74 1 0
-github.com/muety/wakapi/config/config.go:413.2,414.14 2 0
-github.com/muety/wakapi/config/config.go:360.96,362.3 1 0
-github.com/muety/wakapi/config/config.go:374.52,376.3 1 0
-github.com/muety/wakapi/config/config.go:378.47,379.14 1 0
-github.com/muety/wakapi/config/config.go:379.14,381.4 1 0
-github.com/muety/wakapi/config/config.go:384.29,387.3 2 0
-github.com/muety/wakapi/config/config.go:390.106,392.3 1 0
-github.com/muety/wakapi/config/config.go:393.28,395.3 1 0
-github.com/muety/wakapi/config/config.go:396.51,399.3 2 0
-github.com/muety/wakapi/config/config.go:400.94,402.3 1 0
-github.com/muety/wakapi/config/config.go:403.81,405.3 1 0
-github.com/muety/wakapi/config/config.go:406.75,408.3 1 0
-github.com/muety/wakapi/config/config.go:409.74,411.3 1 0
-github.com/muety/wakapi/config/db.go:39.50,40.19 1 0
-github.com/muety/wakapi/config/db.go:53.2,53.12 1 0
-github.com/muety/wakapi/config/db.go:41.23,45.5 1 0
-github.com/muety/wakapi/config/db.go:46.26,49.5 1 0
-github.com/muety/wakapi/config/db.go:50.24,51.48 1 0
-github.com/muety/wakapi/config/db.go:56.53,66.2 1 1
-github.com/muety/wakapi/config/db.go:68.56,70.16 2 1
-github.com/muety/wakapi/config/db.go:74.2,81.3 1 1
-github.com/muety/wakapi/config/db.go:70.16,72.3 1 0
-github.com/muety/wakapi/config/db.go:84.54,86.2 1 1
-github.com/muety/wakapi/config/eventbus.go:26.13,28.2 1 1
-github.com/muety/wakapi/config/eventbus.go:30.26,32.2 1 0
-github.com/muety/wakapi/config/fs.go:9.56,10.19 1 0
-github.com/muety/wakapi/config/fs.go:13.2,13.19 1 0
-github.com/muety/wakapi/config/fs.go:10.19,12.3 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:30.112,37.2 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:39.59,43.2 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:55.68,60.79 4 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:64.2,65.75 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:71.2,73.16 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:78.2,81.62 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:87.2,89.22 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:114.2,114.19 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:120.2,121.50 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:126.2,127.16 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:133.2,135.44 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:60.79,62.3 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:65.75,66.59 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:66.59,68.4 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:73.16,76.3 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:81.62,85.3 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:90.17,92.68 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:93.12,95.63 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:96.16,98.67 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:99.18,101.69 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:102.17,104.68 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:105.15,107.66 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:109.10,111.30 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:114.19,118.3 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:121.50,124.3 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:127.16,131.3 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:138.144,140.16 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:144.2,151.29 3 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:155.2,163.16 2 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:167.2,167.36 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:140.16,142.3 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:151.29,153.3 1 0
-github.com/muety/wakapi/routes/compat/shields/v1/badge.go:163.16,165.3 1 0
-github.com/muety/wakapi/middlewares/sentry.go:15.60,16.43 1 0
-github.com/muety/wakapi/middlewares/sentry.go:16.43,20.3 1 0
-github.com/muety/wakapi/middlewares/sentry.go:23.78,26.54 3 0
-github.com/muety/wakapi/middlewares/sentry.go:26.54,27.43 1 0
-github.com/muety/wakapi/middlewares/sentry.go:27.43,29.4 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:30.91,36.2 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:38.90,41.2 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:43.90,46.2 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:48.71,49.71 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:49.71,51.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:54.107,58.16 3 0
-github.com/muety/wakapi/middlewares/authenticate.go:61.2,61.16 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:65.2,65.31 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:81.2,82.12 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:58.16,60.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:61.16,63.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:65.31,66.31 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:71.3,71.29 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:78.3,78.9 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:66.31,69.4 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:71.29,74.4 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:74.9,77.4 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:85.70,86.39 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:91.2,91.14 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:86.39,87.60 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:87.60,89.4 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:94.98,96.16 2 1
-github.com/muety/wakapi/middlewares/authenticate.go:100.2,103.16 4 1
-github.com/muety/wakapi/middlewares/authenticate.go:106.2,106.18 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:96.16,98.3 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:103.16,105.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:109.97,113.19 4 1
-github.com/muety/wakapi/middlewares/authenticate.go:116.2,117.16 2 1
-github.com/muety/wakapi/middlewares/authenticate.go:120.2,120.18 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:113.19,115.3 1 1
-github.com/muety/wakapi/middlewares/authenticate.go:117.16,119.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:123.92,125.16 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:129.2,130.16 2 0
-github.com/muety/wakapi/middlewares/authenticate.go:137.2,137.18 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:125.16,127.3 1 0
-github.com/muety/wakapi/middlewares/authenticate.go:130.16,132.3 1 0
-github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0
-github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0
-github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0
-github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0
-github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0
-github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0
-github.com/muety/wakapi/middlewares/logging.go:20.102,21.43 1 0
-github.com/muety/wakapi/middlewares/logging.go:21.43,27.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:30.80,39.44 7 0
-github.com/muety/wakapi/middlewares/logging.go:45.2,54.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:39.44,40.38 1 0
-github.com/muety/wakapi/middlewares/logging.go:40.38,42.4 1 0
-github.com/muety/wakapi/middlewares/logging.go:57.41,59.14 2 0
-github.com/muety/wakapi/middlewares/logging.go:62.2,62.14 1 0
-github.com/muety/wakapi/middlewares/logging.go:65.2,65.11 1 0
-github.com/muety/wakapi/middlewares/logging.go:59.14,61.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:62.14,64.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:68.41,69.42 1 0
-github.com/muety/wakapi/middlewares/logging.go:72.2,72.12 1 0
-github.com/muety/wakapi/middlewares/logging.go:69.42,71.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:103.52,105.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:117.45,118.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:118.20,122.3 3 0
-github.com/muety/wakapi/middlewares/logging.go:124.54,127.18 3 0
-github.com/muety/wakapi/middlewares/logging.go:134.2,135.15 2 0
-github.com/muety/wakapi/middlewares/logging.go:127.18,130.17 2 0
-github.com/muety/wakapi/middlewares/logging.go:130.17,132.4 1 0
-github.com/muety/wakapi/middlewares/logging.go:137.42,138.20 1 0
-github.com/muety/wakapi/middlewares/logging.go:138.20,140.3 1 0
-github.com/muety/wakapi/middlewares/logging.go:142.36,144.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:145.42,147.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:148.40,150.2 1 0
-github.com/muety/wakapi/middlewares/logging.go:151.52,153.2 1 0
-github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0
-github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0
-github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0
-github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0
-github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0
-github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0
-github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0
-github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0
-github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0
-github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0
-github.com/muety/wakapi/middlewares/security.go:19.62,20.43 1 0
-github.com/muety/wakapi/middlewares/security.go:20.43,22.3 1 0
-github.com/muety/wakapi/middlewares/security.go:25.80,26.36 1 0
-github.com/muety/wakapi/middlewares/security.go:31.2,31.27 1 0
-github.com/muety/wakapi/middlewares/security.go:26.36,27.30 1 0
-github.com/muety/wakapi/middlewares/security.go:27.30,29.4 1 0
-github.com/muety/wakapi/utils/date.go:14.43,16.2 1 1
-github.com/muety/wakapi/utils/date.go:18.48,20.2 1 0
-github.com/muety/wakapi/utils/date.go:22.41,24.21 2 1
-github.com/muety/wakapi/utils/date.go:27.2,27.23 1 1
-github.com/muety/wakapi/utils/date.go:24.21,26.3 1 0
-github.com/muety/wakapi/utils/date.go:30.46,32.2 1 0
-github.com/muety/wakapi/utils/date.go:34.51,36.2 1 0
-github.com/muety/wakapi/utils/date.go:38.44,41.2 2 1
-github.com/muety/wakapi/utils/date.go:43.52,45.2 1 0
-github.com/muety/wakapi/utils/date.go:47.45,49.2 1 0
-github.com/muety/wakapi/utils/date.go:51.51,53.2 1 0
-github.com/muety/wakapi/utils/date.go:55.44,57.2 1 0
-github.com/muety/wakapi/utils/date.go:60.42,62.2 1 1
-github.com/muety/wakapi/utils/date.go:65.46,67.2 1 0
-github.com/muety/wakapi/utils/date.go:70.41,72.21 2 1
-github.com/muety/wakapi/utils/date.go:75.2,75.33 1 1
-github.com/muety/wakapi/utils/date.go:72.21,74.3 1 1
-github.com/muety/wakapi/utils/date.go:79.63,81.2 1 0
-github.com/muety/wakapi/utils/date.go:84.62,90.2 5 0
-github.com/muety/wakapi/utils/date.go:93.67,96.33 2 1
-github.com/muety/wakapi/utils/date.go:105.2,105.18 1 1
-github.com/muety/wakapi/utils/date.go:96.33,98.19 2 1
-github.com/muety/wakapi/utils/date.go:101.3,102.10 2 1
-github.com/muety/wakapi/utils/date.go:98.19,100.4 1 1
-github.com/muety/wakapi/utils/date.go:108.50,114.2 5 0
-github.com/muety/wakapi/utils/date.go:117.36,120.2 2 0
-github.com/muety/wakapi/utils/date.go:123.79,126.36 3 1
-github.com/muety/wakapi/utils/date.go:130.2,130.21 1 1
-github.com/muety/wakapi/utils/date.go:134.2,134.21 1 1
-github.com/muety/wakapi/utils/date.go:138.2,138.13 1 1
-github.com/muety/wakapi/utils/date.go:126.36,129.3 2 0
-github.com/muety/wakapi/utils/date.go:130.21,133.3 2 1
-github.com/muety/wakapi/utils/date.go:134.21,137.3 2 1
+github.com/muety/wakapi/utils/common.go:18.73,19.58 1 0
+github.com/muety/wakapi/utils/common.go:22.2,22.87 1 0
+github.com/muety/wakapi/utils/common.go:25.2,25.64 1 0
+github.com/muety/wakapi/utils/common.go:19.58,21.3 1 0
+github.com/muety/wakapi/utils/common.go:22.87,24.3 1 0
+github.com/muety/wakapi/utils/common.go:28.40,30.2 1 0
+github.com/muety/wakapi/utils/common.go:32.44,34.2 1 0
+github.com/muety/wakapi/utils/common.go:36.49,38.2 1 0
+github.com/muety/wakapi/utils/common.go:40.45,42.2 1 0
+github.com/muety/wakapi/utils/common.go:44.24,46.2 1 0
+github.com/muety/wakapi/utils/common.go:48.56,51.45 3 1
+github.com/muety/wakapi/utils/common.go:54.2,54.40 1 1
+github.com/muety/wakapi/utils/common.go:51.45,53.3 1 1
+github.com/muety/wakapi/utils/date.go:9.48,11.2 1 0
+github.com/muety/wakapi/utils/date.go:13.51,15.2 1 0
+github.com/muety/wakapi/utils/date.go:17.52,19.2 1 0
+github.com/muety/wakapi/utils/date.go:21.51,23.2 1 0
+github.com/muety/wakapi/utils/date.go:26.41,28.21 2 0
+github.com/muety/wakapi/utils/date.go:31.2,31.33 1 0
+github.com/muety/wakapi/utils/date.go:28.21,30.3 1 0
+github.com/muety/wakapi/utils/date.go:35.67,38.33 2 1
+github.com/muety/wakapi/utils/date.go:47.2,47.18 1 1
+github.com/muety/wakapi/utils/date.go:38.33,40.19 2 1
+github.com/muety/wakapi/utils/date.go:43.3,44.10 2 1
+github.com/muety/wakapi/utils/date.go:40.19,42.4 1 1
+github.com/muety/wakapi/utils/date.go:50.50,56.2 5 0
+github.com/muety/wakapi/utils/date.go:59.36,62.2 2 0
+github.com/muety/wakapi/utils/db.go:8.34,9.37 1 0
+github.com/muety/wakapi/utils/db.go:17.2,18.14 2 0
+github.com/muety/wakapi/utils/db.go:9.37,11.110 2 0
+github.com/muety/wakapi/utils/db.go:15.3,15.20 1 0
+github.com/muety/wakapi/utils/db.go:11.110,14.4 2 0
+github.com/muety/wakapi/utils/db.go:21.39,22.37 1 0
+github.com/muety/wakapi/utils/db.go:30.2,31.14 2 0
+github.com/muety/wakapi/utils/db.go:22.37,24.119 2 0
+github.com/muety/wakapi/utils/db.go:28.3,28.20 1 0
+github.com/muety/wakapi/utils/db.go:24.119,27.4 2 0
+github.com/muety/wakapi/utils/http.go:21.13,23.2 1 1
+github.com/muety/wakapi/utils/http.go:25.90,28.58 3 0
+github.com/muety/wakapi/utils/http.go:28.58,30.3 1 0
+github.com/muety/wakapi/utils/http.go:33.62,35.48 2 0
+github.com/muety/wakapi/utils/http.go:38.2,38.93 1 0
+github.com/muety/wakapi/utils/http.go:43.2,43.14 1 0
+github.com/muety/wakapi/utils/http.go:35.48,37.3 1 0
+github.com/muety/wakapi/utils/http.go:38.93,39.89 1 0
+github.com/muety/wakapi/utils/http.go:39.89,41.4 1 0
github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
+github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
+github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
+github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
+github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
+github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
+github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
+github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
+github.com/muety/wakapi/utils/auth.go:37.65,39.85 2 0
+github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
+github.com/muety/wakapi/utils/auth.go:39.85,41.3 1 0
+github.com/muety/wakapi/utils/auth.go:47.94,49.16 2 0
+github.com/muety/wakapi/utils/auth.go:53.2,53.107 1 0
+github.com/muety/wakapi/utils/auth.go:57.2,57.22 1 0
+github.com/muety/wakapi/utils/auth.go:49.16,51.3 1 0
+github.com/muety/wakapi/utils/auth.go:53.107,55.3 1 0
+github.com/muety/wakapi/utils/auth.go:60.56,64.2 3 0
+github.com/muety/wakapi/utils/auth.go:66.55,69.16 3 0
+github.com/muety/wakapi/utils/auth.go:72.2,72.16 1 0
+github.com/muety/wakapi/utils/auth.go:69.16,71.3 1 0
+github.com/muety/wakapi/utils/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/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
@@ -669,54 +582,6 @@ github.com/muety/wakapi/utils/summary.go:130.47,132.3 1 0
github.com/muety/wakapi/utils/summary.go:136.48,140.51 2 0
github.com/muety/wakapi/utils/summary.go:143.2,143.12 1 0
github.com/muety/wakapi/utils/summary.go:140.51,142.3 1 0
-github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
-github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
-github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
-github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
-github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
-github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
-github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
-github.com/muety/wakapi/utils/auth.go:37.65,39.85 2 0
-github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
-github.com/muety/wakapi/utils/auth.go:39.85,41.3 1 0
-github.com/muety/wakapi/utils/auth.go:47.94,49.16 2 0
-github.com/muety/wakapi/utils/auth.go:53.2,53.107 1 0
-github.com/muety/wakapi/utils/auth.go:57.2,57.22 1 0
-github.com/muety/wakapi/utils/auth.go:49.16,51.3 1 0
-github.com/muety/wakapi/utils/auth.go:53.107,55.3 1 0
-github.com/muety/wakapi/utils/auth.go:60.56,64.2 3 0
-github.com/muety/wakapi/utils/auth.go:66.55,69.16 3 0
-github.com/muety/wakapi/utils/auth.go:72.2,72.16 1 0
-github.com/muety/wakapi/utils/auth.go:69.16,71.3 1 0
-github.com/muety/wakapi/utils/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:18.73,19.58 1 0
-github.com/muety/wakapi/utils/common.go:22.2,22.87 1 0
-github.com/muety/wakapi/utils/common.go:25.2,25.64 1 0
-github.com/muety/wakapi/utils/common.go:19.58,21.3 1 0
-github.com/muety/wakapi/utils/common.go:22.87,24.3 1 0
-github.com/muety/wakapi/utils/common.go:28.40,30.2 1 0
-github.com/muety/wakapi/utils/common.go:32.44,34.2 1 0
-github.com/muety/wakapi/utils/common.go:36.49,38.2 1 0
-github.com/muety/wakapi/utils/common.go:40.45,42.2 1 0
-github.com/muety/wakapi/utils/common.go:44.24,46.2 1 0
-github.com/muety/wakapi/utils/common.go:48.56,51.45 3 1
-github.com/muety/wakapi/utils/common.go:54.2,54.40 1 1
-github.com/muety/wakapi/utils/common.go:51.45,53.3 1 1
-github.com/muety/wakapi/utils/db.go:8.34,9.37 1 0
-github.com/muety/wakapi/utils/db.go:17.2,18.14 2 0
-github.com/muety/wakapi/utils/db.go:9.37,11.110 2 0
-github.com/muety/wakapi/utils/db.go:15.3,15.20 1 0
-github.com/muety/wakapi/utils/db.go:11.110,14.4 2 0
-github.com/muety/wakapi/utils/db.go:21.39,22.37 1 0
-github.com/muety/wakapi/utils/db.go:30.2,31.14 2 0
-github.com/muety/wakapi/utils/db.go:22.37,24.119 2 0
-github.com/muety/wakapi/utils/db.go:28.3,28.20 1 0
-github.com/muety/wakapi/utils/db.go:24.119,27.4 2 0
-github.com/muety/wakapi/utils/http.go:9.90,12.58 3 0
-github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
github.com/muety/wakapi/utils/template.go:13.41,15.16 2 0
github.com/muety/wakapi/utils/template.go:18.2,18.23 1 0
github.com/muety/wakapi/utils/template.go:15.16,17.3 1 0
@@ -736,6 +601,208 @@ github.com/muety/wakapi/utils/template.go:39.51,40.12 1 0
github.com/muety/wakapi/utils/template.go:44.17,46.4 1 0
github.com/muety/wakapi/utils/template.go:48.17,50.4 1 0
github.com/muety/wakapi/utils/template.go:55.17,57.4 1 0
+github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0
+github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0
+github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0
+github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0
+github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0
+github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0
+github.com/muety/wakapi/middlewares/logging.go:20.102,21.43 1 0
+github.com/muety/wakapi/middlewares/logging.go:21.43,27.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:30.80,39.44 7 0
+github.com/muety/wakapi/middlewares/logging.go:45.2,54.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:39.44,40.38 1 0
+github.com/muety/wakapi/middlewares/logging.go:40.38,42.4 1 0
+github.com/muety/wakapi/middlewares/logging.go:57.41,59.14 2 0
+github.com/muety/wakapi/middlewares/logging.go:62.2,62.14 1 0
+github.com/muety/wakapi/middlewares/logging.go:65.2,65.11 1 0
+github.com/muety/wakapi/middlewares/logging.go:59.14,61.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:62.14,64.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:68.41,69.42 1 0
+github.com/muety/wakapi/middlewares/logging.go:72.2,72.12 1 0
+github.com/muety/wakapi/middlewares/logging.go:69.42,71.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:103.52,105.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:117.45,118.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:118.20,122.3 3 0
+github.com/muety/wakapi/middlewares/logging.go:124.54,127.18 3 0
+github.com/muety/wakapi/middlewares/logging.go:134.2,135.15 2 0
+github.com/muety/wakapi/middlewares/logging.go:127.18,130.17 2 0
+github.com/muety/wakapi/middlewares/logging.go:130.17,132.4 1 0
+github.com/muety/wakapi/middlewares/logging.go:137.42,138.20 1 0
+github.com/muety/wakapi/middlewares/logging.go:138.20,140.3 1 0
+github.com/muety/wakapi/middlewares/logging.go:142.36,144.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:145.42,147.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:148.40,150.2 1 0
+github.com/muety/wakapi/middlewares/logging.go:151.52,153.2 1 0
+github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0
+github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0
+github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0
+github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0
+github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0
+github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0
+github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0
+github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0
+github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0
+github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0
+github.com/muety/wakapi/middlewares/security.go:19.62,20.43 1 0
+github.com/muety/wakapi/middlewares/security.go:20.43,22.3 1 0
+github.com/muety/wakapi/middlewares/security.go:25.80,26.36 1 0
+github.com/muety/wakapi/middlewares/security.go:31.2,31.27 1 0
+github.com/muety/wakapi/middlewares/security.go:26.36,27.30 1 0
+github.com/muety/wakapi/middlewares/security.go:27.30,29.4 1 0
+github.com/muety/wakapi/middlewares/sentry.go:15.60,16.43 1 0
+github.com/muety/wakapi/middlewares/sentry.go:16.43,20.3 1 0
+github.com/muety/wakapi/middlewares/sentry.go:23.78,26.54 3 0
+github.com/muety/wakapi/middlewares/sentry.go:26.54,27.43 1 0
+github.com/muety/wakapi/middlewares/sentry.go:27.43,29.4 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:30.91,36.2 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:38.90,41.2 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:43.90,46.2 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:48.71,49.71 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:49.71,51.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:54.107,58.16 3 0
+github.com/muety/wakapi/middlewares/authenticate.go:61.2,61.16 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:65.2,65.31 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:81.2,82.12 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:58.16,60.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:61.16,63.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:65.31,66.31 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:71.3,71.29 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:78.3,78.9 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:66.31,69.4 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:71.29,74.4 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:74.9,77.4 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:85.70,86.39 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:91.2,91.14 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:86.39,87.60 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:87.60,89.4 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:94.98,96.16 2 1
+github.com/muety/wakapi/middlewares/authenticate.go:100.2,103.16 4 1
+github.com/muety/wakapi/middlewares/authenticate.go:106.2,106.18 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:96.16,98.3 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:103.16,105.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:109.97,113.19 4 1
+github.com/muety/wakapi/middlewares/authenticate.go:116.2,117.16 2 1
+github.com/muety/wakapi/middlewares/authenticate.go:120.2,120.18 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:113.19,115.3 1 1
+github.com/muety/wakapi/middlewares/authenticate.go:117.16,119.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:123.92,125.16 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:129.2,130.16 2 0
+github.com/muety/wakapi/middlewares/authenticate.go:137.2,137.18 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:125.16,127.3 1 0
+github.com/muety/wakapi/middlewares/authenticate.go:130.16,132.3 1 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:25.112,32.2 1 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:34.59,38.2 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:50.68,52.16 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:57.2,58.16 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:64.2,65.50 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:70.2,78.16 3 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:84.2,86.44 3 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:52.16,55.3 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:58.16,62.3 3 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:65.50,68.3 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:78.16,82.3 3 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:89.144,91.16 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:95.2,102.29 3 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:106.2,114.16 2 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:118.2,118.36 1 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:91.16,93.3 1 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:102.29,104.3 1 0
+github.com/muety/wakapi/routes/compat/shields/v1/badge.go:114.16,116.3 1 0
+github.com/muety/wakapi/services/user.go:25.99,35.33 3 0
+github.com/muety/wakapi/services/user.go:56.2,56.12 1 0
+github.com/muety/wakapi/services/user.go:35.33,36.31 1 0
+github.com/muety/wakapi/services/user.go:36.31,42.73 4 0
+github.com/muety/wakapi/services/user.go:46.4,46.24 1 0
+github.com/muety/wakapi/services/user.go:42.73,44.5 1 0
+github.com/muety/wakapi/services/user.go:46.24,47.80 1 0
+github.com/muety/wakapi/services/user.go:47.80,49.6 1 0
+github.com/muety/wakapi/services/user.go:49.11,51.6 1 0
+github.com/muety/wakapi/services/user.go:59.74,60.40 1 0
+github.com/muety/wakapi/services/user.go:64.2,65.16 2 0
+github.com/muety/wakapi/services/user.go:69.2,70.15 2 0
+github.com/muety/wakapi/services/user.go:60.40,62.3 1 0
+github.com/muety/wakapi/services/user.go:65.16,67.3 1 0
+github.com/muety/wakapi/services/user.go:73.72,74.37 1 0
+github.com/muety/wakapi/services/user.go:78.2,79.16 2 0
+github.com/muety/wakapi/services/user.go:83.2,84.15 2 0
+github.com/muety/wakapi/services/user.go:74.37,76.3 1 0
+github.com/muety/wakapi/services/user.go:79.16,81.3 1 0
+github.com/muety/wakapi/services/user.go:87.76,89.2 1 0
+github.com/muety/wakapi/services/user.go:91.86,93.2 1 0
+github.com/muety/wakapi/services/user.go:95.58,97.2 1 0
+github.com/muety/wakapi/services/user.go:99.86,101.2 1 0
+github.com/muety/wakapi/services/user.go:103.71,105.12 2 0
+github.com/muety/wakapi/services/user.go:109.2,110.42 2 0
+github.com/muety/wakapi/services/user.go:114.2,115.16 2 0
+github.com/muety/wakapi/services/user.go:119.2,120.21 2 0
+github.com/muety/wakapi/services/user.go:105.12,107.3 1 0
+github.com/muety/wakapi/services/user.go:110.42,112.3 1 0
+github.com/muety/wakapi/services/user.go:115.16,117.3 1 0
+github.com/muety/wakapi/services/user.go:123.48,125.2 1 0
+github.com/muety/wakapi/services/user.go:127.102,137.93 2 0
+github.com/muety/wakapi/services/user.go:143.2,143.38 1 0
+github.com/muety/wakapi/services/user.go:137.93,139.3 1 0
+github.com/muety/wakapi/services/user.go:139.8,141.3 1 0
+github.com/muety/wakapi/services/user.go:146.73,150.2 3 0
+github.com/muety/wakapi/services/user.go:152.78,156.2 3 0
+github.com/muety/wakapi/services/user.go:158.122,161.35 2 0
+github.com/muety/wakapi/services/user.go:167.2,167.35 1 0
+github.com/muety/wakapi/services/user.go:171.2,171.18 1 0
+github.com/muety/wakapi/services/user.go:161.35,162.89 1 0
+github.com/muety/wakapi/services/user.go:162.89,164.4 1 0
+github.com/muety/wakapi/services/user.go:167.35,169.3 1 0
+github.com/muety/wakapi/services/user.go:174.106,177.96 3 0
+github.com/muety/wakapi/services/user.go:182.2,182.68 1 0
+github.com/muety/wakapi/services/user.go:177.96,179.3 1 0
+github.com/muety/wakapi/services/user.go:179.8,181.3 1 0
+github.com/muety/wakapi/services/user.go:185.85,187.2 1 0
+github.com/muety/wakapi/services/user.go:189.57,196.2 4 0
+github.com/muety/wakapi/services/user.go:198.38,200.2 1 0
+github.com/muety/wakapi/services/user.go:202.57,207.2 1 0
+github.com/muety/wakapi/services/aggregation.go:30.142,38.2 1 0
+github.com/muety/wakapi/services/aggregation.go:47.43,49.64 1 0
+github.com/muety/wakapi/services/aggregation.go:53.2,55.19 3 0
+github.com/muety/wakapi/services/aggregation.go:49.64,51.3 1 0
+github.com/muety/wakapi/services/aggregation.go:58.77,59.47 1 0
+github.com/muety/wakapi/services/aggregation.go:62.2,67.40 4 0
+github.com/muety/wakapi/services/aggregation.go:71.2,71.50 1 0
+github.com/muety/wakapi/services/aggregation.go:76.2,76.60 1 0
+github.com/muety/wakapi/services/aggregation.go:82.2,82.35 1 0
+github.com/muety/wakapi/services/aggregation.go:59.47,61.3 1 0
+github.com/muety/wakapi/services/aggregation.go:67.40,69.3 1 0
+github.com/muety/wakapi/services/aggregation.go:71.50,73.3 1 0
+github.com/muety/wakapi/services/aggregation.go:76.60,80.3 3 0
+github.com/muety/wakapi/services/aggregation.go:85.109,86.24 1 0
+github.com/muety/wakapi/services/aggregation.go:86.24,87.116 1 0
+github.com/muety/wakapi/services/aggregation.go:87.116,89.4 1 0
+github.com/muety/wakapi/services/aggregation.go:89.9,92.4 2 0
+github.com/muety/wakapi/services/aggregation.go:96.80,97.33 1 0
+github.com/muety/wakapi/services/aggregation.go:97.33,98.60 1 0
+github.com/muety/wakapi/services/aggregation.go:98.60,100.4 1 0
+github.com/muety/wakapi/services/aggregation.go:104.110,109.16 3 0
+github.com/muety/wakapi/services/aggregation.go:115.2,116.16 2 0
+github.com/muety/wakapi/services/aggregation.go:122.2,123.44 2 0
+github.com/muety/wakapi/services/aggregation.go:128.2,128.41 1 0
+github.com/muety/wakapi/services/aggregation.go:146.2,146.12 1 0
+github.com/muety/wakapi/services/aggregation.go:109.16,112.3 2 0
+github.com/muety/wakapi/services/aggregation.go:116.16,119.3 2 0
+github.com/muety/wakapi/services/aggregation.go:123.44,125.3 1 0
+github.com/muety/wakapi/services/aggregation.go:128.41,129.71 1 0
+github.com/muety/wakapi/services/aggregation.go:133.3,133.21 1 0
+github.com/muety/wakapi/services/aggregation.go:129.71,130.12 1 0
+github.com/muety/wakapi/services/aggregation.go:133.21,137.4 1 0
+github.com/muety/wakapi/services/aggregation.go:137.9,137.62 1 0
+github.com/muety/wakapi/services/aggregation.go:137.62,141.4 1 0
+github.com/muety/wakapi/services/aggregation.go:149.83,152.27 3 0
+github.com/muety/wakapi/services/aggregation.go:157.2,158.12 2 0
+github.com/muety/wakapi/services/aggregation.go:152.27,153.34 1 0
+github.com/muety/wakapi/services/aggregation.go:153.34,155.4 1 0
+github.com/muety/wakapi/services/aggregation.go:161.79,164.27 3 0
+github.com/muety/wakapi/services/aggregation.go:164.27,166.3 1 0
+github.com/muety/wakapi/services/aggregation.go:169.83,184.41 5 0
+github.com/muety/wakapi/services/aggregation.go:184.41,194.3 3 0
+github.com/muety/wakapi/services/aggregation.go:197.34,200.2 2 0
github.com/muety/wakapi/services/alias.go:19.77,24.2 1 1
github.com/muety/wakapi/services/alias.go:28.60,29.43 1 1
github.com/muety/wakapi/services/alias.go:32.2,32.14 1 1
@@ -797,259 +864,35 @@ github.com/muety/wakapi/services/alias.go:178.3,178.30 1 0
github.com/muety/wakapi/services/alias.go:173.47,174.16 1 0
github.com/muety/wakapi/services/alias.go:174.16,176.5 1 0
github.com/muety/wakapi/services/alias.go:179.8,181.3 1 0
-github.com/muety/wakapi/services/duration.go:16.78,22.2 2 1
-github.com/muety/wakapi/services/duration.go:24.123,27.42 2 1
-github.com/muety/wakapi/services/duration.go:33.2,34.16 2 1
-github.com/muety/wakapi/services/duration.go:41.2,46.31 4 1
-github.com/muety/wakapi/services/duration.go:81.2,83.31 2 1
-github.com/muety/wakapi/services/duration.go:96.2,96.49 1 1
-github.com/muety/wakapi/services/duration.go:100.2,100.32 1 1
-github.com/muety/wakapi/services/duration.go:27.42,28.90 1 1
-github.com/muety/wakapi/services/duration.go:28.90,30.4 1 1
-github.com/muety/wakapi/services/duration.go:34.16,36.3 1 0
-github.com/muety/wakapi/services/duration.go:46.31,47.42 1 1
-github.com/muety/wakapi/services/duration.go:51.3,53.62 2 1
-github.com/muety/wakapi/services/duration.go:57.3,57.20 1 1
-github.com/muety/wakapi/services/duration.go:62.3,63.35 2 1
-github.com/muety/wakapi/services/duration.go:66.3,68.72 2 1
-github.com/muety/wakapi/services/duration.go:78.3,78.10 1 1
-github.com/muety/wakapi/services/duration.go:47.42,48.12 1 1
-github.com/muety/wakapi/services/duration.go:53.62,55.4 1 1
-github.com/muety/wakapi/services/duration.go:57.20,59.12 2 1
-github.com/muety/wakapi/services/duration.go:63.35,65.4 1 1
-github.com/muety/wakapi/services/duration.go:68.72,70.41 2 1
-github.com/muety/wakapi/services/duration.go:73.4,73.15 1 1
-github.com/muety/wakapi/services/duration.go:70.41,72.5 1 1
-github.com/muety/wakapi/services/duration.go:74.9,76.4 1 1
-github.com/muety/wakapi/services/duration.go:83.31,84.26 1 1
-github.com/muety/wakapi/services/duration.go:84.26,89.23 1 1
-github.com/muety/wakapi/services/duration.go:92.4,92.36 1 1
-github.com/muety/wakapi/services/duration.go:89.23,91.5 1 1
-github.com/muety/wakapi/services/duration.go:96.49,98.3 1 1
-github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
-github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
-github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
-github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
-github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
-github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
-github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0
-github.com/muety/wakapi/services/misc.go:21.126,28.2 1 0
-github.com/muety/wakapi/services/misc.go:40.50,42.48 1 0
-github.com/muety/wakapi/services/misc.go:46.2,48.19 3 0
-github.com/muety/wakapi/services/misc.go:42.48,44.3 1 0
-github.com/muety/wakapi/services/misc.go:51.51,53.16 2 0
-github.com/muety/wakapi/services/misc.go:57.2,60.26 3 0
-github.com/muety/wakapi/services/misc.go:66.2,68.40 2 0
-github.com/muety/wakapi/services/misc.go:73.2,75.33 3 0
-github.com/muety/wakapi/services/misc.go:79.2,84.17 2 0
-github.com/muety/wakapi/services/misc.go:88.2,91.17 1 0
-github.com/muety/wakapi/services/misc.go:95.2,95.12 1 0
-github.com/muety/wakapi/services/misc.go:53.16,55.3 1 0
-github.com/muety/wakapi/services/misc.go:60.26,65.3 1 0
-github.com/muety/wakapi/services/misc.go:68.40,70.3 1 0
-github.com/muety/wakapi/services/misc.go:75.33,78.3 2 0
-github.com/muety/wakapi/services/misc.go:84.17,86.3 1 0
-github.com/muety/wakapi/services/misc.go:91.17,93.3 1 0
-github.com/muety/wakapi/services/misc.go:98.116,99.24 1 0
-github.com/muety/wakapi/services/misc.go:99.24,100.156 1 0
-github.com/muety/wakapi/services/misc.go:100.156,102.4 1 0
-github.com/muety/wakapi/services/misc.go:102.9,107.4 1 0
-github.com/muety/wakapi/services/project_label.go:21.111,28.2 1 0
-github.com/muety/wakapi/services/project_label.go:30.80,32.2 1 0
-github.com/muety/wakapi/services/project_label.go:34.90,35.51 1 0
-github.com/muety/wakapi/services/project_label.go:39.2,40.16 2 0
-github.com/muety/wakapi/services/project_label.go:43.2,44.20 2 0
-github.com/muety/wakapi/services/project_label.go:35.51,37.3 1 0
-github.com/muety/wakapi/services/project_label.go:40.16,42.3 1 0
-github.com/muety/wakapi/services/project_label.go:48.108,50.16 2 0
-github.com/muety/wakapi/services/project_label.go:53.2,53.112 1 0
-github.com/muety/wakapi/services/project_label.go:56.2,56.26 1 0
-github.com/muety/wakapi/services/project_label.go:50.16,52.3 1 0
-github.com/muety/wakapi/services/project_label.go:53.112,55.3 1 0
-github.com/muety/wakapi/services/project_label.go:60.116,62.16 2 0
-github.com/muety/wakapi/services/project_label.go:65.2,65.112 1 0
-github.com/muety/wakapi/services/project_label.go:68.2,68.26 1 0
-github.com/muety/wakapi/services/project_label.go:62.16,64.3 1 0
-github.com/muety/wakapi/services/project_label.go:65.112,67.3 1 0
-github.com/muety/wakapi/services/project_label.go:71.98,73.16 2 0
-github.com/muety/wakapi/services/project_label.go:77.2,79.20 3 0
-github.com/muety/wakapi/services/project_label.go:73.16,75.3 1 0
-github.com/muety/wakapi/services/project_label.go:82.74,83.24 1 0
-github.com/muety/wakapi/services/project_label.go:86.2,89.12 4 0
-github.com/muety/wakapi/services/project_label.go:83.24,85.3 1 0
-github.com/muety/wakapi/services/project_label.go:92.89,94.14 2 0
-github.com/muety/wakapi/services/project_label.go:97.2,100.4 1 0
-github.com/muety/wakapi/services/project_label.go:94.14,96.3 1 0
-github.com/muety/wakapi/services/summary.go:29.189,41.33 3 1
-github.com/muety/wakapi/services/summary.go:52.2,52.12 1 1
-github.com/muety/wakapi/services/summary.go:41.33,42.31 1 1
-github.com/muety/wakapi/services/summary.go:42.31,44.39 2 0
-github.com/muety/wakapi/services/summary.go:44.39,45.71 1 0
-github.com/muety/wakapi/services/summary.go:45.71,47.6 1 0
-github.com/muety/wakapi/services/summary.go:58.161,61.66 2 1
-github.com/muety/wakapi/services/summary.go:66.2,71.20 4 1
-github.com/muety/wakapi/services/summary.go:77.2,77.65 1 1
-github.com/muety/wakapi/services/summary.go:82.2,83.16 2 1
-github.com/muety/wakapi/services/summary.go:88.2,93.105 5 1
-github.com/muety/wakapi/services/summary.go:97.2,98.30 2 1
-github.com/muety/wakapi/services/summary.go:61.66,63.3 1 0
-github.com/muety/wakapi/services/summary.go:71.20,74.3 2 1
-github.com/muety/wakapi/services/summary.go:77.65,79.3 1 0
-github.com/muety/wakapi/services/summary.go:83.16,85.3 1 0
-github.com/muety/wakapi/services/summary.go:93.105,95.3 1 1
-github.com/muety/wakapi/services/summary.go:101.126,105.41 2 1
-github.com/muety/wakapi/services/summary.go:116.2,117.44 2 1
-github.com/muety/wakapi/services/summary.go:126.2,127.16 2 1
-github.com/muety/wakapi/services/summary.go:131.2,131.30 1 1
-github.com/muety/wakapi/services/summary.go:105.41,108.17 2 1
-github.com/muety/wakapi/services/summary.go:108.17,110.4 1 1
-github.com/muety/wakapi/services/summary.go:110.9,112.4 1 0
-github.com/muety/wakapi/services/summary.go:117.44,118.87 1 1
-github.com/muety/wakapi/services/summary.go:118.87,120.4 1 1
-github.com/muety/wakapi/services/summary.go:120.9,122.4 1 0
-github.com/muety/wakapi/services/summary.go:127.16,129.3 1 0
-github.com/muety/wakapi/services/summary.go:134.127,137.16 2 1
-github.com/muety/wakapi/services/summary.go:141.2,142.74 2 1
-github.com/muety/wakapi/services/summary.go:146.2,148.26 3 1
-github.com/muety/wakapi/services/summary.go:153.2,160.34 7 1
-github.com/muety/wakapi/services/summary.go:178.2,178.25 1 1
-github.com/muety/wakapi/services/summary.go:183.2,196.30 2 1
-github.com/muety/wakapi/services/summary.go:137.16,139.3 1 0
-github.com/muety/wakapi/services/summary.go:142.74,144.3 1 1
-github.com/muety/wakapi/services/summary.go:148.26,150.3 1 1
-github.com/muety/wakapi/services/summary.go:160.34,162.20 2 1
-github.com/muety/wakapi/services/summary.go:163.30,164.29 1 1
-github.com/muety/wakapi/services/summary.go:165.31,166.30 1 1
-github.com/muety/wakapi/services/summary.go:167.29,168.28 1 1
-github.com/muety/wakapi/services/summary.go:169.25,170.24 1 1
-github.com/muety/wakapi/services/summary.go:171.30,172.29 1 1
-github.com/muety/wakapi/services/summary.go:173.29,174.28 1 1
-github.com/muety/wakapi/services/summary.go:178.25,181.3 2 1
-github.com/muety/wakapi/services/summary.go:201.76,203.2 1 0
-github.com/muety/wakapi/services/summary.go:205.62,208.2 2 0
-github.com/muety/wakapi/services/summary.go:210.66,213.2 2 0
-github.com/muety/wakapi/services/summary.go:217.125,220.30 2 1
-github.com/muety/wakapi/services/summary.go:224.2,225.28 2 1
-github.com/muety/wakapi/services/summary.go:233.2,233.40 1 1
-github.com/muety/wakapi/services/summary.go:237.2,237.67 1 1
-github.com/muety/wakapi/services/summary.go:220.30,222.3 1 1
-github.com/muety/wakapi/services/summary.go:225.28,231.3 1 1
-github.com/muety/wakapi/services/summary.go:233.40,235.3 1 1
-github.com/muety/wakapi/services/summary.go:240.87,241.72 1 1
-github.com/muety/wakapi/services/summary.go:249.2,250.16 2 1
-github.com/muety/wakapi/services/summary.go:255.2,256.37 2 1
-github.com/muety/wakapi/services/summary.go:260.2,262.30 3 1
-github.com/muety/wakapi/services/summary.go:273.2,274.29 2 1
-github.com/muety/wakapi/services/summary.go:279.2,280.16 2 1
-github.com/muety/wakapi/services/summary.go:241.72,247.3 1 1
-github.com/muety/wakapi/services/summary.go:250.16,253.3 2 0
-github.com/muety/wakapi/services/summary.go:256.37,258.3 1 1
-github.com/muety/wakapi/services/summary.go:262.30,263.48 1 1
-github.com/muety/wakapi/services/summary.go:263.48,264.41 1 1
-github.com/muety/wakapi/services/summary.go:267.4,268.29 2 1
-github.com/muety/wakapi/services/summary.go:264.41,266.5 1 1
-github.com/muety/wakapi/services/summary.go:274.29,275.18 1 1
-github.com/muety/wakapi/services/summary.go:275.18,277.4 1 1
-github.com/muety/wakapi/services/summary.go:283.97,284.24 1 1
-github.com/muety/wakapi/services/summary.go:288.2,304.30 5 1
-github.com/muety/wakapi/services/summary.go:335.2,338.26 3 1
-github.com/muety/wakapi/services/summary.go:284.24,286.3 1 0
-github.com/muety/wakapi/services/summary.go:304.30,306.41 2 1
-github.com/muety/wakapi/services/summary.go:311.3,311.38 1 1
-github.com/muety/wakapi/services/summary.go:315.3,315.37 1 1
-github.com/muety/wakapi/services/summary.go:319.3,319.34 1 1
-github.com/muety/wakapi/services/summary.go:323.3,332.25 9 1
-github.com/muety/wakapi/services/summary.go:306.41,308.12 2 1
-github.com/muety/wakapi/services/summary.go:311.38,313.4 1 0
-github.com/muety/wakapi/services/summary.go:315.37,317.4 1 1
-github.com/muety/wakapi/services/summary.go:319.34,321.4 1 1
-github.com/muety/wakapi/services/summary.go:341.127,345.32 2 1
-github.com/muety/wakapi/services/summary.go:349.2,349.27 1 1
-github.com/muety/wakapi/services/summary.go:357.2,359.26 3 1
-github.com/muety/wakapi/services/summary.go:364.2,364.43 1 1
-github.com/muety/wakapi/services/summary.go:368.2,368.17 1 1
-github.com/muety/wakapi/services/summary.go:345.32,347.3 1 1
-github.com/muety/wakapi/services/summary.go:349.27,350.37 1 1
-github.com/muety/wakapi/services/summary.go:350.37,352.4 1 1
-github.com/muety/wakapi/services/summary.go:352.9,354.4 1 1
-github.com/muety/wakapi/services/summary.go:359.26,362.3 2 1
-github.com/muety/wakapi/services/summary.go:364.43,366.3 1 1
-github.com/muety/wakapi/services/summary.go:371.116,372.25 1 1
-github.com/muety/wakapi/services/summary.go:376.2,379.44 2 1
-github.com/muety/wakapi/services/summary.go:384.2,384.40 1 1
-github.com/muety/wakapi/services/summary.go:409.2,409.54 1 1
-github.com/muety/wakapi/services/summary.go:413.2,413.18 1 1
-github.com/muety/wakapi/services/summary.go:372.25,374.3 1 0
-github.com/muety/wakapi/services/summary.go:379.44,381.3 1 1
-github.com/muety/wakapi/services/summary.go:384.40,386.19 2 1
-github.com/muety/wakapi/services/summary.go:393.3,398.34 3 1
-github.com/muety/wakapi/services/summary.go:403.3,403.22 1 1
-github.com/muety/wakapi/services/summary.go:386.19,387.12 1 1
-github.com/muety/wakapi/services/summary.go:398.34,400.4 1 1
-github.com/muety/wakapi/services/summary.go:403.22,405.4 1 1
-github.com/muety/wakapi/services/summary.go:409.54,411.3 1 1
-github.com/muety/wakapi/services/summary.go:416.59,418.2 1 1
-github.com/muety/wakapi/services/summary.go:420.63,421.37 1 0
-github.com/muety/wakapi/services/summary.go:421.37,422.36 1 0
-github.com/muety/wakapi/services/summary.go:422.36,424.4 1 0
-github.com/muety/wakapi/services/summary.go:428.85,429.40 1 1
-github.com/muety/wakapi/services/summary.go:429.40,432.3 2 1
-github.com/muety/wakapi/services/summary.go:435.99,436.42 1 1
-github.com/muety/wakapi/services/summary.go:436.42,438.17 2 1
-github.com/muety/wakapi/services/summary.go:441.3,442.29 2 1
-github.com/muety/wakapi/services/summary.go:445.3,445.22 1 1
-github.com/muety/wakapi/services/summary.go:438.17,440.4 1 0
-github.com/muety/wakapi/services/summary.go:442.29,444.4 1 1
-github.com/muety/wakapi/services/summary.go:449.114,450.33 1 1
-github.com/muety/wakapi/services/summary.go:450.33,453.17 3 1
-github.com/muety/wakapi/services/summary.go:458.3,459.28 2 1
-github.com/muety/wakapi/services/summary.go:462.3,462.24 1 1
-github.com/muety/wakapi/services/summary.go:453.17,454.33 1 1
-github.com/muety/wakapi/services/summary.go:454.33,456.5 1 1
-github.com/muety/wakapi/services/summary.go:459.28,461.4 1 1
-github.com/muety/wakapi/services/aggregation.go:30.142,38.2 1 0
-github.com/muety/wakapi/services/aggregation.go:47.43,49.64 1 0
-github.com/muety/wakapi/services/aggregation.go:53.2,55.19 3 0
-github.com/muety/wakapi/services/aggregation.go:49.64,51.3 1 0
-github.com/muety/wakapi/services/aggregation.go:58.77,59.47 1 0
-github.com/muety/wakapi/services/aggregation.go:62.2,67.40 4 0
-github.com/muety/wakapi/services/aggregation.go:71.2,71.50 1 0
-github.com/muety/wakapi/services/aggregation.go:76.2,76.60 1 0
-github.com/muety/wakapi/services/aggregation.go:82.2,82.35 1 0
-github.com/muety/wakapi/services/aggregation.go:59.47,61.3 1 0
-github.com/muety/wakapi/services/aggregation.go:67.40,69.3 1 0
-github.com/muety/wakapi/services/aggregation.go:71.50,73.3 1 0
-github.com/muety/wakapi/services/aggregation.go:76.60,80.3 3 0
-github.com/muety/wakapi/services/aggregation.go:85.109,86.24 1 0
-github.com/muety/wakapi/services/aggregation.go:86.24,87.116 1 0
-github.com/muety/wakapi/services/aggregation.go:87.116,89.4 1 0
-github.com/muety/wakapi/services/aggregation.go:89.9,92.4 2 0
-github.com/muety/wakapi/services/aggregation.go:96.80,97.33 1 0
-github.com/muety/wakapi/services/aggregation.go:97.33,98.60 1 0
-github.com/muety/wakapi/services/aggregation.go:98.60,100.4 1 0
-github.com/muety/wakapi/services/aggregation.go:104.110,109.16 3 0
-github.com/muety/wakapi/services/aggregation.go:115.2,116.16 2 0
-github.com/muety/wakapi/services/aggregation.go:122.2,123.44 2 0
-github.com/muety/wakapi/services/aggregation.go:128.2,128.41 1 0
-github.com/muety/wakapi/services/aggregation.go:146.2,146.12 1 0
-github.com/muety/wakapi/services/aggregation.go:109.16,112.3 2 0
-github.com/muety/wakapi/services/aggregation.go:116.16,119.3 2 0
-github.com/muety/wakapi/services/aggregation.go:123.44,125.3 1 0
-github.com/muety/wakapi/services/aggregation.go:128.41,129.71 1 0
-github.com/muety/wakapi/services/aggregation.go:133.3,133.21 1 0
-github.com/muety/wakapi/services/aggregation.go:129.71,130.12 1 0
-github.com/muety/wakapi/services/aggregation.go:133.21,137.4 1 0
-github.com/muety/wakapi/services/aggregation.go:137.9,137.62 1 0
-github.com/muety/wakapi/services/aggregation.go:137.62,141.4 1 0
-github.com/muety/wakapi/services/aggregation.go:149.83,152.27 3 0
-github.com/muety/wakapi/services/aggregation.go:157.2,158.12 2 0
-github.com/muety/wakapi/services/aggregation.go:152.27,153.34 1 0
-github.com/muety/wakapi/services/aggregation.go:153.34,155.4 1 0
-github.com/muety/wakapi/services/aggregation.go:161.79,164.27 3 0
-github.com/muety/wakapi/services/aggregation.go:164.27,166.3 1 0
-github.com/muety/wakapi/services/aggregation.go:169.83,184.41 5 0
-github.com/muety/wakapi/services/aggregation.go:184.41,194.3 3 0
-github.com/muety/wakapi/services/aggregation.go:197.34,200.2 2 0
+github.com/muety/wakapi/services/duration.go:17.78,23.2 2 1
+github.com/muety/wakapi/services/duration.go:25.123,28.42 2 1
+github.com/muety/wakapi/services/duration.go:34.2,35.16 2 1
+github.com/muety/wakapi/services/duration.go:42.2,47.31 4 1
+github.com/muety/wakapi/services/duration.go:95.2,97.31 2 1
+github.com/muety/wakapi/services/duration.go:110.2,110.49 1 1
+github.com/muety/wakapi/services/duration.go:114.2,114.32 1 1
+github.com/muety/wakapi/services/duration.go:28.42,29.90 1 1
+github.com/muety/wakapi/services/duration.go:29.90,31.4 1 1
+github.com/muety/wakapi/services/duration.go:35.16,37.3 1 0
+github.com/muety/wakapi/services/duration.go:47.31,48.42 1 1
+github.com/muety/wakapi/services/duration.go:52.3,54.62 2 1
+github.com/muety/wakapi/services/duration.go:58.3,58.20 1 1
+github.com/muety/wakapi/services/duration.go:63.3,73.15 3 1
+github.com/muety/wakapi/services/duration.go:76.3,82.84 2 1
+github.com/muety/wakapi/services/duration.go:92.3,92.10 1 1
+github.com/muety/wakapi/services/duration.go:48.42,49.12 1 1
+github.com/muety/wakapi/services/duration.go:54.62,56.4 1 1
+github.com/muety/wakapi/services/duration.go:58.20,60.12 2 1
+github.com/muety/wakapi/services/duration.go:73.15,75.4 1 0
+github.com/muety/wakapi/services/duration.go:82.84,84.41 2 1
+github.com/muety/wakapi/services/duration.go:87.4,87.15 1 1
+github.com/muety/wakapi/services/duration.go:84.41,86.5 1 1
+github.com/muety/wakapi/services/duration.go:88.9,90.4 1 1
+github.com/muety/wakapi/services/duration.go:97.31,98.26 1 1
+github.com/muety/wakapi/services/duration.go:98.26,103.23 1 1
+github.com/muety/wakapi/services/duration.go:106.4,106.36 1 1
+github.com/muety/wakapi/services/duration.go:103.23,105.5 1 1
+github.com/muety/wakapi/services/duration.go:110.49,112.3 1 1
github.com/muety/wakapi/services/heartbeat.go:26.141,40.33 3 0
github.com/muety/wakapi/services/heartbeat.go:48.2,48.12 1 0
github.com/muety/wakapi/services/heartbeat.go:40.33,41.31 1 0
@@ -1119,6 +962,41 @@ github.com/muety/wakapi/services/heartbeat.go:255.94,257.42 2 0
github.com/muety/wakapi/services/heartbeat.go:263.2,263.18 1 0
github.com/muety/wakapi/services/heartbeat.go:257.42,259.18 2 0
github.com/muety/wakapi/services/heartbeat.go:259.18,261.4 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/project_label.go:21.111,28.2 1 0
+github.com/muety/wakapi/services/project_label.go:30.80,32.2 1 0
+github.com/muety/wakapi/services/project_label.go:34.90,35.51 1 0
+github.com/muety/wakapi/services/project_label.go:39.2,40.16 2 0
+github.com/muety/wakapi/services/project_label.go:43.2,44.20 2 0
+github.com/muety/wakapi/services/project_label.go:35.51,37.3 1 0
+github.com/muety/wakapi/services/project_label.go:40.16,42.3 1 0
+github.com/muety/wakapi/services/project_label.go:48.108,50.16 2 0
+github.com/muety/wakapi/services/project_label.go:53.2,53.112 1 0
+github.com/muety/wakapi/services/project_label.go:56.2,56.26 1 0
+github.com/muety/wakapi/services/project_label.go:50.16,52.3 1 0
+github.com/muety/wakapi/services/project_label.go:53.112,55.3 1 0
+github.com/muety/wakapi/services/project_label.go:60.116,62.16 2 0
+github.com/muety/wakapi/services/project_label.go:65.2,65.112 1 0
+github.com/muety/wakapi/services/project_label.go:68.2,68.26 1 0
+github.com/muety/wakapi/services/project_label.go:62.16,64.3 1 0
+github.com/muety/wakapi/services/project_label.go:65.112,67.3 1 0
+github.com/muety/wakapi/services/project_label.go:71.98,73.16 2 0
+github.com/muety/wakapi/services/project_label.go:77.2,79.20 3 0
+github.com/muety/wakapi/services/project_label.go:73.16,75.3 1 0
+github.com/muety/wakapi/services/project_label.go:82.74,83.24 1 0
+github.com/muety/wakapi/services/project_label.go:86.2,89.12 4 0
+github.com/muety/wakapi/services/project_label.go:83.24,85.3 1 0
+github.com/muety/wakapi/services/project_label.go:92.89,94.14 2 0
+github.com/muety/wakapi/services/project_label.go:97.2,100.4 1 0
+github.com/muety/wakapi/services/project_label.go:94.14,96.3 1 0
+github.com/muety/wakapi/services/diagnostics.go:14.101,19.2 1 0
+github.com/muety/wakapi/services/diagnostics.go:21.101,23.2 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
@@ -1138,6 +1016,27 @@ 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:21.126,28.2 1 0
+github.com/muety/wakapi/services/misc.go:40.50,42.48 1 0
+github.com/muety/wakapi/services/misc.go:46.2,48.19 3 0
+github.com/muety/wakapi/services/misc.go:42.48,44.3 1 0
+github.com/muety/wakapi/services/misc.go:51.51,53.16 2 0
+github.com/muety/wakapi/services/misc.go:57.2,60.26 3 0
+github.com/muety/wakapi/services/misc.go:66.2,68.40 2 0
+github.com/muety/wakapi/services/misc.go:73.2,75.33 3 0
+github.com/muety/wakapi/services/misc.go:79.2,84.17 2 0
+github.com/muety/wakapi/services/misc.go:88.2,91.17 1 0
+github.com/muety/wakapi/services/misc.go:95.2,95.12 1 0
+github.com/muety/wakapi/services/misc.go:53.16,55.3 1 0
+github.com/muety/wakapi/services/misc.go:60.26,65.3 1 0
+github.com/muety/wakapi/services/misc.go:68.40,70.3 1 0
+github.com/muety/wakapi/services/misc.go:75.33,78.3 2 0
+github.com/muety/wakapi/services/misc.go:84.17,86.3 1 0
+github.com/muety/wakapi/services/misc.go:91.17,93.3 1 0
+github.com/muety/wakapi/services/misc.go:98.116,99.24 1 0
+github.com/muety/wakapi/services/misc.go:99.24,100.156 1 0
+github.com/muety/wakapi/services/misc.go:100.156,102.4 1 0
+github.com/muety/wakapi/services/misc.go:102.9,107.4 1 0
github.com/muety/wakapi/services/report.go:30.122,44.33 4 0
github.com/muety/wakapi/services/report.go:50.2,50.12 1 0
github.com/muety/wakapi/services/report.go:44.33,45.31 1 0
@@ -1167,56 +1066,133 @@ github.com/muety/wakapi/services/report.go:145.2,145.12 1 0
github.com/muety/wakapi/services/report.go:138.41,139.30 1 0
github.com/muety/wakapi/services/report.go:139.30,140.16 1 0
github.com/muety/wakapi/services/report.go:140.16,142.5 1 0
-github.com/muety/wakapi/services/user.go:24.99,34.33 3 0
-github.com/muety/wakapi/services/user.go:55.2,55.12 1 0
-github.com/muety/wakapi/services/user.go:34.33,35.31 1 0
-github.com/muety/wakapi/services/user.go:35.31,41.73 4 0
-github.com/muety/wakapi/services/user.go:45.4,45.24 1 0
-github.com/muety/wakapi/services/user.go:41.73,43.5 1 0
-github.com/muety/wakapi/services/user.go:45.24,46.80 1 0
-github.com/muety/wakapi/services/user.go:46.80,48.6 1 0
-github.com/muety/wakapi/services/user.go:48.11,50.6 1 0
-github.com/muety/wakapi/services/user.go:58.74,59.40 1 0
-github.com/muety/wakapi/services/user.go:63.2,64.16 2 0
-github.com/muety/wakapi/services/user.go:68.2,69.15 2 0
-github.com/muety/wakapi/services/user.go:59.40,61.3 1 0
-github.com/muety/wakapi/services/user.go:64.16,66.3 1 0
-github.com/muety/wakapi/services/user.go:72.72,73.37 1 0
-github.com/muety/wakapi/services/user.go:77.2,78.16 2 0
-github.com/muety/wakapi/services/user.go:82.2,83.15 2 0
-github.com/muety/wakapi/services/user.go:73.37,75.3 1 0
-github.com/muety/wakapi/services/user.go:78.16,80.3 1 0
-github.com/muety/wakapi/services/user.go:86.76,88.2 1 0
-github.com/muety/wakapi/services/user.go:90.86,92.2 1 0
-github.com/muety/wakapi/services/user.go:94.58,96.2 1 0
-github.com/muety/wakapi/services/user.go:98.86,100.2 1 0
-github.com/muety/wakapi/services/user.go:102.71,104.12 2 0
-github.com/muety/wakapi/services/user.go:108.2,109.42 2 0
-github.com/muety/wakapi/services/user.go:113.2,114.16 2 0
-github.com/muety/wakapi/services/user.go:118.2,119.21 2 0
-github.com/muety/wakapi/services/user.go:104.12,106.3 1 0
-github.com/muety/wakapi/services/user.go:109.42,111.3 1 0
-github.com/muety/wakapi/services/user.go:114.16,116.3 1 0
-github.com/muety/wakapi/services/user.go:122.48,124.2 1 0
-github.com/muety/wakapi/services/user.go:126.102,136.93 2 0
-github.com/muety/wakapi/services/user.go:142.2,142.38 1 0
-github.com/muety/wakapi/services/user.go:136.93,138.3 1 0
-github.com/muety/wakapi/services/user.go:138.8,140.3 1 0
-github.com/muety/wakapi/services/user.go:145.73,149.2 3 0
-github.com/muety/wakapi/services/user.go:151.78,155.2 3 0
-github.com/muety/wakapi/services/user.go:157.122,160.35 2 0
-github.com/muety/wakapi/services/user.go:166.2,166.35 1 0
-github.com/muety/wakapi/services/user.go:170.2,170.18 1 0
-github.com/muety/wakapi/services/user.go:160.35,161.89 1 0
-github.com/muety/wakapi/services/user.go:161.89,163.4 1 0
-github.com/muety/wakapi/services/user.go:166.35,168.3 1 0
-github.com/muety/wakapi/services/user.go:173.106,176.96 3 0
-github.com/muety/wakapi/services/user.go:181.2,181.68 1 0
-github.com/muety/wakapi/services/user.go:176.96,178.3 1 0
-github.com/muety/wakapi/services/user.go:178.8,180.3 1 0
-github.com/muety/wakapi/services/user.go:184.85,186.2 1 0
-github.com/muety/wakapi/services/user.go:188.57,195.2 4 0
-github.com/muety/wakapi/services/user.go:197.38,199.2 1 0
-github.com/muety/wakapi/services/user.go:201.57,206.2 1 0
-github.com/muety/wakapi/services/diagnostics.go:14.101,19.2 1 0
-github.com/muety/wakapi/services/diagnostics.go:21.101,23.2 1 0
+github.com/muety/wakapi/services/summary.go:30.189,42.33 3 1
+github.com/muety/wakapi/services/summary.go:53.2,53.12 1 1
+github.com/muety/wakapi/services/summary.go:42.33,43.31 1 1
+github.com/muety/wakapi/services/summary.go:43.31,45.39 2 0
+github.com/muety/wakapi/services/summary.go:45.39,46.71 1 0
+github.com/muety/wakapi/services/summary.go:46.71,48.6 1 0
+github.com/muety/wakapi/services/summary.go:59.161,62.66 2 1
+github.com/muety/wakapi/services/summary.go:67.2,72.20 4 1
+github.com/muety/wakapi/services/summary.go:78.2,78.65 1 1
+github.com/muety/wakapi/services/summary.go:83.2,84.16 2 1
+github.com/muety/wakapi/services/summary.go:89.2,94.105 5 1
+github.com/muety/wakapi/services/summary.go:98.2,99.30 2 1
+github.com/muety/wakapi/services/summary.go:62.66,64.3 1 0
+github.com/muety/wakapi/services/summary.go:72.20,75.3 2 1
+github.com/muety/wakapi/services/summary.go:78.65,80.3 1 0
+github.com/muety/wakapi/services/summary.go:84.16,86.3 1 0
+github.com/muety/wakapi/services/summary.go:94.105,96.3 1 1
+github.com/muety/wakapi/services/summary.go:102.126,106.41 2 1
+github.com/muety/wakapi/services/summary.go:117.2,118.44 2 1
+github.com/muety/wakapi/services/summary.go:133.2,134.16 2 1
+github.com/muety/wakapi/services/summary.go:138.2,138.30 1 1
+github.com/muety/wakapi/services/summary.go:106.41,109.17 2 1
+github.com/muety/wakapi/services/summary.go:109.17,111.4 1 1
+github.com/muety/wakapi/services/summary.go:111.9,113.4 1 0
+github.com/muety/wakapi/services/summary.go:118.44,119.87 1 1
+github.com/muety/wakapi/services/summary.go:119.87,120.71 1 1
+github.com/muety/wakapi/services/summary.go:126.4,126.36 1 1
+github.com/muety/wakapi/services/summary.go:120.71,124.13 1 0
+github.com/muety/wakapi/services/summary.go:127.9,129.4 1 0
+github.com/muety/wakapi/services/summary.go:134.16,136.3 1 0
+github.com/muety/wakapi/services/summary.go:141.127,144.16 2 1
+github.com/muety/wakapi/services/summary.go:148.2,149.74 2 1
+github.com/muety/wakapi/services/summary.go:153.2,155.26 3 1
+github.com/muety/wakapi/services/summary.go:160.2,167.34 7 1
+github.com/muety/wakapi/services/summary.go:185.2,185.25 1 1
+github.com/muety/wakapi/services/summary.go:190.2,203.30 2 1
+github.com/muety/wakapi/services/summary.go:144.16,146.3 1 0
+github.com/muety/wakapi/services/summary.go:149.74,151.3 1 1
+github.com/muety/wakapi/services/summary.go:155.26,157.3 1 1
+github.com/muety/wakapi/services/summary.go:167.34,169.20 2 1
+github.com/muety/wakapi/services/summary.go:170.30,171.29 1 1
+github.com/muety/wakapi/services/summary.go:172.31,173.30 1 1
+github.com/muety/wakapi/services/summary.go:174.29,175.28 1 1
+github.com/muety/wakapi/services/summary.go:176.25,177.24 1 1
+github.com/muety/wakapi/services/summary.go:178.30,179.29 1 1
+github.com/muety/wakapi/services/summary.go:180.29,181.28 1 1
+github.com/muety/wakapi/services/summary.go:185.25,188.3 2 1
+github.com/muety/wakapi/services/summary.go:208.76,210.2 1 0
+github.com/muety/wakapi/services/summary.go:212.62,215.2 2 0
+github.com/muety/wakapi/services/summary.go:217.66,220.2 2 0
+github.com/muety/wakapi/services/summary.go:224.125,227.30 2 1
+github.com/muety/wakapi/services/summary.go:231.2,232.28 2 1
+github.com/muety/wakapi/services/summary.go:240.2,240.40 1 1
+github.com/muety/wakapi/services/summary.go:244.2,244.67 1 1
+github.com/muety/wakapi/services/summary.go:227.30,229.3 1 1
+github.com/muety/wakapi/services/summary.go:232.28,238.3 1 1
+github.com/muety/wakapi/services/summary.go:240.40,242.3 1 1
+github.com/muety/wakapi/services/summary.go:247.87,248.72 1 1
+github.com/muety/wakapi/services/summary.go:256.2,257.16 2 1
+github.com/muety/wakapi/services/summary.go:262.2,263.37 2 1
+github.com/muety/wakapi/services/summary.go:267.2,269.30 3 1
+github.com/muety/wakapi/services/summary.go:280.2,281.29 2 1
+github.com/muety/wakapi/services/summary.go:286.2,287.16 2 1
+github.com/muety/wakapi/services/summary.go:248.72,254.3 1 1
+github.com/muety/wakapi/services/summary.go:257.16,260.3 2 0
+github.com/muety/wakapi/services/summary.go:263.37,265.3 1 1
+github.com/muety/wakapi/services/summary.go:269.30,270.48 1 1
+github.com/muety/wakapi/services/summary.go:270.48,271.41 1 1
+github.com/muety/wakapi/services/summary.go:274.4,275.29 2 1
+github.com/muety/wakapi/services/summary.go:271.41,273.5 1 1
+github.com/muety/wakapi/services/summary.go:281.29,282.18 1 1
+github.com/muety/wakapi/services/summary.go:282.18,284.4 1 1
+github.com/muety/wakapi/services/summary.go:290.97,291.24 1 1
+github.com/muety/wakapi/services/summary.go:295.2,311.30 5 1
+github.com/muety/wakapi/services/summary.go:342.2,345.26 3 1
+github.com/muety/wakapi/services/summary.go:291.24,293.3 1 0
+github.com/muety/wakapi/services/summary.go:311.30,313.41 2 1
+github.com/muety/wakapi/services/summary.go:318.3,318.38 1 1
+github.com/muety/wakapi/services/summary.go:322.3,322.37 1 1
+github.com/muety/wakapi/services/summary.go:326.3,326.34 1 1
+github.com/muety/wakapi/services/summary.go:330.3,339.25 9 1
+github.com/muety/wakapi/services/summary.go:313.41,315.12 2 1
+github.com/muety/wakapi/services/summary.go:318.38,320.4 1 0
+github.com/muety/wakapi/services/summary.go:322.37,324.4 1 1
+github.com/muety/wakapi/services/summary.go:326.34,328.4 1 1
+github.com/muety/wakapi/services/summary.go:348.127,352.32 2 1
+github.com/muety/wakapi/services/summary.go:356.2,356.27 1 1
+github.com/muety/wakapi/services/summary.go:364.2,366.26 3 1
+github.com/muety/wakapi/services/summary.go:371.2,371.43 1 1
+github.com/muety/wakapi/services/summary.go:375.2,375.17 1 1
+github.com/muety/wakapi/services/summary.go:352.32,354.3 1 1
+github.com/muety/wakapi/services/summary.go:356.27,357.37 1 1
+github.com/muety/wakapi/services/summary.go:357.37,359.4 1 1
+github.com/muety/wakapi/services/summary.go:359.9,361.4 1 1
+github.com/muety/wakapi/services/summary.go:366.26,369.3 2 1
+github.com/muety/wakapi/services/summary.go:371.43,373.3 1 1
+github.com/muety/wakapi/services/summary.go:378.130,379.25 1 1
+github.com/muety/wakapi/services/summary.go:383.2,386.44 2 1
+github.com/muety/wakapi/services/summary.go:391.2,391.40 1 1
+github.com/muety/wakapi/services/summary.go:422.2,422.54 1 1
+github.com/muety/wakapi/services/summary.go:426.2,426.18 1 1
+github.com/muety/wakapi/services/summary.go:379.25,381.3 1 0
+github.com/muety/wakapi/services/summary.go:386.44,388.3 1 1
+github.com/muety/wakapi/services/summary.go:391.40,393.51 2 1
+github.com/muety/wakapi/services/summary.go:397.3,404.15 3 1
+github.com/muety/wakapi/services/summary.go:416.3,416.22 1 1
+github.com/muety/wakapi/services/summary.go:393.51,394.12 1 1
+github.com/muety/wakapi/services/summary.go:404.15,410.38 3 1
+github.com/muety/wakapi/services/summary.go:410.38,412.5 1 0
+github.com/muety/wakapi/services/summary.go:416.22,418.4 1 1
+github.com/muety/wakapi/services/summary.go:422.54,424.3 1 1
+github.com/muety/wakapi/services/summary.go:429.59,431.2 1 1
+github.com/muety/wakapi/services/summary.go:433.63,434.37 1 0
+github.com/muety/wakapi/services/summary.go:434.37,435.36 1 0
+github.com/muety/wakapi/services/summary.go:435.36,437.4 1 0
+github.com/muety/wakapi/services/summary.go:441.85,442.40 1 1
+github.com/muety/wakapi/services/summary.go:442.40,445.3 2 1
+github.com/muety/wakapi/services/summary.go:448.99,449.42 1 1
+github.com/muety/wakapi/services/summary.go:449.42,451.17 2 1
+github.com/muety/wakapi/services/summary.go:454.3,455.29 2 1
+github.com/muety/wakapi/services/summary.go:458.3,458.22 1 1
+github.com/muety/wakapi/services/summary.go:451.17,453.4 1 0
+github.com/muety/wakapi/services/summary.go:455.29,457.4 1 1
+github.com/muety/wakapi/services/summary.go:462.114,463.33 1 1
+github.com/muety/wakapi/services/summary.go:463.33,466.17 3 1
+github.com/muety/wakapi/services/summary.go:471.3,472.28 2 1
+github.com/muety/wakapi/services/summary.go:475.3,475.24 1 1
+github.com/muety/wakapi/services/summary.go:466.17,467.33 1 1
+github.com/muety/wakapi/services/summary.go:467.33,469.5 1 1
+github.com/muety/wakapi/services/summary.go:472.28,474.4 1 1
diff --git a/go.mod b/go.mod
index 286aa9b..2b4e343 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
require (
codeberg.org/Codeberg/avatars v0.0.0-20211228163022-8da63012fe69
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
- github.com/duke-git/lancet/v2 v2.0.1
+ github.com/duke-git/lancet/v2 v2.0.4
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac
github.com/emersion/go-smtp v0.15.0
github.com/emvi/logbuch v1.2.0
@@ -20,21 +20,22 @@ require (
github.com/leandro-lugaresi/hub v1.1.1
github.com/lpar/gzipped/v2 v2.0.2
github.com/mitchellh/hashstructure/v2 v2.0.2
+ github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.7.0
github.com/swaggo/swag v1.7.0
go.uber.org/atomic v1.9.0
- golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
+ golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- gorm.io/driver/mysql v1.3.2
- gorm.io/driver/postgres v1.3.1
+ gorm.io/driver/mysql v1.3.3
+ gorm.io/driver/postgres v1.3.4
gorm.io/driver/sqlite v1.3.1
- gorm.io/gorm v1.23.4-0.20220320010245-2d5cb997ed4d
+ gorm.io/gorm v1.23.4
)
require (
- github.com/BurntSushi/toml v1.0.0 // indirect
+ github.com/BurntSushi/toml v1.1.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -45,6 +46,7 @@ require (
github.com/go-openapi/spec v0.20.2 // indirect
github.com/go-openapi/swag v0.19.13 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.11.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
@@ -62,9 +64,10 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/stretchr/objx v0.2.0 // indirect
+ golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
- golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
+ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index ffcf0b2..d962efb 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
codeberg.org/Codeberg/avatars v0.0.0-20211228163022-8da63012fe69 h1:/XvI42KX57UTpeIOIt7IfM+pmEFTL8FGtiIUGcGDOIU=
codeberg.org/Codeberg/avatars v0.0.0-20211228163022-8da63012fe69/go.mod h1:ML/htpPRb3+owhkm4+qG2ZrXnk5WXaQLASOZ5GLCPi8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
-github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
@@ -23,8 +23,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/duke-git/lancet/v2 v2.0.1 h1:OKNGIw22Yhxeq9rjK5241nfO79zTtYiiC3YERzvLqvA=
-github.com/duke-git/lancet/v2 v2.0.1/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA=
+github.com/duke-git/lancet/v2 v2.0.4 h1:IvMurTpL0cGhQmGPtkCge2eCkuiu3USQtglZJnKXxEo=
+github.com/duke-git/lancet/v2 v2.0.4/go.mod h1:5Nawyf/bK783rCiHyVkZLx+jj8028oVVjLOrC21ZONA=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
@@ -61,6 +61,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -83,7 +85,6 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
@@ -109,20 +110,17 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
@@ -169,6 +167,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
+github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e h1:bR8DQ4ZfItytLJwRlrLOPUHd5z18V6tECwYQFy8W+8g=
+github.com/narqo/go-badge v0.0.0-20220127184443-140af28a266e/go.mod h1:m9BzkaxwU4IfPQi9ko23cmuFltayFe8iS0dlRlnEWiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@@ -232,8 +232,10 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
-golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -268,8 +270,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -313,13 +315,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
-gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
-gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
-gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
+gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8=
+gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
+gorm.io/driver/postgres v1.3.4 h1:evZ7plF+Bp+Lr1mO5NdPvd6M/N98XtwHixGB+y7fdEQ=
+gorm.io/driver/postgres v1.3.4/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw=
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
-gorm.io/gorm v1.23.4-0.20220320010245-2d5cb997ed4d h1:jDg/QfjT3PwXwVbUdArbL4dlayfD/zE1tC04Zx+BAcI=
-gorm.io/gorm v1.23.4-0.20220320010245-2d5cb997ed4d/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gorm.io/gorm v1.23.4 h1:1BKWM67O6CflSLcwGQR7ccfmC4ebOxQrTfOQGRE9wjg=
+gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/main.go b/main.go
index 119ee6e..804e015 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,7 @@ package main
import (
"embed"
+ "github.com/muety/wakapi/migrations"
"io/fs"
"log"
"net"
@@ -16,7 +17,6 @@ import (
"github.com/emvi/logbuch"
"github.com/gorilla/handlers"
conf "github.com/muety/wakapi/config"
- "github.com/muety/wakapi/migrations"
"github.com/muety/wakapi/repositories"
"github.com/muety/wakapi/routes/api"
"github.com/muety/wakapi/services/mail"
@@ -138,7 +138,9 @@ func main() {
defer sqlDb.Close()
// Migrate database schema
- migrations.Run(db, config)
+ if !config.SkipMigrations {
+ migrations.Run(db, config)
+ }
// Repositories
aliasRepository = repositories.NewAliasRepository(db)
@@ -167,11 +169,9 @@ func main() {
miscService = services.NewMiscService(userService, summaryService, keyValueService)
// Schedule background tasks
- if !config.QuickStart {
- go aggregationService.Schedule()
- go miscService.ScheduleCountTotalTime()
- go reportService.Schedule()
- }
+ go aggregationService.Schedule()
+ go miscService.ScheduleCountTotalTime()
+ go reportService.Schedule()
routes.Init()
@@ -182,6 +182,7 @@ func main() {
metricsHandler := api.NewMetricsHandler(userService, summaryService, heartbeatService, keyValueService, metricsRepository)
diagnosticsHandler := api.NewDiagnosticsApiHandler(userService, diagnosticsService)
avatarHandler := api.NewAvatarHandler()
+ badgeHandler := api.NewBadgeHandler(userService, summaryService)
// Compat Handlers
wakatimeV1StatusBarHandler := wtV1Routes.NewStatusBarHandler(userService, summaryService)
@@ -240,6 +241,7 @@ func main() {
metricsHandler.RegisterRoutes(apiRouter)
diagnosticsHandler.RegisterRoutes(apiRouter)
avatarHandler.RegisterRoutes(apiRouter)
+ badgeHandler.RegisterRoutes(apiRouter)
wakatimeV1StatusBarHandler.RegisterRoutes(apiRouter)
wakatimeV1AllHandler.RegisterRoutes(apiRouter)
wakatimeV1SummariesHandler.RegisterRoutes(apiRouter)
diff --git a/migrations/20220319_add_user_project_idx.go b/migrations/20220319_add_user_project_idx.go
deleted file mode 100644
index ab38d0e..0000000
--- a/migrations/20220319_add_user_project_idx.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package migrations
-
-import (
- "fmt"
- "github.com/emvi/logbuch"
- "github.com/muety/wakapi/config"
- "github.com/muety/wakapi/models"
- "gorm.io/gorm"
-)
-
-func init() {
- const name = "20220319-add_user_project_idx"
- f := migrationFunc{
- name: name,
- f: func(db *gorm.DB, cfg *config.Config) error {
- if hasRun(name, db) {
- return nil
- }
-
- idxName := "idx_user_project"
-
- if !db.Migrator().HasIndex(&models.Heartbeat{}, idxName) {
- logbuch.Info("running migration '%s'", name)
- if err := db.Exec(fmt.Sprintf("create index %s on heartbeats (user_id, project)", idxName)).Error; err != nil {
- logbuch.Warn("failed to create %s", idxName)
- }
- }
-
- setHasRun(name, db)
- return nil
- },
- }
-
- registerPostMigration(f)
-}
diff --git a/migrations/20220403_drop_user_project_idx.go b/migrations/20220403_drop_user_project_idx.go
new file mode 100644
index 0000000..c6a2aa3
--- /dev/null
+++ b/migrations/20220403_drop_user_project_idx.go
@@ -0,0 +1,40 @@
+package migrations
+
+import (
+ "github.com/emvi/logbuch"
+ "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/models"
+ "gorm.io/gorm"
+)
+
+// migration to fix https://github.com/muety/wakapi/issues/346
+// caused by https://github.com/muety/wakapi/blob/2.3.2/migrations/20220319_add_user_project_idx.go in combination with
+// the wrongly defined index at https://github.com/muety/wakapi/blob/5aae18e2415d9e620f383f98cd8cbdf39cd99f27/models/heartbeat.go#L18
+// and https://github.com/go-gorm/sqlite/issues/87
+// -> drop index and let it be auto-created again with properly formatted ddl
+
+func init() {
+ const name = "20220403-drop_user_project_idx"
+ const idxName = "idx_user_project"
+
+ f := migrationFunc{
+ name: name,
+ f: func(db *gorm.DB, cfg *config.Config) error {
+ if !db.Migrator().HasTable(&models.KeyStringValue{}) || hasRun(name, db) {
+ return nil
+ }
+
+ if cfg.Db.IsSQLite() && db.Migrator().HasIndex(&models.Heartbeat{}, idxName) {
+ logbuch.Info("running migration '%s'", name)
+ if err := db.Migrator().DropIndex(&models.Heartbeat{}, idxName); err != nil {
+ logbuch.Warn("failed to drop %s", idxName)
+ }
+ }
+
+ setHasRun(name, db)
+ return nil
+ },
+ }
+
+ registerPreMigration(f)
+}
diff --git a/models/compat/shields/v1/badge.go b/models/compat/shields/v1/badge.go
index f9055ac..39f6dc1 100644
--- a/models/compat/shields/v1/badge.go
+++ b/models/compat/shields/v1/badge.go
@@ -8,8 +8,8 @@ import (
// https://shields.io/endpoint
const (
- defaultLabel = "coding time"
- defaultColor = "#2D3748" // not working
+ defaultLabel = "wakapi.dev"
+ defaultColor = "2F855A"
)
type BadgeData struct {
diff --git a/models/heartbeat.go b/models/heartbeat.go
index 9afed6c..ed7470d 100644
--- a/models/heartbeat.go
+++ b/models/heartbeat.go
@@ -11,11 +11,11 @@ import (
type Heartbeat struct {
ID uint64 `gorm:"primary_key" hash:"ignore"`
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
- UserID string `json:"-" gorm:"not null; index:idx_time_user,idx_user_project"` // idx_user_project is for quickly fetching a user's project list (settings page)
+ UserID string `json:"-" gorm:"not null; index:idx_time_user; index:idx_user_project"` // idx_user_project is for quickly fetching a user's project list (settings page)
Entity string `json:"entity" gorm:"not null"`
Type string `json:"type"`
Category string `json:"category"`
- Project string `json:"project" gorm:"index:idx_project,idx_user_project"`
+ Project string `json:"project" gorm:"index:idx_project; index:idx_user_project"`
Branch string `json:"branch" gorm:"index:idx_branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
diff --git a/models/shared.go b/models/shared.go
index ab75246..a38c30d 100644
--- a/models/shared.go
+++ b/models/shared.go
@@ -29,6 +29,11 @@ type Interval struct {
End time.Time
}
+type KeyedInterval struct {
+ Interval
+ Key *IntervalKey
+}
+
// CustomTime is a wrapper type around time.Time, mainly used for the purpose of transparently unmarshalling Python timestamps in the format . (e.g. 1619335137.3324468)
type CustomTime time.Time
diff --git a/repositories/summary.go b/repositories/summary.go
index d330014..29b2c01 100644
--- a/repositories/summary.go
+++ b/repositories/summary.go
@@ -1,8 +1,10 @@
package repositories
import (
+ "github.com/duke-git/lancet/v2/slice"
"github.com/muety/wakapi/models"
"gorm.io/gorm"
+ "gorm.io/gorm/clause"
"time"
)
@@ -23,7 +25,7 @@ func (r *SummaryRepository) GetAll() ([]*models.Summary, error) {
return nil, err
}
- if err := r.populateItems(summaries); err != nil {
+ if err := r.populateItems(summaries, []clause.Interface{}); err != nil {
return nil, err
}
@@ -39,17 +41,26 @@ func (r *SummaryRepository) Insert(summary *models.Summary) error {
func (r *SummaryRepository) GetByUserWithin(user *models.User, from, to time.Time) ([]*models.Summary, error) {
var summaries []*models.Summary
- if err := r.db.
- Where(&models.Summary{UserID: user.ID}).
- Where("from_time >= ?", from.Local()).
- Where("to_time <= ?", to.Local()).
- Order("from_time asc").
- // branch summaries are currently not persisted, as only relevant in combination with project filter
- Find(&summaries).Error; err != nil {
+
+ queryConditions := []clause.Interface{
+ clause.Where{Exprs: r.db.Statement.BuildCondition("user_id = ?", user.ID)},
+ clause.Where{Exprs: r.db.Statement.BuildCondition("from_time >= ?", from.Local())},
+ clause.Where{Exprs: r.db.Statement.BuildCondition("to_time <= ?", to.Local())},
+ }
+
+ q := r.db.Model(&models.Summary{}).
+ Order("from_time asc")
+
+ for _, c := range queryConditions {
+ q.Statement.AddClause(c)
+ }
+
+ // branch summaries are currently not persisted, as only relevant in combination with project filter
+ if err := q.Find(&summaries).Error; err != nil {
return nil, err
}
- if err := r.populateItems(summaries); err != nil {
+ if err := r.populateItems(summaries, queryConditions); err != nil {
return nil, err
}
@@ -76,28 +87,32 @@ func (r *SummaryRepository) DeleteByUser(userId string) error {
}
// inplace
-func (r *SummaryRepository) populateItems(summaries []*models.Summary) error {
- summaryMap := map[uint]*models.Summary{}
- summaryIds := make([]uint, len(summaries))
- for i, s := range summaries {
- if s.NumHeartbeats == 0 {
- continue
- }
- summaryMap[s.ID] = s
- summaryIds[i] = s.ID
- }
-
+func (r *SummaryRepository) populateItems(summaries []*models.Summary, conditions []clause.Interface) error {
var items []*models.SummaryItem
- if err := r.db.
- Model(&models.SummaryItem{}).
- Where("summary_id in ?", summaryIds).
- Find(&items).Error; err != nil {
+ summaryMap := slice.GroupWith[*models.Summary, uint](summaries, func(s *models.Summary) uint {
+ return s.ID
+ })
+
+ q := r.db.Model(&models.SummaryItem{}).
+ Select("summary_items.*").
+ Joins("cross join summaries").
+ Where("summary_items.summary_id = summaries.id").
+ Where("num_heartbeats > ?", 0)
+
+ for _, c := range conditions {
+ q.Statement.AddClause(c)
+ }
+
+ if err := q.Find(&items).Error; err != nil {
return err
}
for _, item := range items {
- l := summaryMap[item.SummaryID].ItemsByType(item.Type)
+ if _, ok := summaryMap[item.SummaryID]; ok {
+ continue
+ }
+ l := summaryMap[item.SummaryID][0].ItemsByType(item.Type)
*l = append(*l, item)
}
diff --git a/routes/api/avatar.go b/routes/api/avatar.go
index 5faf49b..43b1cf3 100644
--- a/routes/api/avatar.go
+++ b/routes/api/avatar.go
@@ -5,7 +5,9 @@ import (
"github.com/gorilla/mux"
lru "github.com/hashicorp/golang-lru"
conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/utils"
"net/http"
+ "time"
)
type AvatarHandler struct {
@@ -33,6 +35,10 @@ func (h *AvatarHandler) RegisterRoutes(router *mux.Router) {
func (h *AvatarHandler) Get(w http.ResponseWriter, r *http.Request) {
hash := mux.Vars(r)["hash"]
+ if utils.IsNoCache(r, 1*time.Hour) {
+ h.cache.Remove(hash)
+ }
+
if !h.cache.Contains(hash) {
h.cache.Add(hash, avatars.MakeMaleAvatar(hash))
}
diff --git a/routes/api/badge.go b/routes/api/badge.go
new file mode 100644
index 0000000..3c69755
--- /dev/null
+++ b/routes/api/badge.go
@@ -0,0 +1,98 @@
+package api
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/v2/maputil"
+ "github.com/duke-git/lancet/v2/slice"
+ "github.com/gorilla/mux"
+ conf "github.com/muety/wakapi/config"
+ "github.com/muety/wakapi/models"
+ v1 "github.com/muety/wakapi/models/compat/shields/v1"
+ routeutils "github.com/muety/wakapi/routes/utils"
+ "github.com/muety/wakapi/services"
+ "github.com/muety/wakapi/utils"
+ "github.com/narqo/go-badge"
+ "github.com/patrickmn/go-cache"
+ "net/http"
+ "time"
+)
+
+type BadgeHandler struct {
+ config *conf.Config
+ cache *cache.Cache
+ userSrvc services.IUserService
+ summarySrvc services.ISummaryService
+}
+
+func NewBadgeHandler(userService services.IUserService, summaryService services.ISummaryService) *BadgeHandler {
+ return &BadgeHandler{
+ config: conf.Get(),
+ cache: cache.New(time.Hour, time.Hour),
+ userSrvc: userService,
+ summarySrvc: summaryService,
+ }
+}
+
+func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
+ r := router.PathPrefix("/badge/{user}").Subrouter()
+ r.Methods(http.MethodGet).HandlerFunc(h.Get)
+}
+
+func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
+ user, err := h.userSrvc.GetUserById(mux.Vars(r)["user"])
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ interval, filters, err := routeutils.GetBadgeParams(r, user)
+ if err != nil {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte(err.Error()))
+ return
+ }
+
+ cacheKey := fmt.Sprintf("%s_%v_%s", user.ID, *interval.Key, filters.Hash())
+ noCache := utils.IsNoCache(r, 1*time.Hour)
+ if cacheResult, ok := h.cache.Get(cacheKey); ok && !noCache {
+ respondSvg(w, cacheResult.([]byte))
+ return
+ }
+
+ params := &models.SummaryParams{
+ From: interval.Start,
+ To: interval.End,
+ User: user,
+ Filters: filters,
+ }
+
+ summary, err, status := routeutils.LoadUserSummaryByParams(h.summarySrvc, params)
+ if err != nil {
+ w.WriteHeader(status)
+ w.Write([]byte(err.Error()))
+ return
+ }
+
+ badgeData := v1.NewBadgeDataFrom(summary)
+ if customLabel := r.URL.Query().Get("label"); customLabel != "" {
+ badgeData.Label = customLabel
+ }
+ if customColor := r.URL.Query().Get("color"); customColor != "" {
+ badgeData.Color = customColor
+ }
+
+ if badgeData.Color[0:1] != "#" && !slice.Contain(maputil.Keys(badge.ColorScheme), badgeData.Color) {
+ badgeData.Color = "#" + badgeData.Color
+ }
+
+ badgeSvg, err := badge.RenderBytes(badgeData.Label, badgeData.Message, badge.Color(badgeData.Color))
+ h.cache.SetDefault(cacheKey, badgeSvg)
+ respondSvg(w, badgeSvg)
+}
+
+func respondSvg(w http.ResponseWriter, data []byte) {
+ w.Header().Set("Content-Type", "image/svg+xml")
+ w.Header().Set("Cache-Control", "max-age=3600")
+ w.WriteHeader(http.StatusOK)
+ w.Write(data)
+}
diff --git a/routes/api/diagnostics.go b/routes/api/diagnostics.go
index f903dbe..f22bf4a 100644
--- a/routes/api/diagnostics.go
+++ b/routes/api/diagnostics.go
@@ -6,7 +6,6 @@ import (
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
- "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
@@ -29,9 +28,6 @@ func NewDiagnosticsApiHandler(userService services.IUserService, diagnosticsServ
func (h *DiagnosticsApiHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/plugins/errors").Subrouter()
- r.Use(
- middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
- )
r.Path("").Methods(http.MethodPost).HandlerFunc(h.Post)
}
diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go
index fbc4162..48ff935 100644
--- a/routes/compat/shields/v1/badge.go
+++ b/routes/compat/shields/v1/badge.go
@@ -2,8 +2,8 @@ package v1
import (
"fmt"
+ routeutils "github.com/muety/wakapi/routes/utils"
"net/http"
- "regexp"
"time"
"github.com/gorilla/mux"
@@ -15,11 +15,6 @@ import (
"github.com/patrickmn/go-cache"
)
-const (
- intervalPattern = `interval:([a-z0-9_]+)`
- entityFilterPattern = `(project|os|editor|language|machine|label):([_a-zA-Z0-9-\s\.]+)`
-)
-
type BadgeHandler struct {
config *conf.Config
userSrvc services.IUserService
@@ -53,77 +48,33 @@ func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
// @Success 200 {object} v1.BadgeData
// @Router /compat/shields/v1/{user}/{interval}/{filter} [get]
func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
- intervalReg := regexp.MustCompile(intervalPattern)
- entityFilterReg := regexp.MustCompile(entityFilterPattern)
-
- var filterEntity, filterKey string
- if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
- filterEntity, filterKey = groups[1], groups[2]
- }
-
- var interval = models.IntervalPast30Days
- if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 {
- if i, err := utils.ParseInterval(groups[1]); err == nil {
- interval = i
- }
- }
-
- requestedUserId := mux.Vars(r)["user"]
- user, err := h.userSrvc.GetUserById(requestedUserId)
+ user, err := h.userSrvc.GetUserById(mux.Vars(r)["user"])
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
- _, rangeFrom, rangeTo := utils.ResolveIntervalTZ(interval, user.TZ())
- minStart := rangeTo.Add(-24 * time.Hour * time.Duration(user.ShareDataMaxDays))
- // negative value means no limit
- if rangeFrom.Before(minStart) && user.ShareDataMaxDays >= 0 {
+ interval, filters, err := routeutils.GetBadgeParams(r, user)
+ if err != nil {
w.WriteHeader(http.StatusForbidden)
- w.Write([]byte("requested time range too broad"))
+ w.Write([]byte(err.Error()))
return
}
- var permitEntity bool
- var filters *models.Filters
- switch filterEntity {
- case "project":
- permitEntity = user.ShareProjects
- filters = models.NewFiltersWith(models.SummaryProject, filterKey)
- case "os":
- permitEntity = user.ShareOSs
- filters = models.NewFiltersWith(models.SummaryOS, filterKey)
- case "editor":
- permitEntity = user.ShareEditors
- filters = models.NewFiltersWith(models.SummaryEditor, filterKey)
- case "language":
- permitEntity = user.ShareLanguages
- filters = models.NewFiltersWith(models.SummaryLanguage, filterKey)
- case "machine":
- permitEntity = user.ShareMachines
- filters = models.NewFiltersWith(models.SummaryMachine, filterKey)
- case "label":
- permitEntity = user.ShareLabels
- filters = models.NewFiltersWith(models.SummaryLabel, filterKey)
- // branches are intentionally omitted here, as only relevant in combination with a project filter
- default:
- permitEntity = true
- filters = &models.Filters{}
- }
-
- if !permitEntity {
- w.WriteHeader(http.StatusForbidden)
- w.Write([]byte("user did not opt in to share entity-specific data"))
- return
- }
-
- cacheKey := fmt.Sprintf("%s_%v_%s_%s", user.ID, *interval, filterEntity, filterKey)
+ cacheKey := fmt.Sprintf("%s_%v_%s", user.ID, *interval.Key, filters.Hash())
if cacheResult, ok := h.cache.Get(cacheKey); ok {
utils.RespondJSON(w, r, http.StatusOK, cacheResult.(*v1.BadgeData))
return
}
- summary, err, status := h.loadUserSummary(user, interval, filters)
+ params := &models.SummaryParams{
+ From: interval.Start,
+ To: interval.End,
+ User: user,
+ Filters: filters,
+ }
+
+ summary, err, status := routeutils.LoadUserSummaryByParams(h.summarySrvc, params)
if err != nil {
w.WriteHeader(status)
w.Write([]byte(err.Error()))
diff --git a/routes/compat/shields/v1/badge_test.go b/routes/compat/shields/v1/badge_test.go
index 9bb2eec..88292df 100644
--- a/routes/compat/shields/v1/badge_test.go
+++ b/routes/compat/shields/v1/badge_test.go
@@ -30,7 +30,7 @@ func TestBadgeHandler_EntityPattern(t *testing.T) {
{test: pathPrefix + "project:Anchr-Android_v2.0", key: "project", val: "Anchr-Android_v2.0"}, // all the way
}
- sut := regexp.MustCompile(entityFilterPattern)
+ sut := regexp.MustCompile(`(project|os|editor|language|machine|label):([^:?&/]+)`) // see entityFilterPattern in badge_utils.go
for _, tc := range tests {
var key, val string
diff --git a/routes/login.go b/routes/login.go
index 9ad3c8e..ac3eb04 100644
--- a/routes/login.go
+++ b/routes/login.go
@@ -91,6 +91,7 @@ func (h *LoginHandler) PostLogin(w http.ResponseWriter, r *http.Request) {
encoded, err := h.config.Security.SecureCookie.Encode(models.AuthCookieKey, login.Username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
+ conf.Log().Request(r).Error("failed to encode secure cookie - %v", err)
templates[conf.LoginTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error"))
return
}
@@ -163,6 +164,7 @@ func (h *LoginHandler) PostSignup(w http.ResponseWriter, r *http.Request) {
_, created, err := h.userSrvc.CreateOrGet(&signup, numUsers == 0)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
+ conf.Log().Request(r).Error("failed to create new user - %v", err)
templates[conf.SignupTemplate].Execute(w, h.buildViewModel(r).WithError("failed to create new user"))
return
}
@@ -237,6 +239,7 @@ func (h *LoginHandler) PostSetPassword(w http.ResponseWriter, r *http.Request) {
user.ResetToken = ""
if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil {
w.WriteHeader(http.StatusInternalServerError)
+ conf.Log().Request(r).Error("failed to set new password - %v", err)
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to set new password"))
return
} else {
@@ -245,6 +248,7 @@ func (h *LoginHandler) PostSetPassword(w http.ResponseWriter, r *http.Request) {
if _, err := h.userSrvc.Update(user); err != nil {
w.WriteHeader(http.StatusInternalServerError)
+ conf.Log().Request(r).Error("failed to save new password - %v", err)
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to save new password"))
return
}
@@ -278,6 +282,7 @@ func (h *LoginHandler) PostResetPassword(w http.ResponseWriter, r *http.Request)
if user, err := h.userSrvc.GetUserByEmail(resetRequest.Email); user != nil && err == nil {
if u, err := h.userSrvc.GenerateResetToken(user); err != nil {
w.WriteHeader(http.StatusInternalServerError)
+ conf.Log().Request(r).Error("failed to generate password reset token - %v", err)
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to generate password reset token"))
return
} else {
diff --git a/routes/summary.go b/routes/summary.go
index c7dd865..268da1d 100644
--- a/routes/summary.go
+++ b/routes/summary.go
@@ -51,6 +51,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
summary, err, status := su.LoadUserSummary(h.summarySrvc, r)
if err != nil {
w.WriteHeader(status)
+ conf.Log().Request(r).Error("failed to load summary - %v", err)
templates[conf.SummaryTemplate].Execute(w, h.buildViewModel(r).WithError(err.Error()))
return
}
diff --git a/routes/utils/badge_utils.go b/routes/utils/badge_utils.go
new file mode 100644
index 0000000..7a82a81
--- /dev/null
+++ b/routes/utils/badge_utils.go
@@ -0,0 +1,85 @@
+package utils
+
+import (
+ "errors"
+ "github.com/muety/wakapi/models"
+ "github.com/muety/wakapi/utils"
+ "net/http"
+ "regexp"
+ "time"
+)
+
+const (
+ intervalPattern = `interval:([a-z0-9_]+)`
+ entityFilterPattern = `(project|os|editor|language|machine|label):([^:?&/]+)`
+)
+
+var (
+ intervalReg *regexp.Regexp
+ entityFilterReg *regexp.Regexp
+)
+
+func init() {
+ intervalReg = regexp.MustCompile(intervalPattern)
+ entityFilterReg = regexp.MustCompile(entityFilterPattern)
+}
+
+func GetBadgeParams(r *http.Request, requestedUser *models.User) (*models.KeyedInterval, *models.Filters, error) {
+ var filterEntity, filterKey string
+ if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
+ filterEntity, filterKey = groups[1], groups[2]
+ }
+
+ var intervalKey = models.IntervalPast30Days
+ if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 {
+ if i, err := utils.ParseInterval(groups[1]); err == nil {
+ intervalKey = i
+ }
+ }
+
+ _, rangeFrom, rangeTo := utils.ResolveIntervalTZ(intervalKey, requestedUser.TZ())
+ interval := &models.KeyedInterval{
+ Interval: models.Interval{Start: rangeFrom, End: rangeTo},
+ Key: intervalKey,
+ }
+
+ minStart := rangeTo.Add(-24 * time.Hour * time.Duration(requestedUser.ShareDataMaxDays))
+ // negative value means no limit
+ if rangeFrom.Before(minStart) && requestedUser.ShareDataMaxDays >= 0 {
+ return nil, nil, errors.New("requested time range too broad")
+ }
+
+ var permitEntity bool
+ var filters *models.Filters
+ switch filterEntity {
+ case "project":
+ permitEntity = requestedUser.ShareProjects
+ filters = models.NewFiltersWith(models.SummaryProject, filterKey)
+ case "os":
+ permitEntity = requestedUser.ShareOSs
+ filters = models.NewFiltersWith(models.SummaryOS, filterKey)
+ case "editor":
+ permitEntity = requestedUser.ShareEditors
+ filters = models.NewFiltersWith(models.SummaryEditor, filterKey)
+ case "language":
+ permitEntity = requestedUser.ShareLanguages
+ filters = models.NewFiltersWith(models.SummaryLanguage, filterKey)
+ case "machine":
+ permitEntity = requestedUser.ShareMachines
+ filters = models.NewFiltersWith(models.SummaryMachine, filterKey)
+ case "label":
+ permitEntity = requestedUser.ShareLabels
+ filters = models.NewFiltersWith(models.SummaryLabel, filterKey)
+ // branches are intentionally omitted here, as only relevant in combination with a project filter
+ default:
+ // non-entity-specific request, just a general, in-total query
+ permitEntity = true
+ filters = &models.Filters{}
+ }
+
+ if !permitEntity {
+ return nil, nil, errors.New("user did not opt in to share entity-specific data")
+ }
+
+ return interval, filters, nil
+}
diff --git a/routes/utils/summary_utils.go b/routes/utils/summary_utils.go
index 739434f..d07304c 100644
--- a/routes/utils/summary_utils.go
+++ b/routes/utils/summary_utils.go
@@ -1,7 +1,6 @@
package utils
import (
- "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
@@ -9,24 +8,33 @@ import (
)
func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summary, error, int) {
- user := middlewares.GetPrincipal(r)
summaryParams, err := utils.ParseSummaryParams(r)
if err != nil {
return nil, err, http.StatusBadRequest
}
+ return LoadUserSummaryByParams(ss, summaryParams)
+}
+func LoadUserSummaryByParams(ss services.ISummaryService, params *models.SummaryParams) (*models.Summary, error, int) {
var retrieveSummary services.SummaryRetriever = ss.Retrieve
- if summaryParams.Recompute {
+ if params.Recompute {
retrieveSummary = ss.Summarize
}
- summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Filters, summaryParams.Recompute)
+ summary, err := ss.Aliased(
+ params.From,
+ params.To,
+ params.User,
+ retrieveSummary,
+ params.Filters,
+ params.Recompute,
+ )
if err != nil {
return nil, err, http.StatusInternalServerError
}
- summary.FromTime = models.CustomTime(summary.FromTime.T().In(user.TZ()))
- summary.ToTime = models.CustomTime(summary.ToTime.T().In(user.TZ()))
+ summary.FromTime = models.CustomTime(summary.FromTime.T().In(params.User.TZ()))
+ summary.ToTime = models.CustomTime(summary.ToTime.T().In(params.User.TZ()))
return summary, nil, http.StatusOK
}
diff --git a/services/aggregation.go b/services/aggregation.go
index 6310ef9..f11653a 100644
--- a/services/aggregation.go
+++ b/services/aggregation.go
@@ -45,13 +45,8 @@ type AggregationJob struct {
// Schedule a job to (re-)generate summaries every day shortly after midnight
func (srv *AggregationService) Schedule() {
- // Run once initially
- if err := srv.Run(datastructure.NewSet[string]()); err != nil {
- logbuch.Fatal("failed to run AggregationJob: %v", err)
- }
-
s := gocron.NewScheduler(time.Local)
- s.Every(1).Day().At(srv.config.App.AggregationTime).Do(srv.Run, datastructure.NewSet[string]())
+ s.Every(1).Day().At(srv.config.App.AggregationTime).WaitForSchedule().Do(srv.Run, datastructure.NewSet[string]())
s.StartBlocking()
}
diff --git a/services/duration.go b/services/duration.go
index 1b40220..af2aa86 100644
--- a/services/duration.go
+++ b/services/duration.go
@@ -1,6 +1,7 @@
package services
import (
+ "github.com/duke-git/lancet/v2/mathutil"
"github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"time"
@@ -59,13 +60,26 @@ func (srv *DurationService) Get(from, to time.Time, user *models.User, filters *
continue
}
- dur := d1.Time.T().Sub(latest.Time.T().Add(latest.Duration))
- if dur > HeartbeatDiffThreshold {
- dur = HeartbeatDiffThreshold
+ sameDay := d1.Time.T().Day() == latest.Time.T().Day()
+ dur := time.Duration(mathutil.Min(
+ int64(d1.Time.T().Sub(latest.Time.T().Add(latest.Duration))),
+ int64(HeartbeatDiffThreshold),
+ ))
+
+ // skip heartbeats that span across two adjacent summaries (assuming there are no more than 1 summary per day)
+ // this is relevant to prevent the time difference between generating summaries from raw heartbeats and aggregating pre-generated summaries
+ // for the latter case, the very last heartbeat of a day won't be counted, so we don't want to count it here either
+ // another option would be to adapt the Summarize() method to always append up to HeartbeatDiffThreshold seconds to a day's very last duration
+ if !sameDay {
+ dur = 0
}
latest.Duration += dur
- if dur >= HeartbeatDiffThreshold || latest.GroupHash != d1.GroupHash {
+ // start new "group" if:
+ // (a) heartbeats were too far apart each other,
+ // (b) if they are of a different entity or,
+ // (c) if they span across two days
+ if dur >= HeartbeatDiffThreshold || latest.GroupHash != d1.GroupHash || !sameDay {
list := mapping[d1.GroupHash]
if d0 := list[len(list)-1]; d0 != d1 {
mapping[d1.GroupHash] = append(mapping[d1.GroupHash], d1)
diff --git a/services/misc.go b/services/misc.go
index e1b03d5..f493209 100644
--- a/services/misc.go
+++ b/services/misc.go
@@ -38,13 +38,8 @@ type CountTotalTimeResult struct {
}
func (srv *MiscService) ScheduleCountTotalTime() {
- // Run once initially
- if err := srv.runCountTotalTime(); err != nil {
- logbuch.Fatal("failed to run CountTotalTimeJob: %v", err)
- }
-
s := gocron.NewScheduler(time.Local)
- s.Every(1).Hour().Do(srv.runCountTotalTime)
+ s.Every(1).Hour().WaitForSchedule().Do(srv.runCountTotalTime)
s.StartBlocking()
}
diff --git a/services/summary.go b/services/summary.go
index 1e22269..4dfbe6f 100644
--- a/services/summary.go
+++ b/services/summary.go
@@ -117,6 +117,12 @@ func (srv *SummaryService) Retrieve(from, to time.Time, user *models.User, filte
missingIntervals := srv.getMissingIntervals(from, to, summaries, false)
for _, interval := range missingIntervals {
if s, err := srv.Summarize(interval.Start, interval.End, user, filters); err == nil {
+ if len(missingIntervals) > 2 && s.FromTime.T().Equal(s.ToTime.T()) {
+ // little hack here: GetAllWithin will query for >= from_date
+ // however, for "in-between" / intra-day missing intervals, we want strictly > from_date to prevent double-counting
+ // to not have to rewrite many interfaces, we skip these summaries here
+ continue
+ }
summaries = append(summaries, s)
} else {
return nil, err
@@ -401,14 +407,14 @@ func (srv *SummaryService) getMissingIntervals(from, to time.Time, summaries []*
// we always want to jump to beginning of next day
// however, if left summary ends already at midnight, we would instead jump to beginning of second-next day -> go back again
- if td1.Sub(t1) == 24*time.Hour {
+ if td1.AddDate(0, 0, 1).Equal(t1) {
td1 = td1.Add(-1 * time.Hour)
}
}
// one or more day missing in between?
if td1.Before(td2) {
- intervals = append(intervals, &models.Interval{Start: summaries[i].ToTime.T(), End: summaries[i+1].FromTime.T()})
+ intervals = append(intervals, &models.Interval{Start: t1, End: t2})
}
}
diff --git a/services/summary_test.go b/services/summary_test.go
index 0c9f61b..b9d591d 100644
--- a/services/summary_test.go
+++ b/services/summary_test.go
@@ -318,7 +318,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve() {
assert.Equal(suite.T(), 45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject1))
assert.Equal(suite.T(), 45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject2))
assert.Equal(suite.T(), 200, result.NumHeartbeats)
- suite.DurationService.AssertNumberOfCalls(suite.T(), "Get", 2+1+1)
+ suite.DurationService.AssertNumberOfCalls(suite.T(), "Get", 2+1)
}
func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve_DuplicateSummaries() {
diff --git a/static/assets/css/app.dist.css b/static/assets/css/app.dist.css
index 3bc2193..abe1771 100644
--- a/static/assets/css/app.dist.css
+++ b/static/assets/css/app.dist.css
@@ -1 +1 @@
-/*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com*//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */html{-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:initial;background-image:none}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:after,:before{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.absolute{position:absolute}.relative{position:relative}.top-0{top:0}.right-0{right:0}.z-10{z-index:10}.row-span-2{grid-row:span 2/span 2}.float-right{float:right}.m-0{margin:0}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-8{margin-top:2rem;margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-10{margin-top:2.5rem}.mt-12{margin-top:3rem}.mt-16{margin-top:4rem}.mt-20{margin-top:5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mr-8{margin-right:2rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mb-16{margin-bottom:4rem}.-mb-1{margin-bottom:-.25rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.-ml-1{margin-left:-.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-4{width:1rem}.w-12{width:3rem}.w-40{width:10rem}.w-1\/2{width:50%}.w-full{width:100%}.max-w-lg{max-width:32rem}.max-w-4xl{max-width:56rem}.max-w-screen-sm{max-width:640px}.max-w-screen-lg{max-width:1024px}.max-w-screen-xl{max-width:1280px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-6{row-gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border-4{border-width:4px}.border{border-width:1px}.border-t{border-top-width:1px}.border-l{border-left-width:1px}.border-gray-700{--tw-border-opacity:1;border-color:rgba(55,65,81,var(--tw-border-opacity))}.border-gray-800{--tw-border-opacity:1;border-color:rgba(31,41,55,var(--tw-border-opacity))}.border-green-700{--tw-border-opacity:1;border-color:rgba(4,120,87,var(--tw-border-opacity))}.bg-transparent{background-color:initial}.bg-gray-800{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgba(17,24,39,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.focus\:bg-gray-800:focus,.hover\:bg-gray-800:hover{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pt-10{padding-top:2.5rem}.pb-4{padding-bottom:1rem}.pb-10{padding-bottom:2.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-8xl{font-size:6rem;line-height:1}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.leading-snug{line-height:1.375}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgba(4,120,87,var(--tw-text-opacity))}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{--tw-shadow:0 0 #0000}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)}.outline-none{outline:2px solid transparent;outline-offset:2px}*,:after,:before{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000}body{font-family:Source Sans\ 3,Roboto,Helvetica Neue,Helvetica,Arial,sans-serif}[v-cloak]{display:none}.bg-gray-850{background-color:#242b3a}.hover\:bg-gray-850:hover{--bg-opacity:1;background-color:#242b3a}.text-xxs{font-size:.65rem}.text-8xl{font-size:5rem;line-height:1.1}.imp\:cursor-not-allowed{cursor:not-allowed!important}.h1{margin:0;font-size:1.875rem;line-height:2.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.h1-subcaption{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.btn-default,.h1-subcaption{font-size:.875rem;line-height:1.25rem}.btn-default{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity));padding:.5rem 1rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.btn-default:hover{--bg-opacity:1;background-color:#242b3a}.btn-disabled{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.btn-primary{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(4,120,87,var(--tw-bg-opacity))}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgba(6,95,70,var(--tw-bg-opacity))}.btn-primary{padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.btn-danger{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.btn-danger:hover{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.btn-danger{padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.input-default{width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem}.input-default:focus{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.input-default{padding:.5rem 1rem;--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px;background-color:#242b3a}.select-default{cursor:pointer;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem}.select-default:focus{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.select-default{padding:.5rem 1rem;--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px;background-color:#242b3a}.menu-item{display:flex;cursor:pointer;align-items:center}.menu-item>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.menu-item{border-radius:.25rem;padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600}.menu-item:hover{--bg-opacity:1;background-color:#242b3a}.submenu-item{border-radius:.25rem}.submenu-item:hover{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.submenu-item{padding:.25rem;text-align:right}.chip{margin-bottom:.25rem;display:inline-block;border-radius:.25rem;border-radius:9999px;padding:.25rem .5rem;font-size:.75rem;line-height:1rem;background-color:#242b3a}.chip,.link{font-weight:600}.link{color:rgba(156,163,175,var(--tw-text-opacity))}.link,.link:hover{--tw-text-opacity:1}.link:hover{color:rgba(209,213,219,var(--tw-text-opacity))}::-webkit-calendar-picker-indicator{filter:invert(1);cursor:pointer}@media (min-width:640px){.sm\:inline-block{display:inline-block}.sm\:flex{display:flex}}@media (min-width:768px){.md\:mb-0{margin-bottom:0}.md\:flex{display:flex}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.333333%}.md\:w-2\/3{width:66.666667%}.md\:w-3\/4{width:75%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:flex-nowrap{flex-wrap:nowrap}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width:1024px){.lg\:inline-block{display:inline-block}.lg\:w-3\/4{width:75%}.lg\:px-24{padding-left:6rem;padding-right:6rem}}
\ No newline at end of file
+/*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com*//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */html{-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:initial;background-image:none}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:after,:before{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.absolute{position:absolute}.relative{position:relative}.top-0{top:0}.right-0{right:0}.z-10{z-index:10}.row-span-2{grid-row:span 2/span 2}.float-right{float:right}.m-0{margin:0}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-8{margin-top:2rem;margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-10{margin-top:2.5rem}.mt-12{margin-top:3rem}.mt-16{margin-top:4rem}.mt-20{margin-top:5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mr-8{margin-right:2rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mb-10{margin-bottom:2.5rem}.mb-16{margin-bottom:4rem}.-mb-1{margin-bottom:-.25rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.-ml-1{margin-left:-.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-4{width:1rem}.w-12{width:3rem}.w-40{width:10rem}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-2\/3{width:66.666667%}.w-full{width:100%}.max-w-lg{max-width:32rem}.max-w-4xl{max-width:56rem}.max-w-screen-sm{max-width:640px}.max-w-screen-lg{max-width:1024px}.max-w-screen-xl{max-width:1280px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-6{row-gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border-4{border-width:4px}.border{border-width:1px}.border-t{border-top-width:1px}.border-l{border-left-width:1px}.border-gray-700{--tw-border-opacity:1;border-color:rgba(55,65,81,var(--tw-border-opacity))}.border-gray-800{--tw-border-opacity:1;border-color:rgba(31,41,55,var(--tw-border-opacity))}.border-green-700{--tw-border-opacity:1;border-color:rgba(4,120,87,var(--tw-border-opacity))}.bg-transparent{background-color:initial}.bg-gray-800{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgba(17,24,39,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.focus\:bg-gray-800:focus,.hover\:bg-gray-800:hover{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pt-10{padding-top:2.5rem}.pb-4{padding-bottom:1rem}.pb-10{padding-bottom:2.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-8xl{font-size:6rem;line-height:1}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.leading-snug{line-height:1.375}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgba(4,120,87,var(--tw-text-opacity))}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{--tw-shadow:0 0 #0000}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)}.outline-none{outline:2px solid transparent;outline-offset:2px}*,:after,:before{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000}body{font-family:Source Sans\ 3,Roboto,Helvetica Neue,Helvetica,Arial,sans-serif}[v-cloak]{display:none}.bg-gray-850{background-color:#242b3a}.hover\:bg-gray-850:hover{--bg-opacity:1;background-color:#242b3a}.text-xxs{font-size:.65rem}.text-8xl{font-size:5rem;line-height:1.1}.imp\:cursor-not-allowed{cursor:not-allowed!important}.h1{margin:0;font-size:1.875rem;line-height:2.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.h1-subcaption{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.btn-default,.h1-subcaption{font-size:.875rem;line-height:1.25rem}.btn-default{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity));padding:.5rem 1rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.btn-default:hover{--bg-opacity:1;background-color:#242b3a}.btn-disabled{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity));padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.btn-primary{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(4,120,87,var(--tw-bg-opacity))}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgba(6,95,70,var(--tw-bg-opacity))}.btn-primary{padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.btn-danger{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.btn-danger:hover{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.btn-danger{padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600;--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.input-default{width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem}.input-default:focus{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.input-default{padding:.5rem 1rem;--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px;background-color:#242b3a}.select-default{cursor:pointer;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem}.select-default:focus{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.select-default{padding:.5rem 1rem;--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity));outline:2px solid transparent;outline-offset:2px;background-color:#242b3a}.menu-item{display:flex;cursor:pointer;align-items:center}.menu-item>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.menu-item{border-radius:.25rem;padding:.5rem 1rem;font-size:.875rem;line-height:1.25rem;font-weight:600}.menu-item:hover{--bg-opacity:1;background-color:#242b3a}.submenu-item{border-radius:.25rem}.submenu-item:hover{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.submenu-item{padding:.25rem;text-align:right}.chip{margin-bottom:.25rem;display:inline-block;border-radius:.25rem;border-radius:9999px;padding:.25rem .5rem;font-size:.75rem;line-height:1rem;background-color:#242b3a}.chip,.link{font-weight:600}.link{color:rgba(156,163,175,var(--tw-text-opacity))}.link,.link:hover{--tw-text-opacity:1}.link:hover{color:rgba(209,213,219,var(--tw-text-opacity))}::-webkit-calendar-picker-indicator{filter:invert(1);cursor:pointer}@media (min-width:640px){.sm\:inline-block{display:inline-block}.sm\:flex{display:flex}}@media (min-width:768px){.md\:mb-0{margin-bottom:0}.md\:flex{display:flex}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.333333%}.md\:w-2\/3{width:66.666667%}.md\:w-3\/4{width:75%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:flex-nowrap{flex-wrap:nowrap}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width:1024px){.lg\:inline-block{display:inline-block}.lg\:w-3\/4{width:75%}.lg\:px-24{padding-left:6rem;padding-right:6rem}}
\ No newline at end of file
diff --git a/static/assets/css/app.dist.css.br b/static/assets/css/app.dist.css.br
index 34b8ad3..2bc790a 100644
Binary files a/static/assets/css/app.dist.css.br and b/static/assets/css/app.dist.css.br differ
diff --git a/static/assets/js/components/summary.js b/static/assets/js/components/summary.js
index cbe9d49..fff253d 100644
--- a/static/assets/js/components/summary.js
+++ b/static/assets/js/components/summary.js
@@ -1,3 +1,9 @@
PetiteVue.createApp({
$delimiters: ['${', '}'],
+ get currentInterval() {
+ const urlParams = new URLSearchParams(window.location.search)
+ if (urlParams.has('interval')) return urlParams.get('interval')
+ if (!urlParams.has('from') && !urlParams.has('to')) return 'today'
+ return null
+ }
}).mount('#summary-page')
\ No newline at end of file
diff --git a/testing/wakapi_api_tests.postman_collection.json b/testing/wakapi_api_tests.postman_collection.json
index 73e807b..a3d0725 100644
--- a/testing/wakapi_api_tests.postman_collection.json
+++ b/testing/wakapi_api_tests.postman_collection.json
@@ -2097,7 +2097,7 @@
"",
"pm.test(\"Correct content\", function () {",
" const jsonData = pm.response.json();",
- " pm.expect(jsonData.data.text).to.eql('0 hrs 8 mins');",
+ " pm.expect(jsonData.data.text).to.eql('0 hrs 4 mins');",
"});"
],
"type": "text/javascript"
diff --git a/utils/http.go b/utils/http.go
index afbeb44..e2ab8b9 100644
--- a/utils/http.go
+++ b/utils/http.go
@@ -4,8 +4,24 @@ import (
"encoding/json"
"github.com/muety/wakapi/config"
"net/http"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
)
+const (
+ cacheMaxAgePattern = `max-age=(\d+)`
+)
+
+var (
+ cacheMaxAgeRe *regexp.Regexp
+)
+
+func init() {
+ cacheMaxAgeRe = regexp.MustCompile(cacheMaxAgePattern)
+}
+
func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
@@ -13,3 +29,16 @@ func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object inte
config.Log().Request(r).Error("error while writing json response: %v", err)
}
}
+
+func IsNoCache(r *http.Request, cacheTtl time.Duration) bool {
+ cacheControl := r.Header.Get("cache-control")
+ if strings.Contains(cacheControl, "no-cache") {
+ return true
+ }
+ if match := cacheMaxAgeRe.FindStringSubmatch(cacheControl); match != nil && len(match) > 1 {
+ if maxAge, _ := strconv.Atoi(match[1]); time.Duration(maxAge)*time.Second <= cacheTtl {
+ return true
+ }
+ }
+ return false
+}
diff --git a/version.txt b/version.txt
index e703481..fd06a92 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-2.3.2
\ No newline at end of file
+2.3.4
\ No newline at end of file
diff --git a/views/index.tpl.html b/views/index.tpl.html
index 459dd31..cc99eda 100644
--- a/views/index.tpl.html
+++ b/views/index.tpl.html
@@ -58,7 +58,7 @@
-
+
@@ -81,11 +81,11 @@
diff --git a/views/logo.tpl.html b/views/logo.tpl.html
index dfdda4e..702768d 100644
--- a/views/logo.tpl.html
+++ b/views/logo.tpl.html
@@ -1,3 +1,3 @@
-
+
\ No newline at end of file
diff --git a/views/settings.tpl.html b/views/settings.tpl.html
index 32a5dd2..559698a 100644
--- a/views/settings.tpl.html
+++ b/views/settings.tpl.html
@@ -545,41 +545,53 @@
-
Badges (Shields.IO)
+
Badges
- The integration with Shields.IO allows to generate badges for README pages or forums. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See Permissions .
- Only available on public instances, not on localhost.
+ This integration with allows to generate badges for README pages or forums. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See Permissions . Adapt the URL's label and color parameters for customized badges.
+ In addition, there is an endpoint compatible with Shields.IO to allow for even more customization (e.g. different styles ). Only available on public instances, not on localhost.
{{ if ne .User.ShareDataMaxDays 0 }}
-
-
+
+
+
+
+
+
diff --git a/views/summary.tpl.html b/views/summary.tpl.html
index 4a1d0e2..6ecb11c 100644
--- a/views/summary.tpl.html
+++ b/views/summary.tpl.html
@@ -16,194 +16,201 @@
{{ if .User.HasData }}
-
-
+
+
+
+ {{ end }}
+
+
+
+ {{ if .User.HasData }}
+
+ {{ if not .IsProjectDetails }}
+
+
+
+ Total Time
+ {{ .TotalTime | duration }}
+
+
+ Total Heartbeats
+ {{ .NumHeartbeats }}
+
+
+ Top Project
+ {{ .MaxByToString 0 }}
+
+
+ Top Language
+ {{ .MaxByToString 1 }}
+
+
+ Top OS
+ {{ .MaxByToString 3 }}
+
+
+ Top Editor
+ {{ .MaxByToString 2 }}
+
+
+ {{ else }}
+
+
Project "{{ .GetProjectFilter }}"
+
+
{{ .TotalTime | duration }}
+
+
+
+
+
+ {{ end }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Operating Systems
+ Machines
+
+
+
+
+
+
+
+ No data
+
+
+
+
+
+
+
+
+ Operating Systems
+ Machines
+
+
+
+
+
+
+
+ No data
+
+
+
+
+
+
+
+ {{ else }}
+
+
+
+
+
+
Welcome to Wakapi!
+
+ It looks like there is no data available for the specified time range. If you logged in to Wakapi for the first time, see the setup instructions below on how to get started.
+
+
+
Setup Instructions
+
+ # Step 1: Download WakaTime plugin for your IDE
+ # See: https://wakatime.com/plugins
+
+ # Step 2: Set your ~/.wakatime.cfg to this:
+
+ [settings]
+ api_url = %s/api
+ api_key = {{ .ApiKey }}
+
+ # Step 3: Start coding and then check back here!
+
+
+
+
+ {{ end }}
+
+
-{{ end }}
-
-
-
- {{ if .User.HasData }}
-
- {{ if not .IsProjectDetails }}
-
-
-
- Total Time
- {{ .TotalTime | duration }}
-
-
- Total Heartbeats
- {{ .NumHeartbeats }}
-
-
- Top Project
- {{ .MaxByToString 0 }}
-
-
- Top Language
- {{ .MaxByToString 1 }}
-
-
- Top OS
- {{ .MaxByToString 3 }}
-
-
- Top Editor
- {{ .MaxByToString 2 }}
-
-
- {{ else }}
-
-
Project "{{ .GetProjectFilter }}"
- {{ .TotalTime | duration }}
-
- {{ end }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Operating Systems
- Machines
-
-
-
-
-
-
-
- No data
-
-
-
-
-
-
-
-
- Operating Systems
- Machines
-
-
-
-
-
-
-
- No data
-
-
-
-
-
-
-
- {{ else }}
-
-
-
-
-
-
Welcome to Wakapi!
-
- It looks like there is no data available for the specified time range. If you logged in to Wakapi for the first time, see the setup instructions below on how to get started.
-
-
-
Setup Instructions
-
- # Step 1: Download WakaTime plugin for your IDE
- # See: https://wakatime.com/plugins
-
- # Step 2: Set your ~/.wakatime.cfg to this:
-
- [settings]
- api_url = %s/api
- api_key = {{ .ApiKey }}
-
- # Step 3: Start coding and then check back here!
-
-
-
-
- {{ end }}
-
-
-
{{ template "footer.tpl.html" . }}
{{ template "foot.tpl.html" . }}