1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

Compare commits

..

21 Commits

Author SHA1 Message Date
1c0477f861 Merge pull request #279 from muety/docker
fix: anticipated docker push issue
2021-12-14 15:31:06 +01:00
28a3418ad5 fix: limit sqlite connection pool to one 2021-12-14 02:17:59 +01:00
c5db2c235f chore: enable foreign key constraints for new sqlite databases 2021-12-14 00:47:04 +01:00
9cbddaeedf fix: anticipated docker push issue 2021-12-02 23:06:19 +11:00
485dfe2888 fix: user time zone test (fix #275) [ci skip] 2021-11-28 12:40:46 +01:00
Kid
78a26dbf3c Another typo fix 2021-11-26 22:55:43 +11:00
b2c72c6420 Merge pull request #272 from kidonng/patch-1 [ci skip]
Fix a typo
2021-11-25 15:31:05 +01:00
Kid
6852494d36 Fix a typo 2021-11-25 22:28:20 +08:00
305166ce68 Remove Table of Contents from README [ci skip]
Github has menu, and the links don't seem to work on Docker Hub
2021-11-25 20:31:41 +11:00
400f25c23e fix: remove dead client-side proxy link in README [ci skip]
This link is present in Client Setup section
2021-11-25 20:28:43 +11:00
3aacd3461d Merge pull request #265 from muety/ghcr
ci: add ghcr for Docker deploy, change cache
2021-10-21 13:42:29 +02:00
7e2460e1f0 ci: add ghcr for Docker deploy, change cache 2021-10-21 20:54:13 +11:00
57175ae7f8 docs: update readme [ci skip] 2021-10-14 13:09:26 +02:00
5df0f48303 feat: user avatars 2021-10-14 12:04:21 +02:00
76a7cf7e80 chore: include runtime metrics 2021-10-14 10:35:01 +02:00
7cae3c43d0 chore: enhanced caching for user entity sets (resolve #264) 2021-10-14 10:22:59 +02:00
5fc87dd143 fix: tests 2021-10-13 17:51:54 +02:00
7329f6a34e chore: version 2021-10-13 17:47:33 +02:00
3b96bd3723 docs: include relay endpoint in swagger docs 2021-10-13 17:47:18 +02:00
2c7977cf63 chore: invert visualization of project labels (resolve #263) 2021-10-13 17:12:55 +02:00
782da0b49e chore: update landing page 2021-10-13 11:27:04 +02:00
40 changed files with 1382 additions and 610 deletions

View File

@ -20,39 +20,42 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push to Docker Hub
- name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: |
n1try/wakapi:${{ env.GIT_TAG }}
n1try/wakapi:latest
n1try/wakapi:${{ env.GIT_TAG }}
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
cache-from: type=registry,ref=n1try/wakapi:buildcache
cache-to: type=registry,ref=n1try/wakapi:buildcache,mode=max
- name: Build and push to Docker Hub (Alpine)
- name: Build and push (Alpine)
uses: docker/build-push-action@v2
with:
file: Dockerfile.alpine
push: true
tags: |
n1try/wakapi:${{ env.GIT_TAG }}-alpine
n1try/wakapi:latest-alpine
n1try/wakapi:${{ env.GIT_TAG }}-alpine
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-alpine
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
cache-from: type=registry,ref=n1try/wakapi:buildcache-alpine
cache-to: type=registry,ref=n1try/wakapi:buildcache-alpine,mode=max

View File

@ -32,21 +32,7 @@
<img src="static/assets/images/screenshot.png" width="500px">
</p>
## Table of Contents
* [User Survey](#-user-survey)
* [Features](#-features)
* [Roadmap](#-roadmap)
* [How to use](#%EF%B8%8F-how-to-use)
* [Configuration Options](#-configuration-options)
* [API Endpoints](#-api-endpoints)
* [Integrations](#-integrations)
* [Best Practices](#-best-practices)
* [Tests](#-tests)
* [Developer Notes](#-developer-notes)
* [Support](#-support)
* [FAQs](#-faqs)
Further instructions can be found in the [Wiki](https://github.com/muety/wakapi/wiki).
Installation instructions can be found below and in the [Wiki](https://github.com/muety/wakapi/wiki).
## 📬 **User Survey**
I'd love to get some community feedback from active Wakapi users. If you want, please participate in the recent [user survey](https://github.com/muety/wakapi/issues/82). Thanks a lot!
@ -71,7 +57,7 @@ Plans for the near future mainly include, besides usual improvements and bug fix
There are different options for how to use Wakapi, ranging from our hosted cloud service to self-hosting it. Regardless of which option choose, you will always have to do the [client setup](#-client-setup) in addition.
### ☁️ Option 1: Use [wakapi.dev](https://wakapi.dev)
If you want to you out free, hosted cloud service, all you need to do is create an account and the set up your client-side tooling (see below).
If you want to try out free, hosted cloud service, all you need to do is create an account and the set up your client-side tooling (see below).
However, we do not guarantee data persistence, so you might potentially lose your data if the service is taken down some day ❕
@ -141,42 +127,43 @@ Optionally, you can set up a [client-side proxy](https://github.com/muety/wakapi
## 🔧 Configuration Options
You can specify configuration options either via a config file (default: `config.yml`, customziable through the `-c` argument) or via environment variables. Here is an overview of all options.
| YAML Key | Environment Variable | Default | Description |
|---------------------------|---------------------------|--------------|---------------------------------------------------------------------|
| `env` | `ENVIRONMENT` | `dev` | Whether to use development- or production settings |
| `app.custom_languages` | - | - | Map from file endings to language names |
| `server.port` | `WAKAPI_PORT` | `3000` | Port to listen on |
| `server.listen_ipv4` | `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) |
| `server.listen_ipv6` | `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) |
| `server.listen_socket` | `WAKAPI_LISTEN_SOCKET` | - | UNIX socket to listen on (leave blank to disable UNIX socket) |
| `server.timeout_sec` | `WAKAPI_TIMEOUT_SEC` | `30` | Request timeout in seconds |
| `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) |
| `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 |
| `security.allow_signup` | `WAKAPI_ALLOW_SIGNUP` | `true` | Whether to enable user registration |
| `security.expose_metrics` | `WAKAPI_EXPOSE_METRICS` | `false` | Whether to expose Prometheus metrics under `/api/metrics` |
| `db.host` | `WAKAPI_DB_HOST` | - | Database host |
| `db.port` | `WAKAPI_DB_PORT` | - | Database port |
| `db.user` | `WAKAPI_DB_USER` | - | Database user |
| `db.password` | `WAKAPI_DB_PASSWORD` | - | Database password |
| `db.name` | `WAKAPI_DB_NAME` | `wakapi_db.db` | Database name |
| `db.dialect` | `WAKAPI_DB_TYPE` | `sqlite3` | Database type (one of `sqlite3`, `mysql`, `postgres`, `cockroach`) |
| `db.charset` | `WAKAPI_DB_CHARSET` | `utf8mb4` | Database connection charset (for MySQL only) |
| `db.max_conn` | `WAKAPI_DB_MAX_CONNECTIONS` | `2` | Maximum number of database connections |
| `db.ssl` | `WAKAPI_DB_SSL` | `false` | Whether to use TLS encryption for database connection (Postgres and CockroachDB only) |
| `db.automgirate_fail_silently` | `WAKAPI_DB_AUTOMIGRATE_FAIL_SILENTLY` | `false` | Whether to ignore schema auto-migration failures when starting up |
| `mail.enabled` | `WAKAPI_MAIL_ENABLED` | `true` | Whether to allow Wakapi to send e-mail (e.g. for password resets) |
| `mail.sender` | `WAKAPI_MAIL_SENDER` | `noreply@wakapi.dev` | Default sender address for outgoing mails (ignored for MailWhale) |
| `mail.provider` | `WAKAPI_MAIL_PROVIDER` | `smtp` | Implementation to use for sending mails (one of [`smtp`, `mailwhale`]) |
| `mail.smtp.*` | `WAKAPI_MAIL_SMTP_*` | `-` | Various options to configure SMTP. See [default config](config.default.yml) for details |
| `mail.mailwhale.*` | `WAKAPI_MAIL_MAILWHALE_*` | `-` | Various options to configure [MailWhale](https://mailwhale.dev) sending service. See [default config](config.default.yml) for details |
| `sentry.dsn` | `WAKAPI_SENTRY_DSN` | | DSN for to integrate [Sentry](https://sentry.io) for error logging and tracing (leave empty to disable) |
| `sentry.enable_tracing` | `WAKAPI_SENTRY_TRACING` | `false` | Whether to enable Sentry request tracing |
| `sentry.sample_rate` | `WAKAPI_SENTRY_SAMPLE_RATE` | `0.75` | Probability of tracing a request in Sentry |
| `sentry.sample_rate_heartbeats` | `WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS` | `0.1` | Probability of tracing a heartbeats request in Sentry |
| YAML Key / Env. Variable | Default | Description |
|-----------------------------|--------------|---------------------------------------------------------------------|
| `env` /<br>`ENVIRONMENT` | `dev` | Whether to use development- or production settings |
| `app.custom_languages` | - | Map from file endings to language names |
| `app.avatar_url_template` | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) |
| `server.port` /<br> `WAKAPI_PORT` | `3000` | Port to listen on |
| `server.listen_ipv4` /<br> `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) |
| `server.listen_ipv6` /<br> `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) |
| `server.listen_socket` /<br> `WAKAPI_LISTEN_SOCKET` | - | UNIX socket to listen on (leave blank to disable UNIX socket) |
| `server.timeout_sec` /<br> `WAKAPI_TIMEOUT_SEC` | `30` | Request timeout in seconds |
| `server.tls_cert_path` /<br> `WAKAPI_TLS_CERT_PATH` | - | Path of SSL server certificate (leave blank to not use HTTPS) |
| `server.tls_key_path` /<br> `WAKAPI_TLS_KEY_PATH` | - | Path of SSL server private key (leave blank to not use HTTPS) |
| `server.base_path` /<br> `WAKAPI_BASE_PATH` | `/` | Web base path (change when running behind a proxy under a sub-path) |
| `security.password_salt` /<br> `WAKAPI_PASSWORD_SALT` | - | Pepper to use for password hashing |
| `security.insecure_cookies` /<br> `WAKAPI_INSECURE_COOKIES` | `false` | Whether or not to allow cookies over HTTP |
| `security.cookie_max_age` /<br> `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 |
| `security.allow_signup` /<br> `WAKAPI_ALLOW_SIGNUP` | `true` | Whether to enable user registration |
| `security.expose_metrics` /<br> `WAKAPI_EXPOSE_METRICS` | `false` | Whether to expose Prometheus metrics under `/api/metrics` |
| `db.host` /<br> `WAKAPI_DB_HOST` | - | Database host |
| `db.port` /<br> `WAKAPI_DB_PORT` | - | Database port |
| `db.user` /<br> `WAKAPI_DB_USER` | - | Database user |
| `db.password` /<br> `WAKAPI_DB_PASSWORD` | - | Database password |
| `db.name` /<br> `WAKAPI_DB_NAME` | `wakapi_db.db` | Database name |
| `db.dialect` /<br> `WAKAPI_DB_TYPE` | `sqlite3` | Database type (one of `sqlite3`, `mysql`, `postgres`, `cockroach`) |
| `db.charset` /<br> `WAKAPI_DB_CHARSET` | `utf8mb4` | Database connection charset (for MySQL only) |
| `db.max_conn` /<br> `WAKAPI_DB_MAX_CONNECTIONS` | `2` | Maximum number of database connections |
| `db.ssl` /<br> `WAKAPI_DB_SSL` | `false` | Whether to use TLS encryption for database connection (Postgres and CockroachDB only) |
| `db.automgirate_fail_silently` /<br> `WAKAPI_DB_AUTOMIGRATE_FAIL_SILENTLY` | `false` | Whether to ignore schema auto-migration failures when starting up |
| `mail.enabled` /<br> `WAKAPI_MAIL_ENABLED` | `true` | Whether to allow Wakapi to send e-mail (e.g. for password resets) |
| `mail.sender` /<br> `WAKAPI_MAIL_SENDER` | `noreply@wakapi.dev` | Default sender address for outgoing mails (ignored for MailWhale) |
| `mail.provider` /<br> `WAKAPI_MAIL_PROVIDER` | `smtp` | Implementation to use for sending mails (one of [`smtp`, `mailwhale`]) |
| `mail.smtp.*` /<br> `WAKAPI_MAIL_SMTP_*` | `-` | Various options to configure SMTP. See [default config](config.default.yml) for details |
| `mail.mailwhale.*` /<br> `WAKAPI_MAIL_MAILWHALE_*` | `-` | Various options to configure [MailWhale](https://mailwhale.dev) sending service. See [default config](config.default.yml) for details |
| `sentry.dsn` /<br> `WAKAPI_SENTRY_DSN` | | DSN for to integrate [Sentry](https://sentry.io) for error logging and tracing (leave empty to disable) |
| `sentry.enable_tracing` /<br> `WAKAPI_SENTRY_TRACING` | `false` | Whether to enable Sentry request tracing |
| `sentry.sample_rate` /<br> `WAKAPI_SENTRY_SAMPLE_RATE` | `0.75` | Probability of tracing a request in Sentry |
| `sentry.sample_rate_heartbeats` /<br> `WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS`| `0.1` | Probability of tracing a heartbeats request in Sentry |
### Supported databases
Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of different relational databases is supported.
@ -186,9 +173,6 @@ Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of differ
* [Postgres](https://hub.docker.com/_/postgres) (_open-source as well_)
* [CockroachDB](https://www.cockroachlabs.com/docs/stable/install-cockroachdb-linux.html) (_cloud-native, distributed, Postgres-compatible API_)
### Client-side proxy (`optional`)
See the [advanced setup instructions](docs/advanced_setup.md).
## 🔧 API Endpoints
See our [Swagger API Documentation](https://wakapi.dev/swagger-ui).
@ -358,7 +342,7 @@ All data is cached locally on your machine and sent in batches once you're onlin
<details>
<summary><b>How did Wakapi come about?</b></summary>
Wakapi was started when I was a student, who wanted to track detailed statistics about my coding time. Although I'm a big fan of WakaTime I didn't want to pay <a href="https://wakatime.com/pricing)">9 $ a month</a> back then. Luckily, most parts of WakaTime are open source!
Wakapi was started when I was a student, who wanted to track detailed statistics about my coding time. Although I'm a big fan of WakaTime I didn't want to pay <a href="https://wakatime.com/pricing">$9 a month</a> back then. Luckily, most parts of WakaTime are open source!
</details>
<details>

View File

@ -21,6 +21,10 @@ app:
jsx: JSX
svelte: Svelte
# url template for user avatar images (to be used with services like gravatar or dicebear)
# available variable placeholders are: username, username_hash, email, email_hash
avatar_url_template: https://avatars.dicebear.com/api/pixel-art-neutral/{username_hash}.svg
db:
host: # leave blank when using sqlite3
port: # leave blank when using sqlite3

View File

@ -62,14 +62,15 @@ var cFlag = flag.String("config", defaultConfigPath, "config file location")
var env string
type appConfig struct {
AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
ReportTimeWeekly string `yaml:"report_time_weekly" default:"fri,18:00" env:"WAKAPI_REPORT_TIME_WEEKLY"`
ImportBackoffMin int `yaml:"import_backoff_min" default:"5" env:"WAKAPI_IMPORT_BACKOFF_MIN"`
ImportBatchSize int `yaml:"import_batch_size" default:"50" env:"WAKAPI_IMPORT_BATCH_SIZE"`
InactiveDays int `yaml:"inactive_days" default:"7" env:"WAKAPI_INACTIVE_DAYS"`
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
CustomLanguages map[string]string `yaml:"custom_languages"`
Colors map[string]map[string]string `yaml:"-"`
AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
ReportTimeWeekly string `yaml:"report_time_weekly" default:"fri,18:00" env:"WAKAPI_REPORT_TIME_WEEKLY"`
ImportBackoffMin int `yaml:"import_backoff_min" default:"5" env:"WAKAPI_IMPORT_BACKOFF_MIN"`
ImportBatchSize int `yaml:"import_batch_size" default:"50" env:"WAKAPI_IMPORT_BATCH_SIZE"`
InactiveDays int `yaml:"inactive_days" default:"7" env:"WAKAPI_INACTIVE_DAYS"`
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
AvatarURLTemplate string `yaml:"avatar_url_template" default:"https://avatars.dicebear.com/api/pixel-art-neutral/{username_hash}.svg"`
CustomLanguages map[string]string `yaml:"custom_languages"`
Colors map[string]map[string]string `yaml:"-"`
}
type securityConfig struct {
@ -238,6 +239,10 @@ func (c *appConfig) GetWeeklyReportTime() string {
return strings.Split(c.ReportTimeWeekly, ",")[1]
}
func (c *dbConfig) IsSQLite() bool {
return c.Dialect == "sqlite3"
}
func (c *serverConfig) GetPublicUrl() string {
return strings.TrimSuffix(c.PublicUrl, "/")
}
@ -364,6 +369,10 @@ func Load(version string) *Config {
if config.Db.MaxConn <= 0 {
logbuch.Fatal("you must allow at least one database connection")
}
if config.Db.MaxConn > 1 && config.Db.IsSQLite() {
logbuch.Warn("with sqlite, only a single connection is supported") // otherwise 'PRAGMA foreign_keys=ON' would somehow have to be set for every connection in the pool
config.Db.MaxConn = 1
}
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
}

View File

@ -1,11 +1,14 @@
mode: set
github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0
github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0
github.com/muety/wakapi/models/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/heartbeats.go:7.31,9.2 1 0
github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0
github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0
github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0
github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0
github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0
github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
github.com/muety/wakapi/models/summary.go:74.29,76.2 1 1
github.com/muety/wakapi/models/summary.go:78.35,80.2 1 0
github.com/muety/wakapi/models/summary.go:82.37,90.2 7 1
@ -72,21 +75,6 @@ github.com/muety/wakapi/models/summary.go:287.50,291.2 1 1
github.com/muety/wakapi/models/summary.go:293.33,295.2 1 1
github.com/muety/wakapi/models/summary.go:297.43,299.2 1 1
github.com/muety/wakapi/models/summary.go:301.38,303.2 1 1
github.com/muety/wakapi/models/user.go:8.13,10.2 1 1
github.com/muety/wakapi/models/user.go:79.36,80.22 1 1
github.com/muety/wakapi/models/user.go:83.2,84.16 2 1
github.com/muety/wakapi/models/user.go:87.2,87.11 1 1
github.com/muety/wakapi/models/user.go:80.22,82.3 1 1
github.com/muety/wakapi/models/user.go:84.16,86.3 1 0
github.com/muety/wakapi/models/user.go:90.41,93.2 2 1
github.com/muety/wakapi/models/user.go:95.43,98.2 1 0
github.com/muety/wakapi/models/user.go:100.45,103.2 1 0
github.com/muety/wakapi/models/user.go:105.33,110.2 1 0
github.com/muety/wakapi/models/user.go:112.41,114.2 1 0
github.com/muety/wakapi/models/user.go:116.45,118.2 1 0
github.com/muety/wakapi/models/user.go:120.45,122.2 1 0
github.com/muety/wakapi/models/user.go:124.39,126.2 1 0
github.com/muety/wakapi/models/user.go:128.39,131.2 2 0
github.com/muety/wakapi/models/filters.go:17.56,18.16 1 0
github.com/muety/wakapi/models/filters.go:32.2,32.19 1 0
github.com/muety/wakapi/models/filters.go:19.22,20.32 1 0
@ -125,20 +113,10 @@ github.com/muety/wakapi/models/heartbeat.go:68.37,84.2 1 0
github.com/muety/wakapi/models/heartbeat.go:92.41,94.16 2 0
github.com/muety/wakapi/models/heartbeat.go:97.2,98.10 2 0
github.com/muety/wakapi/models/heartbeat.go:94.16,96.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/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.go:19.44,23.2 3 0
github.com/muety/wakapi/models/mail.go:25.44,29.2 3 0
github.com/muety/wakapi/models/mail.go:31.32,44.2 1 0
github.com/muety/wakapi/models/mail.go:46.41,48.2 1 0
github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1
github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0
github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 1
@ -157,13 +135,20 @@ 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/project_label.go:11.39,13.2 1 0
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
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/project_label.go:11.39,13.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/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
@ -179,96 +164,64 @@ 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/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 0
github.com/muety/wakapi/middlewares/authenticate.go:120.2,120.18 1 0
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/models/user.go:8.13,10.2 1 1
github.com/muety/wakapi/models/user.go:79.36,80.22 1 1
github.com/muety/wakapi/models/user.go:83.2,84.16 2 1
github.com/muety/wakapi/models/user.go:87.2,87.11 1 1
github.com/muety/wakapi/models/user.go:80.22,82.3 1 1
github.com/muety/wakapi/models/user.go:84.16,86.3 1 0
github.com/muety/wakapi/models/user.go:90.41,93.2 2 1
github.com/muety/wakapi/models/user.go:95.43,98.2 1 0
github.com/muety/wakapi/models/user.go:100.45,103.2 1 0
github.com/muety/wakapi/models/user.go:105.33,110.2 1 0
github.com/muety/wakapi/models/user.go:112.41,114.2 1 0
github.com/muety/wakapi/models/user.go:116.45,118.2 1 0
github.com/muety/wakapi/models/user.go:120.45,122.2 1 0
github.com/muety/wakapi/models/user.go:124.39,126.2 1 0
github.com/muety/wakapi/models/user.go:128.39,131.2 2 0
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
github.com/muety/wakapi/config/sentry.go:48.79,51.2 2 0
github.com/muety/wakapi/config/sentry.go:53.72,57.2 3 0
github.com/muety/wakapi/config/sentry.go:59.71,63.2 3 0
github.com/muety/wakapi/config/sentry.go:65.71,69.2 3 0
github.com/muety/wakapi/config/sentry.go:71.72,75.2 3 0
github.com/muety/wakapi/config/sentry.go:77.72,81.2 3 0
github.com/muety/wakapi/config/sentry.go:83.67,88.18 4 0
github.com/muety/wakapi/config/sentry.go:100.2,100.28 1 0
github.com/muety/wakapi/config/sentry.go:88.18,89.65 1 0
github.com/muety/wakapi/config/sentry.go:89.65,92.42 3 0
github.com/muety/wakapi/config/sentry.go:95.4,96.10 2 0
github.com/muety/wakapi/config/sentry.go:92.42,94.5 1 0
github.com/muety/wakapi/config/sentry.go:110.50,114.91 1 0
github.com/muety/wakapi/config/sentry.go:114.91,115.29 1 0
github.com/muety/wakapi/config/sentry.go:119.4,122.38 3 0
github.com/muety/wakapi/config/sentry.go:127.4,127.39 1 0
github.com/muety/wakapi/config/sentry.go:130.4,130.69 1 0
github.com/muety/wakapi/config/sentry.go:115.29,117.5 1 0
github.com/muety/wakapi/config/sentry.go:122.38,123.38 1 0
github.com/muety/wakapi/config/sentry.go:123.38,125.6 1 0
github.com/muety/wakapi/config/sentry.go:127.39,129.5 1 0
github.com/muety/wakapi/config/sentry.go:132.79,133.27 1 0
github.com/muety/wakapi/config/sentry.go:140.4,140.16 1 0
github.com/muety/wakapi/config/sentry.go:133.27,134.84 1 0
github.com/muety/wakapi/config/sentry.go:134.84,135.42 1 0
github.com/muety/wakapi/config/sentry.go:135.42,137.7 1 0
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:152.70,154.2 1 0
github.com/muety/wakapi/config/config.go:156.65,158.2 1 0
github.com/muety/wakapi/config/config.go:160.82,170.2 1 0
@ -360,67 +313,46 @@ 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
github.com/muety/wakapi/config/sentry.go:48.79,51.2 2 0
github.com/muety/wakapi/config/sentry.go:53.72,57.2 3 0
github.com/muety/wakapi/config/sentry.go:59.71,63.2 3 0
github.com/muety/wakapi/config/sentry.go:65.71,69.2 3 0
github.com/muety/wakapi/config/sentry.go:71.72,75.2 3 0
github.com/muety/wakapi/config/sentry.go:77.72,81.2 3 0
github.com/muety/wakapi/config/sentry.go:83.67,88.18 4 0
github.com/muety/wakapi/config/sentry.go:100.2,100.28 1 0
github.com/muety/wakapi/config/sentry.go:88.18,89.65 1 0
github.com/muety/wakapi/config/sentry.go:89.65,92.42 3 0
github.com/muety/wakapi/config/sentry.go:95.4,96.10 2 0
github.com/muety/wakapi/config/sentry.go:92.42,94.5 1 0
github.com/muety/wakapi/config/sentry.go:110.50,114.91 1 0
github.com/muety/wakapi/config/sentry.go:114.91,115.29 1 0
github.com/muety/wakapi/config/sentry.go:119.4,122.38 3 0
github.com/muety/wakapi/config/sentry.go:127.4,127.39 1 0
github.com/muety/wakapi/config/sentry.go:130.4,130.69 1 0
github.com/muety/wakapi/config/sentry.go:115.29,117.5 1 0
github.com/muety/wakapi/config/sentry.go:122.38,123.38 1 0
github.com/muety/wakapi/config/sentry.go:123.38,125.6 1 0
github.com/muety/wakapi/config/sentry.go:127.39,129.5 1 0
github.com/muety/wakapi/config/sentry.go:132.79,133.27 1 0
github.com/muety/wakapi/config/sentry.go:140.4,140.16 1 0
github.com/muety/wakapi/config/sentry.go:133.27,134.84 1 0
github.com/muety/wakapi/config/sentry.go:134.84,135.42 1 0
github.com/muety/wakapi/config/sentry.go:135.42,137.7 1 0
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/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/strings.go:8.34,10.2 1 0
github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
github.com/muety/wakapi/utils/date.go:8.43,10.2 1 1
github.com/muety/wakapi/utils/date.go:12.48,14.2 1 0
github.com/muety/wakapi/utils/date.go:16.41,18.21 2 1
github.com/muety/wakapi/utils/date.go:21.2,21.23 1 1
github.com/muety/wakapi/utils/date.go:18.21,20.3 1 0
github.com/muety/wakapi/utils/date.go:24.46,26.2 1 0
github.com/muety/wakapi/utils/date.go:28.51,30.2 1 0
github.com/muety/wakapi/utils/date.go:32.44,35.2 2 1
github.com/muety/wakapi/utils/date.go:37.52,39.2 1 0
github.com/muety/wakapi/utils/date.go:41.45,43.2 1 0
github.com/muety/wakapi/utils/date.go:45.51,47.2 1 0
github.com/muety/wakapi/utils/date.go:49.44,51.2 1 0
github.com/muety/wakapi/utils/date.go:54.42,56.2 1 1
github.com/muety/wakapi/utils/date.go:59.46,61.2 1 0
github.com/muety/wakapi/utils/date.go:64.41,66.21 2 1
github.com/muety/wakapi/utils/date.go:69.2,69.36 1 1
github.com/muety/wakapi/utils/date.go:66.21,68.3 1 1
github.com/muety/wakapi/utils/date.go:73.63,75.2 1 0
github.com/muety/wakapi/utils/date.go:78.62,84.2 5 0
github.com/muety/wakapi/utils/date.go:87.67,90.33 2 1
github.com/muety/wakapi/utils/date.go:99.2,99.18 1 1
github.com/muety/wakapi/utils/date.go:90.33,92.19 2 1
github.com/muety/wakapi/utils/date.go:95.3,96.10 2 1
github.com/muety/wakapi/utils/date.go:92.19,94.4 1 1
github.com/muety/wakapi/utils/date.go:102.50,108.2 5 0
github.com/muety/wakapi/utils/date.go:111.79,114.36 3 1
github.com/muety/wakapi/utils/date.go:118.2,118.21 1 1
github.com/muety/wakapi/utils/date.go:122.2,122.21 1 1
github.com/muety/wakapi/utils/date.go:126.2,126.13 1 1
github.com/muety/wakapi/utils/date.go:114.36,117.3 2 0
github.com/muety/wakapi/utils/date.go:118.21,121.3 2 1
github.com/muety/wakapi/utils/date.go:122.21,125.3 2 1
github.com/muety/wakapi/utils/filesystem.go:14.68,16.16 2 0
github.com/muety/wakapi/utils/filesystem.go:20.2,21.15 2 0
github.com/muety/wakapi/utils/filesystem.go:33.2,33.15 1 0
github.com/muety/wakapi/utils/filesystem.go:16.16,18.3 1 0
github.com/muety/wakapi/utils/filesystem.go:21.15,23.47 2 0
github.com/muety/wakapi/utils/filesystem.go:23.47,25.23 2 0
github.com/muety/wakapi/utils/filesystem.go:29.4,29.19 1 0
github.com/muety/wakapi/utils/filesystem.go:25.23,27.5 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
@ -457,6 +389,32 @@ github.com/muety/wakapi/utils/summary.go:92.17,94.4 1 0
github.com/muety/wakapi/utils/summary.go:107.48,111.51 2 0
github.com/muety/wakapi/utils/summary.go:114.2,114.12 1 0
github.com/muety/wakapi/utils/summary.go:111.51,113.3 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/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/set.go:3.51,5.26 2 0
github.com/muety/wakapi/utils/set.go:8.2,8.12 1 0
github.com/muety/wakapi/utils/set.go:5.26,7.3 1 0
github.com/muety/wakapi/utils/set.go:11.49,13.21 2 0
github.com/muety/wakapi/utils/set.go:16.2,16.14 1 0
github.com/muety/wakapi/utils/set.go:13.21,15.3 1 0
github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
github.com/muety/wakapi/utils/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
@ -476,22 +434,6 @@ github.com/muety/wakapi/utils/template.go:39.51,40.12 1 0
github.com/muety/wakapi/utils/template.go:44.17,46.4 1 0
github.com/muety/wakapi/utils/template.go:48.17,50.4 1 0
github.com/muety/wakapi/utils/template.go:55.17,57.4 1 0
github.com/muety/wakapi/utils/filesystem.go:14.68,16.16 2 0
github.com/muety/wakapi/utils/filesystem.go:20.2,21.15 2 0
github.com/muety/wakapi/utils/filesystem.go:33.2,33.15 1 0
github.com/muety/wakapi/utils/filesystem.go:16.16,18.3 1 0
github.com/muety/wakapi/utils/filesystem.go:21.15,23.47 2 0
github.com/muety/wakapi/utils/filesystem.go:23.47,25.23 2 0
github.com/muety/wakapi/utils/filesystem.go:29.4,29.19 1 0
github.com/muety/wakapi/utils/filesystem.go:25.23,27.5 1 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/set.go:3.51,5.26 2 0
github.com/muety/wakapi/utils/set.go:8.2,8.12 1 0
github.com/muety/wakapi/utils/set.go:5.26,7.3 1 0
github.com/muety/wakapi/utils/set.go:11.49,13.21 2 0
github.com/muety/wakapi/utils/set.go:16.2,16.14 1 0
github.com/muety/wakapi/utils/set.go:13.21,15.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
@ -518,61 +460,96 @@ 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/date.go:8.43,10.2 1 1
github.com/muety/wakapi/utils/date.go:12.48,14.2 1 0
github.com/muety/wakapi/utils/date.go:16.41,18.21 2 1
github.com/muety/wakapi/utils/date.go:21.2,21.23 1 1
github.com/muety/wakapi/utils/date.go:18.21,20.3 1 0
github.com/muety/wakapi/utils/date.go:24.46,26.2 1 0
github.com/muety/wakapi/utils/date.go:28.51,30.2 1 0
github.com/muety/wakapi/utils/date.go:32.44,35.2 2 1
github.com/muety/wakapi/utils/date.go:37.52,39.2 1 0
github.com/muety/wakapi/utils/date.go:41.45,43.2 1 0
github.com/muety/wakapi/utils/date.go:45.51,47.2 1 0
github.com/muety/wakapi/utils/date.go:49.44,51.2 1 0
github.com/muety/wakapi/utils/date.go:54.42,56.2 1 1
github.com/muety/wakapi/utils/date.go:59.46,61.2 1 0
github.com/muety/wakapi/utils/date.go:64.41,66.21 2 1
github.com/muety/wakapi/utils/date.go:69.2,69.36 1 1
github.com/muety/wakapi/utils/date.go:66.21,68.3 1 1
github.com/muety/wakapi/utils/date.go:73.63,75.2 1 0
github.com/muety/wakapi/utils/date.go:78.62,84.2 5 0
github.com/muety/wakapi/utils/date.go:87.67,90.33 2 1
github.com/muety/wakapi/utils/date.go:99.2,99.18 1 1
github.com/muety/wakapi/utils/date.go:90.33,92.19 2 1
github.com/muety/wakapi/utils/date.go:95.3,96.10 2 1
github.com/muety/wakapi/utils/date.go:92.19,94.4 1 1
github.com/muety/wakapi/utils/date.go:102.50,108.2 5 0
github.com/muety/wakapi/utils/date.go:111.79,114.36 3 1
github.com/muety/wakapi/utils/date.go:118.2,118.21 1 1
github.com/muety/wakapi/utils/date.go:122.2,122.21 1 1
github.com/muety/wakapi/utils/date.go:126.2,126.13 1 1
github.com/muety/wakapi/utils/date.go:114.36,117.3 2 0
github.com/muety/wakapi/utils/date.go:118.21,121.3 2 1
github.com/muety/wakapi/utils/date.go:122.21,125.3 2 1
github.com/muety/wakapi/services/project_label.go:20.111,27.2 1 0
github.com/muety/wakapi/services/project_label.go:29.80,31.2 1 0
github.com/muety/wakapi/services/project_label.go:33.90,34.51 1 0
github.com/muety/wakapi/services/project_label.go:38.2,39.16 2 0
github.com/muety/wakapi/services/project_label.go:42.2,43.20 2 0
github.com/muety/wakapi/services/project_label.go:34.51,36.3 1 0
github.com/muety/wakapi/services/project_label.go:39.16,41.3 1 0
github.com/muety/wakapi/services/project_label.go:46.108,49.16 3 0
github.com/muety/wakapi/services/project_label.go:53.2,53.31 1 0
github.com/muety/wakapi/services/project_label.go:61.2,61.20 1 0
github.com/muety/wakapi/services/project_label.go:49.16,51.3 1 0
github.com/muety/wakapi/services/project_label.go:53.31,54.41 1 0
github.com/muety/wakapi/services/project_label.go:54.41,56.4 1 0
github.com/muety/wakapi/services/project_label.go:56.9,58.4 1 0
github.com/muety/wakapi/services/project_label.go:64.98,66.16 2 0
github.com/muety/wakapi/services/project_label.go:70.2,72.20 3 0
github.com/muety/wakapi/services/project_label.go:66.16,68.3 1 0
github.com/muety/wakapi/services/project_label.go:75.74,76.24 1 0
github.com/muety/wakapi/services/project_label.go:79.2,82.12 4 0
github.com/muety/wakapi/services/project_label.go:76.24,78.3 1 0
github.com/muety/wakapi/services/project_label.go:85.89,87.14 2 0
github.com/muety/wakapi/services/project_label.go:90.2,93.4 1 0
github.com/muety/wakapi/services/project_label.go:87.14,89.3 1 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/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/services/summary.go:32.191,44.33 3 1
github.com/muety/wakapi/services/summary.go:55.2,55.12 1 1
github.com/muety/wakapi/services/summary.go:44.33,45.31 1 1
@ -685,76 +662,67 @@ github.com/muety/wakapi/services/summary.go:413.59,415.2 1 1
github.com/muety/wakapi/services/summary.go:417.63,418.37 1 0
github.com/muety/wakapi/services/summary.go:418.37,419.36 1 0
github.com/muety/wakapi/services/summary.go:419.36,421.4 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/heartbeat.go:25.141,39.33 3 0
github.com/muety/wakapi/services/heartbeat.go:47.2,47.12 1 0
github.com/muety/wakapi/services/heartbeat.go:39.33,40.31 1 0
github.com/muety/wakapi/services/heartbeat.go:40.31,44.4 3 0
github.com/muety/wakapi/services/heartbeat.go:50.72,53.2 2 0
github.com/muety/wakapi/services/heartbeat.go:55.80,60.32 3 0
github.com/muety/wakapi/services/heartbeat.go:68.2,69.16 2 0
github.com/muety/wakapi/services/heartbeat.go:72.2,72.12 1 0
github.com/muety/wakapi/services/heartbeat.go:60.32,61.36 1 0
github.com/muety/wakapi/services/heartbeat.go:65.3,65.43 1 0
github.com/muety/wakapi/services/heartbeat.go:61.36,64.4 2 0
github.com/muety/wakapi/services/heartbeat.go:69.16,71.3 1 0
github.com/muety/wakapi/services/heartbeat.go:75.53,77.8 2 0
github.com/muety/wakapi/services/heartbeat.go:80.2,81.16 2 0
github.com/muety/wakapi/services/heartbeat.go:84.2,84.19 1 0
github.com/muety/wakapi/services/heartbeat.go:77.8,79.3 1 0
github.com/muety/wakapi/services/heartbeat.go:81.16,83.3 1 0
github.com/muety/wakapi/services/heartbeat.go:87.76,90.8 3 0
github.com/muety/wakapi/services/heartbeat.go:93.2,94.16 2 0
github.com/muety/wakapi/services/heartbeat.go:97.2,97.19 1 0
github.com/muety/wakapi/services/heartbeat.go:90.8,92.3 1 0
github.com/muety/wakapi/services/heartbeat.go:94.16,96.3 1 0
github.com/muety/wakapi/services/heartbeat.go:100.96,104.26 3 0
github.com/muety/wakapi/services/heartbeat.go:114.2,115.16 2 0
github.com/muety/wakapi/services/heartbeat.go:119.2,119.28 1 0
github.com/muety/wakapi/services/heartbeat.go:125.2,125.24 1 0
github.com/muety/wakapi/services/heartbeat.go:104.26,107.9 3 0
github.com/muety/wakapi/services/heartbeat.go:107.9,109.4 1 0
github.com/muety/wakapi/services/heartbeat.go:109.9,111.4 1 0
github.com/muety/wakapi/services/heartbeat.go:115.16,117.3 1 0
github.com/muety/wakapi/services/heartbeat.go:119.28,123.3 3 0
github.com/muety/wakapi/services/heartbeat.go:128.111,130.16 2 0
github.com/muety/wakapi/services/heartbeat.go:133.2,133.43 1 0
github.com/muety/wakapi/services/heartbeat.go:130.16,132.3 1 0
github.com/muety/wakapi/services/heartbeat.go:136.92,138.2 1 0
github.com/muety/wakapi/services/heartbeat.go:140.116,142.2 1 0
github.com/muety/wakapi/services/heartbeat.go:144.78,146.2 1 0
github.com/muety/wakapi/services/heartbeat.go:148.104,150.55 2 0
github.com/muety/wakapi/services/heartbeat.go:154.2,155.16 2 0
github.com/muety/wakapi/services/heartbeat.go:159.2,160.28 2 0
github.com/muety/wakapi/services/heartbeat.go:166.2,167.22 2 0
github.com/muety/wakapi/services/heartbeat.go:150.55,152.3 1 0
github.com/muety/wakapi/services/heartbeat.go:155.16,157.3 1 0
github.com/muety/wakapi/services/heartbeat.go:160.28,161.33 1 0
github.com/muety/wakapi/services/heartbeat.go:161.33,163.4 1 0
github.com/muety/wakapi/services/heartbeat.go:170.62,172.2 1 0
github.com/muety/wakapi/services/heartbeat.go:174.116,176.16 2 0
github.com/muety/wakapi/services/heartbeat.go:180.2,180.28 1 0
github.com/muety/wakapi/services/heartbeat.go:184.2,184.24 1 0
github.com/muety/wakapi/services/heartbeat.go:176.16,178.3 1 0
github.com/muety/wakapi/services/heartbeat.go:180.28,182.3 1 0
github.com/muety/wakapi/services/heartbeat.go:187.96,189.2 1 0
github.com/muety/wakapi/services/heartbeat.go:191.107,193.56 2 0
github.com/muety/wakapi/services/heartbeat.go:193.56,194.58 1 0
github.com/muety/wakapi/services/heartbeat.go:194.58,198.4 1 0
github.com/muety/wakapi/services/heartbeat.go:202.85,208.2 5 0
github.com/muety/wakapi/services/heartbeat.go:210.74,211.32 1 0
github.com/muety/wakapi/services/heartbeat.go:211.32,216.3 1 0
github.com/muety/wakapi/services/heartbeat.go:219.72,221.2 1 0
github.com/muety/wakapi/services/heartbeat.go:223.58,225.2 1 0
github.com/muety/wakapi/services/heartbeat.go:227.60,229.2 1 0
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0
github.com/muety/wakapi/services/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
github.com/muety/wakapi/services/heartbeat.go:41.31,45.4 3 0
github.com/muety/wakapi/services/heartbeat.go:51.72,54.2 2 0
github.com/muety/wakapi/services/heartbeat.go:56.80,61.32 3 0
github.com/muety/wakapi/services/heartbeat.go:69.2,70.16 2 0
github.com/muety/wakapi/services/heartbeat.go:73.2,73.12 1 0
github.com/muety/wakapi/services/heartbeat.go:61.32,62.36 1 0
github.com/muety/wakapi/services/heartbeat.go:66.3,66.46 1 0
github.com/muety/wakapi/services/heartbeat.go:62.36,65.4 2 0
github.com/muety/wakapi/services/heartbeat.go:70.16,72.3 1 0
github.com/muety/wakapi/services/heartbeat.go:76.53,78.8 2 0
github.com/muety/wakapi/services/heartbeat.go:81.2,82.16 2 0
github.com/muety/wakapi/services/heartbeat.go:85.2,85.19 1 0
github.com/muety/wakapi/services/heartbeat.go:78.8,80.3 1 0
github.com/muety/wakapi/services/heartbeat.go:82.16,84.3 1 0
github.com/muety/wakapi/services/heartbeat.go:88.76,91.8 3 0
github.com/muety/wakapi/services/heartbeat.go:94.2,95.16 2 0
github.com/muety/wakapi/services/heartbeat.go:98.2,98.19 1 0
github.com/muety/wakapi/services/heartbeat.go:91.8,93.3 1 0
github.com/muety/wakapi/services/heartbeat.go:95.16,97.3 1 0
github.com/muety/wakapi/services/heartbeat.go:101.96,105.26 3 0
github.com/muety/wakapi/services/heartbeat.go:115.2,116.16 2 0
github.com/muety/wakapi/services/heartbeat.go:120.2,120.28 1 0
github.com/muety/wakapi/services/heartbeat.go:126.2,126.24 1 0
github.com/muety/wakapi/services/heartbeat.go:105.26,108.9 3 0
github.com/muety/wakapi/services/heartbeat.go:108.9,110.4 1 0
github.com/muety/wakapi/services/heartbeat.go:110.9,112.4 1 0
github.com/muety/wakapi/services/heartbeat.go:116.16,118.3 1 0
github.com/muety/wakapi/services/heartbeat.go:120.28,124.3 3 0
github.com/muety/wakapi/services/heartbeat.go:129.111,131.16 2 0
github.com/muety/wakapi/services/heartbeat.go:134.2,134.43 1 0
github.com/muety/wakapi/services/heartbeat.go:131.16,133.3 1 0
github.com/muety/wakapi/services/heartbeat.go:137.92,139.2 1 0
github.com/muety/wakapi/services/heartbeat.go:141.116,143.2 1 0
github.com/muety/wakapi/services/heartbeat.go:145.78,147.2 1 0
github.com/muety/wakapi/services/heartbeat.go:149.104,151.54 2 0
github.com/muety/wakapi/services/heartbeat.go:157.2,158.16 2 0
github.com/muety/wakapi/services/heartbeat.go:162.2,163.28 2 0
github.com/muety/wakapi/services/heartbeat.go:169.2,170.22 2 0
github.com/muety/wakapi/services/heartbeat.go:151.54,155.3 3 0
github.com/muety/wakapi/services/heartbeat.go:158.16,160.3 1 0
github.com/muety/wakapi/services/heartbeat.go:163.28,164.33 1 0
github.com/muety/wakapi/services/heartbeat.go:164.33,166.4 1 0
github.com/muety/wakapi/services/heartbeat.go:173.62,175.2 1 0
github.com/muety/wakapi/services/heartbeat.go:177.116,179.16 2 0
github.com/muety/wakapi/services/heartbeat.go:183.2,183.28 1 0
github.com/muety/wakapi/services/heartbeat.go:187.2,187.24 1 0
github.com/muety/wakapi/services/heartbeat.go:179.16,181.3 1 0
github.com/muety/wakapi/services/heartbeat.go:183.28,185.3 1 0
github.com/muety/wakapi/services/heartbeat.go:190.96,192.2 1 0
github.com/muety/wakapi/services/heartbeat.go:194.107,196.55 2 0
github.com/muety/wakapi/services/heartbeat.go:196.55,202.41 4 0
github.com/muety/wakapi/services/heartbeat.go:202.41,207.4 2 0
github.com/muety/wakapi/services/heartbeat.go:211.85,217.2 5 0
github.com/muety/wakapi/services/heartbeat.go:219.74,220.32 1 0
github.com/muety/wakapi/services/heartbeat.go:220.32,225.3 1 0
github.com/muety/wakapi/services/heartbeat.go:228.72,230.2 1 0
github.com/muety/wakapi/services/heartbeat.go:232.58,234.2 1 0
github.com/muety/wakapi/services/heartbeat.go:236.60,238.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
@ -774,6 +742,85 @@ 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/project_label.go:20.111,27.2 1 0
github.com/muety/wakapi/services/project_label.go:29.80,31.2 1 0
github.com/muety/wakapi/services/project_label.go:33.90,34.51 1 0
github.com/muety/wakapi/services/project_label.go:38.2,39.16 2 0
github.com/muety/wakapi/services/project_label.go:42.2,43.20 2 0
github.com/muety/wakapi/services/project_label.go:34.51,36.3 1 0
github.com/muety/wakapi/services/project_label.go:39.16,41.3 1 0
github.com/muety/wakapi/services/project_label.go:46.108,49.16 3 0
github.com/muety/wakapi/services/project_label.go:53.2,53.31 1 0
github.com/muety/wakapi/services/project_label.go:60.2,60.29 1 0
github.com/muety/wakapi/services/project_label.go:49.16,51.3 1 0
github.com/muety/wakapi/services/project_label.go:53.31,54.50 1 0
github.com/muety/wakapi/services/project_label.go:54.50,56.4 1 0
github.com/muety/wakapi/services/project_label.go:56.9,58.4 1 0
github.com/muety/wakapi/services/project_label.go:63.116,66.16 3 0
github.com/muety/wakapi/services/project_label.go:70.2,70.31 1 0
github.com/muety/wakapi/services/project_label.go:77.2,77.29 1 0
github.com/muety/wakapi/services/project_label.go:66.16,68.3 1 0
github.com/muety/wakapi/services/project_label.go:70.31,71.45 1 0
github.com/muety/wakapi/services/project_label.go:71.45,73.4 1 0
github.com/muety/wakapi/services/project_label.go:73.9,75.4 1 0
github.com/muety/wakapi/services/project_label.go:80.98,82.16 2 0
github.com/muety/wakapi/services/project_label.go:86.2,88.20 3 0
github.com/muety/wakapi/services/project_label.go:82.16,84.3 1 0
github.com/muety/wakapi/services/project_label.go:91.74,92.24 1 0
github.com/muety/wakapi/services/project_label.go:95.2,98.12 4 0
github.com/muety/wakapi/services/project_label.go:92.24,94.3 1 0
github.com/muety/wakapi/services/project_label.go:101.89,103.14 2 0
github.com/muety/wakapi/services/project_label.go:106.2,109.4 1 0
github.com/muety/wakapi/services/project_label.go:103.14,105.3 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.151 1 0
github.com/muety/wakapi/services/misc.go:100.151,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
github.com/muety/wakapi/services/report.go:45.31,47.4 1 0
github.com/muety/wakapi/services/report.go:53.38,57.16 3 0
github.com/muety/wakapi/services/report.go:61.2,62.26 2 0
github.com/muety/wakapi/services/report.go:57.16,59.3 1 0
github.com/muety/wakapi/services/report.go:62.26,64.3 1 0
github.com/muety/wakapi/services/report.go:69.61,74.22 3 0
github.com/muety/wakapi/services/report.go:80.2,80.61 1 0
github.com/muety/wakapi/services/report.go:94.2,94.24 1 0
github.com/muety/wakapi/services/report.go:74.22,77.3 2 0
github.com/muety/wakapi/services/report.go:80.61,89.47 3 0
github.com/muety/wakapi/services/report.go:89.47,91.4 1 0
github.com/muety/wakapi/services/report.go:97.80,98.22 1 0
github.com/muety/wakapi/services/report.go:102.2,102.29 1 0
github.com/muety/wakapi/services/report.go:107.2,111.16 4 0
github.com/muety/wakapi/services/report.go:116.2,123.65 2 0
github.com/muety/wakapi/services/report.go:128.2,129.12 2 0
github.com/muety/wakapi/services/report.go:98.22,100.3 1 0
github.com/muety/wakapi/services/report.go:102.29,105.3 2 0
github.com/muety/wakapi/services/report.go:111.16,114.3 2 0
github.com/muety/wakapi/services/report.go:123.65,126.3 2 0
github.com/muety/wakapi/services/report.go:132.63,133.41 1 0
github.com/muety/wakapi/services/report.go:140.2,140.12 1 0
github.com/muety/wakapi/services/report.go:133.41,134.30 1 0
github.com/muety/wakapi/services/report.go:134.30,135.16 1 0
github.com/muety/wakapi/services/report.go:135.16,137.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
@ -905,52 +952,12 @@ github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/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.151 1 0
github.com/muety/wakapi/services/misc.go:100.151,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
github.com/muety/wakapi/services/report.go:45.31,47.4 1 0
github.com/muety/wakapi/services/report.go:53.38,57.16 3 0
github.com/muety/wakapi/services/report.go:61.2,62.26 2 0
github.com/muety/wakapi/services/report.go:57.16,59.3 1 0
github.com/muety/wakapi/services/report.go:62.26,64.3 1 0
github.com/muety/wakapi/services/report.go:69.61,74.22 3 0
github.com/muety/wakapi/services/report.go:80.2,80.61 1 0
github.com/muety/wakapi/services/report.go:94.2,94.24 1 0
github.com/muety/wakapi/services/report.go:74.22,77.3 2 0
github.com/muety/wakapi/services/report.go:80.61,89.47 3 0
github.com/muety/wakapi/services/report.go:89.47,91.4 1 0
github.com/muety/wakapi/services/report.go:97.80,98.22 1 0
github.com/muety/wakapi/services/report.go:102.2,102.29 1 0
github.com/muety/wakapi/services/report.go:107.2,111.16 4 0
github.com/muety/wakapi/services/report.go:116.2,123.65 2 0
github.com/muety/wakapi/services/report.go:128.2,129.12 2 0
github.com/muety/wakapi/services/report.go:98.22,100.3 1 0
github.com/muety/wakapi/services/report.go:102.29,105.3 2 0
github.com/muety/wakapi/services/report.go:111.16,114.3 2 0
github.com/muety/wakapi/services/report.go:123.65,126.3 2 0
github.com/muety/wakapi/services/report.go:132.63,133.41 1 0
github.com/muety/wakapi/services/report.go:140.2,140.12 1 0
github.com/muety/wakapi/services/report.go:133.41,134.30 1 0
github.com/muety/wakapi/services/report.go:134.30,135.16 1 0
github.com/muety/wakapi/services/report.go:135.16,137.5 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/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

View File

@ -118,9 +118,12 @@ func main() {
// Connect to database
var err error
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
if config.Db.Dialect == "sqlite3" {
db.Raw("PRAGMA foreign_keys = ON;")
db.DisableForeignKeyConstraintWhenMigrating = true
if config.Db.IsSQLite() {
db.Exec("PRAGMA foreign_keys = ON;")
if !utils.IsCleanDB(db) && !utils.HasConstraints(db) {
db.DisableForeignKeyConstraintWhenMigrating = true
logbuch.Warn("using existing sqlite database without foreign key constraints and no ability to migrate, functionality may be limited")
}
}
if config.IsDev() {

View File

@ -24,6 +24,11 @@ func (p *ProjectLabelServiceMock) GetByUserGrouped(s string) (map[string][]*mode
return args.Get(0).(map[string][]*models.ProjectLabel), args.Error(1)
}
func (p *ProjectLabelServiceMock) GetByUserGroupedInverted(s string) (map[string][]*models.ProjectLabel, error) {
args := p.Called(s)
return args.Get(0).(map[string][]*models.ProjectLabel), args.Error(1)
}
func (p *ProjectLabelServiceMock) Create(l *models.ProjectLabel) (*models.ProjectLabel, error) {
args := p.Called(l)
return args.Get(0).(*models.ProjectLabel), args.Error(1)

View File

@ -53,6 +53,7 @@ type SummaryViewModel struct {
*Summary
*SummaryParams
User *User
AvatarURL string
LanguageColors map[string]string
EditorColors map[string]string
OSColors map[string]string

View File

@ -1,7 +1,10 @@
package models
import (
"crypto/md5"
"fmt"
"regexp"
"strings"
"time"
)
@ -92,6 +95,18 @@ func (u *User) TZOffset() time.Duration {
return time.Duration(offset * int(time.Second))
}
func (u *User) AvatarURL(urlTemplate string) string {
urlTemplate = strings.ReplaceAll(urlTemplate, "{username}", u.ID)
urlTemplate = strings.ReplaceAll(urlTemplate, "{email}", u.Email)
if strings.Contains(urlTemplate, "{username_hash}") {
urlTemplate = strings.ReplaceAll(urlTemplate, "{username_hash}", fmt.Sprintf("%x", md5.Sum([]byte(u.ID))))
}
if strings.Contains(urlTemplate, "{email_hash}") {
urlTemplate = strings.ReplaceAll(urlTemplate, "{email_hash}", fmt.Sprintf("%x", md5.Sum([]byte(u.Email))))
}
return urlTemplate
}
func (c *CredentialsReset) IsValid() bool {
return ValidatePassword(c.PasswordNew) &&
c.PasswordNew == c.PasswordRepeat

View File

@ -9,11 +9,12 @@ import (
func TestUser_TZ(t *testing.T) {
sut1, sut2 := &User{Location: ""}, &User{Location: "America/Los_Angeles"}
pst, _ := time.LoadLocation("America/Los_Angeles")
_, offset := time.Now().Zone()
_, offset1 := time.Now().Zone()
_, offset2 := time.Now().In(pst).Zone()
assert.Equal(t, time.Local, sut1.TZ())
assert.Equal(t, pst, sut2.TZ())
assert.InDelta(t, time.Duration(offset*int(time.Second)), sut1.TZOffset(), float64(1*time.Second))
assert.InDelta(t, time.Duration(-7*int(time.Hour)), sut2.TZOffset(), float64(1*time.Second))
assert.InDelta(t, time.Duration(offset1*int(time.Second)), sut1.TZOffset(), float64(1*time.Second))
assert.InDelta(t, time.Duration(offset2*int(time.Second)), sut2.TZOffset(), float64(1*time.Second))
}

View File

@ -41,7 +41,7 @@ func (h *DiagnosticsApiHandler) RegisterRoutes(router *mux.Router) {
// @Param diagnostics body models.Diagnostics true "A single diagnostics object sent by WakaTime CLI"
// @Security ApiKeyAuth
// @Success 201
// @Router /plugins/errors [post]
// @Router /api/plugins/errors [post]
func (h *DiagnosticsApiHandler) Post(w http.ResponseWriter, r *http.Request) {
var diagnostics models.Diagnostics

View File

@ -25,7 +25,7 @@ func (h *HealthApiHandler) RegisterRoutes(router *mux.Router) {
// @Tags misc
// @Produce plain
// @Success 200 {string} string
// @Router /health [get]
// @Router /api/health [get]
func (h *HealthApiHandler) Get(w http.ResponseWriter, r *http.Request) {
var dbStatus int
if sqlDb, err := h.db.DB(); err == nil {

View File

@ -60,7 +60,7 @@ func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
// @Security ApiKeyAuth
// @Success 201
// @Router /heartbeat [post]
// @Router /api/heartbeat [post]
func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
if err != nil {
@ -182,7 +182,7 @@ func constructSuccessResponse(n int) *heartbeatResponseVm {
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
// @Security ApiKeyAuth
// @Success 201
// @Router /v1/users/{user}/heartbeats [post]
// @Router /api/v1/users/{user}/heartbeats [post]
func (h *HeartbeatApiHandler) postAlias1() {}
// @Summary Push a new heartbeat
@ -192,7 +192,7 @@ func (h *HeartbeatApiHandler) postAlias1() {}
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
// @Security ApiKeyAuth
// @Success 201
// @Router /compat/wakatime/v1/users/{user}/heartbeats [post]
// @Router /api/compat/wakatime/v1/users/{user}/heartbeats [post]
func (h *HeartbeatApiHandler) postAlias2() {}
// @Summary Push a new heartbeat
@ -202,7 +202,7 @@ func (h *HeartbeatApiHandler) postAlias2() {}
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
// @Security ApiKeyAuth
// @Success 201
// @Router /users/{user}/heartbeats [post]
// @Router /api/users/{user}/heartbeats [post]
func (h *HeartbeatApiHandler) postAlias3() {}
// @Summary Push new heartbeats
@ -212,7 +212,7 @@ func (h *HeartbeatApiHandler) postAlias3() {}
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
// @Security ApiKeyAuth
// @Success 201
// @Router /heartbeats [post]
// @Router /api/heartbeats [post]
func (h *HeartbeatApiHandler) postAlias4() {}
// @Summary Push new heartbeats
@ -222,7 +222,7 @@ func (h *HeartbeatApiHandler) postAlias4() {}
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
// @Security ApiKeyAuth
// @Success 201
// @Router /v1/users/{user}/heartbeats.bulk [post]
// @Router /api/v1/users/{user}/heartbeats.bulk [post]
func (h *HeartbeatApiHandler) postAlias5() {}
// @Summary Push new heartbeats
@ -232,7 +232,7 @@ func (h *HeartbeatApiHandler) postAlias5() {}
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
// @Security ApiKeyAuth
// @Success 201
// @Router /compat/wakatime/v1/users/{user}/heartbeats.bulk [post]
// @Router /api/compat/wakatime/v1/users/{user}/heartbeats.bulk [post]
func (h *HeartbeatApiHandler) postAlias6() {}
// @Summary Push new heartbeats
@ -242,5 +242,5 @@ func (h *HeartbeatApiHandler) postAlias6() {}
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
// @Security ApiKeyAuth
// @Success 201
// @Router /users/{user}/heartbeats.bulk [post]
// @Router /api/users/{user}/heartbeats.bulk [post]
func (h *HeartbeatApiHandler) postAlias7() {}

View File

@ -12,6 +12,7 @@ import (
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
"net/http"
"runtime"
"sort"
"time"
)
@ -34,6 +35,10 @@ const (
DescAdminUserHeartbeats = "Total number of tracked heartbeats by user (all time)."
DescAdminTotalUsers = "Total number of registered users."
DescAdminActiveUsers = "Number of active users."
DescMemAllocTotal = "Total number of bytes allocated for heap"
DescMemSysTotal = "Total number of bytes obtained from the OS"
DescGoroutines = "Total number of running goroutines"
)
type MetricsHandler struct {
@ -208,6 +213,31 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
})
}
// Runtime metrics
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
metrics = append(metrics, &mm.CounterMetric{
Name: MetricsPrefix + "_goroutines_total",
Desc: DescGoroutines,
Value: runtime.NumGoroutine(),
Labels: []mm.Label{},
})
metrics = append(metrics, &mm.CounterMetric{
Name: MetricsPrefix + "_mem_alloc_total",
Desc: DescMemAllocTotal,
Value: int(memStats.Alloc),
Labels: []mm.Label{},
})
metrics = append(metrics, &mm.CounterMetric{
Name: MetricsPrefix + "_mem_sys_total",
Desc: DescMemSysTotal,
Value: int(memStats.Sys),
Labels: []mm.Label{},
})
return &metrics, nil
}

View File

@ -42,7 +42,7 @@ func (h *SummaryApiHandler) RegisterRoutes(router *mux.Router) {
// @Param recompute query bool false "Whether to recompute the summary from raw heartbeat or use cache"
// @Security ApiKeyAuth
// @Success 200 {object} models.Summary
// @Router /summary [get]
// @Router /api/summary [get]
func (h *SummaryApiHandler) Get(w http.ResponseWriter, r *http.Request) {
summary, err, status := su.LoadUserSummary(h.summarySrvc, r)
if err != nil {

View File

@ -50,7 +50,7 @@ func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
// @Param interval path string true "Interval to aggregate data for" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
// @Param filter path string true "Filter to apply (e.g. 'project:wakapi' or 'language:Go')"
// @Success 200 {object} v1.BadgeData
// @Router /compat/shields/v1/{user}/{interval}/{filter} [get]
// @Router /api/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)

View File

@ -44,7 +44,7 @@ func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) {
// @Param user path string true "User ID to fetch data for (or 'current')"
// @Security ApiKeyAuth
// @Success 200 {object} v1.AllTimeViewModel
// @Router /compat/wakatime/v1/users/{user}/all_time_since_today [get]
// @Router /api/compat/wakatime/v1/users/{user}/all_time_since_today [get]
func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
values, _ := url.ParseQuery(r.URL.RawQuery)

View File

@ -44,7 +44,7 @@ func (h *ProjectsHandler) RegisterRoutes(router *mux.Router) {
// @Param q query string true "Query to filter projects by"
// @Security ApiKeyAuth
// @Success 200 {object} v1.ProjectsViewModel
// @Router /compat/wakatime/v1/users/{user}/projects [get]
// @Router /api/compat/wakatime/v1/users/{user}/projects [get]
func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) {
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
if err != nil {

View File

@ -50,7 +50,7 @@ func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
// @Param range path string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
// @Security ApiKeyAuth
// @Success 200 {object} v1.StatsViewModel
// @Router /compat/wakatime/v1/users/{user}/stats/{range} [get]
// @Router /api/compat/wakatime/v1/users/{user}/stats/{range} [get]
func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
var vars = mux.Vars(r)
var authorizedUser, requestedUser *models.User

View File

@ -51,8 +51,8 @@ func (h *StatusBarHandler) RegisterRoutes(router *mux.Router) {
// @Produce json
// @Param user path string true "User ID to fetch data for (or 'current')"
// @Security ApiKeyAuth
// @Success 200 {object} v1.StatusBarViewModel
// @Router /users/{user}/statusbar/today [get]
// @Success 200 {object} StatusBarViewModel
// @Router /api/users/{user}/statusbar/today [get]
func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) {
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
if err != nil {

View File

@ -53,7 +53,7 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
// @Param end query string false "End date (e.g. '2021-02-08')"
// @Security ApiKeyAuth
// @Success 200 {object} v1.SummariesViewModel
// @Router /compat/wakatime/v1/users/{user}/summaries [get]
// @Router /api/compat/wakatime/v1/users/{user}/summaries [get]
func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
_, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
if err != nil {

View File

@ -41,7 +41,7 @@ func (h *UsersHandler) RegisterRoutes(router *mux.Router) {
// @Param user path string true "User ID to fetch (or 'current')"
// @Security ApiKeyAuth
// @Success 200 {object} v1.UserViewModel
// @Router /compat/wakatime/v1/users/{user} [get]
// @Router /api/compat/wakatime/v1/users/{user} [get]
func (h *UsersHandler) Get(w http.ResponseWriter, r *http.Request) {
wakapiUser, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
if err != nil {

View File

@ -73,3 +73,48 @@ func (h *RelayHandler) Any(w http.ResponseWriter, r *http.Request) {
p.ServeHTTP(w, r)
}
// @Summary Proxy an GET API request to another Wakapi instance
// @ID relay-get
// @Tags relay
// @Param X-Target-URL header string true "Original URL to perform the request to"
// @Failure 403 {string} string "Returned if request path is not whitelisted"
// @Failure 502 {string} string "Returned if upstream host is down"
// @Router /relay [get]
func (h *RelayHandler) alias1() {}
// @Summary Proxy an POST API request to another Wakapi instance
// @ID relay-post
// @Tags relay
// @Param X-Target-URL header string true "Original URL to perform the request to"
// @Failure 403 {string} string "Returned if request path is not whitelisted"
// @Failure 502 {string} string "Returned if upstream host is down"
// @Router /relay [post]
func (h *RelayHandler) alias2() {}
// @Summary Proxy an PUT API request to another Wakapi instance
// @ID relay-put
// @Tags relay
// @Param X-Target-URL header string true "Original URL to perform the request to"
// @Failure 403 {string} string "Returned if request path is not whitelisted"
// @Failure 502 {string} string "Returned if upstream host is down"
// @Router /relay [put]
func (h *RelayHandler) alias3() {}
// @Summary Proxy an PATCH API request to another Wakapi instance
// @ID relay-patch
// @Tags relay
// @Param X-Target-URL header string true "Original URL to perform the request to"
// @Failure 403 {string} string "Returned if request path is not whitelisted"
// @Failure 502 {string} string "Returned if upstream host is down"
// @Router /relay [patch]
func (h *RelayHandler) alias4() {}
// @Summary Proxy an DELETE API request to another Wakapi instance
// @ID relay-delete
// @Tags relay
// @Param X-Target-URL header string true "Original URL to perform the request to"
// @Failure 403 {string} string "Returned if request path is not whitelisted"
// @Failure 502 {string} string "Returned if upstream host is down"
// @Router /relay [delete]
func (h *RelayHandler) alias5() {}

View File

@ -52,6 +52,9 @@ func DefaultTemplateFuncs() template.FuncMap {
"htmlSafe": func(html string) template.HTML {
return template.HTML(html)
},
"avatarUrlTemplate": func() string {
return config.Get().App.AvatarURLTemplate
},
}
}

View File

@ -354,27 +354,23 @@ func (h *SettingsHandler) actionDeleteLabel(w http.ResponseWriter, r *http.Reque
}
user := middlewares.GetPrincipal(r)
labelKey := r.PostFormValue("key")
labelValue := r.PostFormValue("value")
labelKey := r.PostFormValue("key") // label key
labelValue := r.PostFormValue("value") // project key
labelMap, err := h.projectLabelSrvc.GetByUserGrouped(user.ID)
labels, err := h.projectLabelSrvc.GetByUser(user.ID)
if err != nil {
return http.StatusInternalServerError, "", "could not delete label"
}
if projectLabels, ok := labelMap[labelKey]; ok {
for _, l := range projectLabels {
if l.Label == labelValue {
if err := h.projectLabelSrvc.Delete(l); err != nil {
return http.StatusInternalServerError, "", "could not delete label"
}
return http.StatusOK, "label deleted successfully", ""
for _, l := range labels {
if l.Label == labelKey && l.ProjectKey == labelValue {
if err := h.projectLabelSrvc.Delete(l); err != nil {
return http.StatusInternalServerError, "", "could not delete label"
}
return http.StatusOK, "label deleted successfully", ""
}
return http.StatusNotFound, "", "label not found"
} else {
return http.StatusNotFound, "", "project not found"
}
return http.StatusNotFound, "", "label not found"
}
func (h *SettingsHandler) actionDeleteLanguageMapping(w http.ResponseWriter, r *http.Request) (int, string, string) {
@ -651,7 +647,7 @@ func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewMode
}
// labels
labelMap, err := h.projectLabelSrvc.GetByUserGrouped(user.ID)
labelMap, err := h.projectLabelSrvc.GetByUserGroupedInverted(user.ID)
if err != nil {
conf.Log().Request(r).Error("error while building settings project label map - %v", err)
return &view.SettingsViewModel{Error: criticalError}
@ -660,11 +656,11 @@ func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewMode
combinedLabels := make([]*view.SettingsVMCombinedLabel, 0)
for _, l := range labelMap {
cl := &view.SettingsVMCombinedLabel{
Key: l[0].ProjectKey,
Key: l[0].Label,
Values: make([]string, len(l)),
}
for i, l1 := range l {
cl.Values[i] = l1.Label
cl.Values[i] = l1.ProjectKey
}
combinedLabels = append(combinedLabels, cl)
}

View File

@ -32,6 +32,8 @@ let icons = [
'twemoji:gear',
'eva:corner-right-down-fill',
'bi:heart-fill',
'fxemoji:running',
'ic:round-person'
]
const output = path.normalize(path.join(__dirname, '../static/assets/icons.js'))

View File

@ -8,6 +8,7 @@ import (
"github.com/muety/wakapi/utils"
"github.com/patrickmn/go-cache"
"strings"
"sync"
"time"
"github.com/muety/wakapi/models"
@ -16,20 +17,20 @@ import (
type HeartbeatService struct {
config *config.Config
cache *cache.Cache
cache2 *cache.Cache
eventBus *hub.Hub
repository repositories.IHeartbeatRepository
languageMappingSrvc ILanguageMappingService
entityCacheLock *sync.RWMutex
}
func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, languageMappingService ILanguageMappingService) *HeartbeatService {
srv := &HeartbeatService{
config: config.Get(),
cache: cache.New(24*time.Hour, 24*time.Hour),
cache2: cache.New(cache.NoExpiration, cache.NoExpiration),
eventBus: config.EventBus(),
repository: heartbeatRepo,
languageMappingSrvc: languageMappingService,
entityCacheLock: &sync.RWMutex{},
}
// using event hub is an unnecessary indirection here, however, we might
@ -48,7 +49,7 @@ func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, langua
}
func (srv *HeartbeatService) Insert(heartbeat *models.Heartbeat) error {
srv.updateEntityUserCacheByHeartbeat(heartbeat)
go srv.updateEntityUserCacheByHeartbeat(heartbeat)
return srv.repository.InsertBatch([]*models.Heartbeat{heartbeat})
}
@ -62,7 +63,7 @@ func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
filteredHeartbeats = append(filteredHeartbeats, hb)
hashes[hb.Hash] = true
}
srv.updateEntityUserCacheByHeartbeat(hb)
go srv.updateEntityUserCacheByHeartbeat(hb)
}
err := srv.repository.InsertBatch(filteredHeartbeats)
@ -147,7 +148,9 @@ func (srv *HeartbeatService) GetFirstByUsers() ([]*models.TimeByUser, error) {
func (srv *HeartbeatService) GetEntitySetByUser(entityType uint8, user *models.User) ([]string, error) {
cacheKey := srv.getEntityUserCacheKey(entityType, user)
if results, found := srv.cache2.Get(cacheKey); found {
if results, found := srv.cache.Get(cacheKey); found {
srv.entityCacheLock.RLock()
defer srv.entityCacheLock.RUnlock()
return utils.SetToStrings(results.(map[string]bool)), nil
}
@ -163,7 +166,7 @@ func (srv *HeartbeatService) GetEntitySetByUser(entityType uint8, user *models.U
}
}
srv.cache2.Set(cacheKey, utils.StringsToSet(filtered), cache.DefaultExpiration)
srv.cache.Set(cacheKey, utils.StringsToSet(filtered), cache.NoExpiration)
return filtered, nil
}
@ -190,21 +193,27 @@ func (srv *HeartbeatService) getEntityUserCacheKey(entityType uint8, user *model
func (srv *HeartbeatService) updateEntityUserCache(entityType uint8, entityKey string, user *models.User) {
cacheKey := srv.getEntityUserCacheKey(entityType, user)
if entities, found := srv.cache2.Get(cacheKey); found {
if _, ok := entities.(map[string]bool)[entityKey]; !ok {
if entities, found := srv.cache.Get(cacheKey); found {
entitySet := entities.(map[string]bool)
srv.entityCacheLock.Lock()
defer srv.entityCacheLock.Unlock()
if _, ok := entitySet[entityKey]; !ok {
entitySet[entityKey] = true
// new project / language / ..., which is not yet present in cache, arrived as part of a heartbeats
// -> invalidate cache
srv.cache2.Delete(cacheKey)
// -> update cache instead of just invalidating it, because rebuilding is expensive here
srv.cache.Set(cacheKey, entitySet, cache.NoExpiration)
}
}
}
func (srv *HeartbeatService) updateEntityUserCacheByHeartbeat(hb *models.Heartbeat) {
srv.updateEntityUserCache(models.SummaryProject, hb.Project, hb.User)
srv.updateEntityUserCache(models.SummaryLanguage, hb.Language, hb.User)
srv.updateEntityUserCache(models.SummaryEditor, hb.Editor, hb.User)
srv.updateEntityUserCache(models.SummaryOS, hb.OperatingSystem, hb.User)
srv.updateEntityUserCache(models.SummaryMachine, hb.Machine, hb.User)
go srv.updateEntityUserCache(models.SummaryProject, hb.Project, hb.User)
go srv.updateEntityUserCache(models.SummaryLanguage, hb.Language, hb.User)
go srv.updateEntityUserCache(models.SummaryEditor, hb.Editor, hb.User)
go srv.updateEntityUserCache(models.SummaryOS, hb.OperatingSystem, hb.User)
go srv.updateEntityUserCache(models.SummaryMachine, hb.Machine, hb.User)
}
func (srv *HeartbeatService) notifyBatch(heartbeats []*models.Heartbeat) {

View File

@ -44,21 +44,37 @@ func (srv *ProjectLabelService) GetByUser(userId string) ([]*models.ProjectLabel
}
func (srv *ProjectLabelService) GetByUserGrouped(userId string) (map[string][]*models.ProjectLabel, error) {
labels := make(map[string][]*models.ProjectLabel)
labelsByProject := make(map[string][]*models.ProjectLabel)
userLabels, err := srv.GetByUser(userId)
if err != nil {
return nil, err
}
for _, l := range userLabels {
if _, ok := labels[l.ProjectKey]; !ok {
labels[l.ProjectKey] = []*models.ProjectLabel{l}
if _, ok := labelsByProject[l.ProjectKey]; !ok {
labelsByProject[l.ProjectKey] = []*models.ProjectLabel{l}
} else {
labels[l.ProjectKey] = append(labels[l.ProjectKey], l)
labelsByProject[l.ProjectKey] = append(labelsByProject[l.ProjectKey], l)
}
}
return labelsByProject, nil
}
return labels, nil
func (srv *ProjectLabelService) GetByUserGroupedInverted(userId string) (map[string][]*models.ProjectLabel, error) {
projectsByLabel := make(map[string][]*models.ProjectLabel)
userLabels, err := srv.GetByUser(userId)
if err != nil {
return nil, err
}
for _, l := range userLabels {
if _, ok := projectsByLabel[l.Label]; !ok {
projectsByLabel[l.Label] = []*models.ProjectLabel{l}
} else {
projectsByLabel[l.Label] = append(projectsByLabel[l.Label], l)
}
}
return projectsByLabel, nil
}
func (srv *ProjectLabelService) Create(label *models.ProjectLabel) (*models.ProjectLabel, error) {

View File

@ -62,6 +62,7 @@ type IProjectLabelService interface {
GetById(uint) (*models.ProjectLabel, error)
GetByUser(string) ([]*models.ProjectLabel, error)
GetByUserGrouped(string) (map[string][]*models.ProjectLabel, error)
GetByUserGroupedInverted(string) (map[string][]*models.ProjectLabel, error)
Create(*models.ProjectLabel) (*models.ProjectLabel, error)
Delete(*models.ProjectLabel) error
}

View File

@ -420,6 +420,13 @@ function hexToRgb(hex) {
} : null;
}
function showUserMenuPopup(event) {
const el = document.getElementById('user-menu-popup')
el.classList.remove('hidden')
el.classList.add('block')
event.stopPropagation()
}
function showApiKeyPopup(event) {
const el = document.getElementById('api-key-popup')
el.classList.remove('hidden')

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,10 @@
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
line-height: 1.15;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
/* Sections
@ -51,9 +53,12 @@ h1 {
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
}
/**
@ -62,8 +67,10 @@ hr {
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/* Text-level semantics
@ -83,10 +90,13 @@ a {
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
border-bottom: none;
/* 1 */
text-decoration: underline;
/* 2 */
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted; /* 2 */
text-decoration: underline dotted;
/* 2 */
}
/**
@ -106,8 +116,10 @@ strong {
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/**
@ -163,10 +175,14 @@ input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
/**
@ -175,7 +191,8 @@ textarea {
*/
button,
input { /* 1 */
input {
/* 1 */
overflow: visible;
}
@ -185,7 +202,8 @@ input { /* 1 */
*/
button,
select { /* 1 */
select {
/* 1 */
text-transform: none;
}
@ -239,12 +257,18 @@ fieldset {
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
/**
@ -394,8 +418,10 @@ ul {
*/
html {
font-family: 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"; /* 1 */
line-height: 1.5; /* 2 */
font-family: 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";
/* 1 */
line-height: 1.5;
/* 2 */
}
/**
@ -427,10 +453,14 @@ html {
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: #e2e8f0; /* 2 */
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e2e8f0;
/* 2 */
}
/*
@ -723,6 +753,10 @@ video {
border-radius: 9999px;
}
.border-2 {
border-width: 2px;
}
.border {
border-width: 1px;
}
@ -831,6 +865,10 @@ video {
font-weight: 400;
}
.font-medium {
font-weight: 500;
}
.font-semibold {
font-weight: 600;
}
@ -999,6 +1037,10 @@ video {
margin-top: 4rem;
}
.mt-24 {
margin-top: 6rem;
}
.-ml-1 {
margin-left: -0.25rem;
}

View File

@ -32,7 +32,7 @@ var doc = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/compat/shields/v1/{user}/{interval}/{filter}": {
"/api/compat/shields/v1/{user}/{interval}/{filter}": {
"get": {
"description": "Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed.",
"produces": [
@ -90,7 +90,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}": {
"/api/compat/wakatime/v1/users/{user}": {
"get": {
"security": [
{
@ -125,7 +125,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/all_time_since_today": {
"/api/compat/wakatime/v1/users/{user}/all_time_since_today": {
"get": {
"security": [
{
@ -160,7 +160,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/heartbeats": {
"/api/compat/wakatime/v1/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -193,7 +193,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/heartbeats.bulk": {
"/api/compat/wakatime/v1/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -229,7 +229,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/projects": {
"/api/compat/wakatime/v1/users/{user}/projects": {
"get": {
"security": [
{
@ -271,7 +271,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/stats/{range}": {
"/api/compat/wakatime/v1/users/{user}/stats/{range}": {
"get": {
"security": [
{
@ -326,7 +326,7 @@ var doc = `{
}
}
},
"/compat/wakatime/v1/users/{user}/summaries": {
"/api/compat/wakatime/v1/users/{user}/summaries": {
"get": {
"security": [
{
@ -393,7 +393,7 @@ var doc = `{
}
}
},
"/health": {
"/api/health": {
"get": {
"produces": [
"text/plain"
@ -413,7 +413,7 @@ var doc = `{
}
}
},
"/heartbeat": {
"/api/heartbeat": {
"post": {
"security": [
{
@ -446,7 +446,7 @@ var doc = `{
}
}
},
"/heartbeats": {
"/api/heartbeats": {
"post": {
"security": [
{
@ -482,7 +482,7 @@ var doc = `{
}
}
},
"/plugins/errors": {
"/api/plugins/errors": {
"post": {
"security": [
{
@ -515,7 +515,7 @@ var doc = `{
}
}
},
"/summary": {
"/api/summary": {
"get": {
"security": [
{
@ -580,7 +580,7 @@ var doc = `{
}
}
},
"/users/{user}/heartbeats": {
"/api/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -613,7 +613,7 @@ var doc = `{
}
}
},
"/users/{user}/heartbeats.bulk": {
"/api/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -649,7 +649,42 @@ var doc = `{
}
}
},
"/v1/users/{user}/heartbeats": {
"/api/users/{user}/statusbar/today": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mimics https://wakatime.com/api/v1/users/current/statusbar/today. Have no official documentation",
"produces": [
"application/json"
],
"tags": [
"wakatime"
],
"summary": "Retrieve summary for statusbar",
"operationId": "statusbar",
"parameters": [
{
"type": "string",
"description": "User ID to fetch data for (or 'current')",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.StatusBarViewModel"
}
}
}
}
},
"/api/v1/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -682,7 +717,7 @@ var doc = `{
}
}
},
"/v1/users/{user}/heartbeats.bulk": {
"/api/v1/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -717,6 +752,158 @@ var doc = `{
}
}
}
},
"/relay": {
"get": {
"tags": [
"relay"
],
"summary": "Proxy an GET API request to another Wakapi instance",
"operationId": "relay-get",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"put": {
"tags": [
"relay"
],
"summary": "Proxy an PUT API request to another Wakapi instance",
"operationId": "relay-put",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"post": {
"tags": [
"relay"
],
"summary": "Proxy an POST API request to another Wakapi instance",
"operationId": "relay-post",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"delete": {
"tags": [
"relay"
],
"summary": "Proxy an DELETE API request to another Wakapi instance",
"operationId": "relay-delete",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"patch": {
"tags": [
"relay"
],
"summary": "Proxy an PATCH API request to another Wakapi instance",
"operationId": "relay-patch",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
@ -791,6 +978,9 @@ var doc = `{
},
"type": {
"type": "string"
},
"user_agent": {
"type": "string"
}
}
},
@ -1014,6 +1204,17 @@ var doc = `{
}
}
},
"v1.StatusBarViewModel": {
"type": "object",
"properties": {
"cached_at": {
"type": "string"
},
"data": {
"$ref": "#/definitions/v1.SummariesData"
}
}
},
"v1.SummariesData": {
"type": "object",
"properties": {

View File

@ -16,7 +16,7 @@
},
"basePath": "/api",
"paths": {
"/compat/shields/v1/{user}/{interval}/{filter}": {
"/api/compat/shields/v1/{user}/{interval}/{filter}": {
"get": {
"description": "Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed.",
"produces": [
@ -74,7 +74,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}": {
"/api/compat/wakatime/v1/users/{user}": {
"get": {
"security": [
{
@ -109,7 +109,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/all_time_since_today": {
"/api/compat/wakatime/v1/users/{user}/all_time_since_today": {
"get": {
"security": [
{
@ -144,7 +144,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/heartbeats": {
"/api/compat/wakatime/v1/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -177,7 +177,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/heartbeats.bulk": {
"/api/compat/wakatime/v1/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -213,7 +213,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/projects": {
"/api/compat/wakatime/v1/users/{user}/projects": {
"get": {
"security": [
{
@ -255,7 +255,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/stats/{range}": {
"/api/compat/wakatime/v1/users/{user}/stats/{range}": {
"get": {
"security": [
{
@ -310,7 +310,7 @@
}
}
},
"/compat/wakatime/v1/users/{user}/summaries": {
"/api/compat/wakatime/v1/users/{user}/summaries": {
"get": {
"security": [
{
@ -377,7 +377,7 @@
}
}
},
"/health": {
"/api/health": {
"get": {
"produces": [
"text/plain"
@ -397,7 +397,7 @@
}
}
},
"/heartbeat": {
"/api/heartbeat": {
"post": {
"security": [
{
@ -430,7 +430,7 @@
}
}
},
"/heartbeats": {
"/api/heartbeats": {
"post": {
"security": [
{
@ -466,7 +466,7 @@
}
}
},
"/plugins/errors": {
"/api/plugins/errors": {
"post": {
"security": [
{
@ -499,7 +499,7 @@
}
}
},
"/summary": {
"/api/summary": {
"get": {
"security": [
{
@ -564,7 +564,7 @@
}
}
},
"/users/{user}/heartbeats": {
"/api/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -597,7 +597,7 @@
}
}
},
"/users/{user}/heartbeats.bulk": {
"/api/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -633,7 +633,42 @@
}
}
},
"/v1/users/{user}/heartbeats": {
"/api/users/{user}/statusbar/today": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Mimics https://wakatime.com/api/v1/users/current/statusbar/today. Have no official documentation",
"produces": [
"application/json"
],
"tags": [
"wakatime"
],
"summary": "Retrieve summary for statusbar",
"operationId": "statusbar",
"parameters": [
{
"type": "string",
"description": "User ID to fetch data for (or 'current')",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.StatusBarViewModel"
}
}
}
}
},
"/api/v1/users/{user}/heartbeats": {
"post": {
"security": [
{
@ -666,7 +701,7 @@
}
}
},
"/v1/users/{user}/heartbeats.bulk": {
"/api/v1/users/{user}/heartbeats.bulk": {
"post": {
"security": [
{
@ -701,6 +736,158 @@
}
}
}
},
"/relay": {
"get": {
"tags": [
"relay"
],
"summary": "Proxy an GET API request to another Wakapi instance",
"operationId": "relay-get",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"put": {
"tags": [
"relay"
],
"summary": "Proxy an PUT API request to another Wakapi instance",
"operationId": "relay-put",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"post": {
"tags": [
"relay"
],
"summary": "Proxy an POST API request to another Wakapi instance",
"operationId": "relay-post",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"delete": {
"tags": [
"relay"
],
"summary": "Proxy an DELETE API request to another Wakapi instance",
"operationId": "relay-delete",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
},
"patch": {
"tags": [
"relay"
],
"summary": "Proxy an PATCH API request to another Wakapi instance",
"operationId": "relay-patch",
"parameters": [
{
"type": "string",
"description": "Original URL to perform the request to",
"name": "X-Target-URL",
"in": "header",
"required": true
}
],
"responses": {
"403": {
"description": "Returned if request path is not whitelisted",
"schema": {
"type": "string"
}
},
"502": {
"description": "Returned if upstream host is down",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
@ -775,6 +962,9 @@
},
"type": {
"type": "string"
},
"user_agent": {
"type": "string"
}
}
},
@ -998,6 +1188,17 @@
}
}
},
"v1.StatusBarViewModel": {
"type": "object",
"properties": {
"cached_at": {
"type": "string"
},
"data": {
"$ref": "#/definitions/v1.SummariesData"
}
}
},
"v1.SummariesData": {
"type": "object",
"properties": {

View File

@ -49,6 +49,8 @@ definitions:
type: number
type:
type: string
user_agent:
type: string
type: object
models.Summary:
properties:
@ -198,6 +200,13 @@ definitions:
data:
$ref: '#/definitions/v1.StatsData'
type: object
v1.StatusBarViewModel:
properties:
cached_at:
type: string
data:
$ref: '#/definitions/v1.SummariesData'
type: object
v1.SummariesData:
properties:
categories:
@ -342,7 +351,7 @@ info:
title: Wakapi API
version: "1.0"
paths:
/compat/shields/v1/{user}/{interval}/{filter}:
/api/compat/shields/v1/{user}/{interval}/{filter}:
get:
description: Retrieve total time for a given entity (e.g. a project) within
a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint).
@ -387,7 +396,7 @@ paths:
summary: Get badge data
tags:
- badges
/compat/wakatime/v1/users/{user}:
/api/compat/wakatime/v1/users/{user}:
get:
description: Mimics https://wakatime.com/developers#users
operationId: get-wakatime-user
@ -409,7 +418,7 @@ paths:
summary: Retrieve the given user
tags:
- wakatime
/compat/wakatime/v1/users/{user}/all_time_since_today:
/api/compat/wakatime/v1/users/{user}/all_time_since_today:
get:
description: Mimics https://wakatime.com/developers#all_time_since_today
operationId: get-all-time
@ -431,7 +440,7 @@ paths:
summary: Retrieve summary for all time
tags:
- wakatime
/compat/wakatime/v1/users/{user}/heartbeats:
/api/compat/wakatime/v1/users/{user}/heartbeats:
post:
consumes:
- application/json
@ -451,7 +460,7 @@ paths:
summary: Push a new heartbeat
tags:
- heartbeat
/compat/wakatime/v1/users/{user}/heartbeats.bulk:
/api/compat/wakatime/v1/users/{user}/heartbeats.bulk:
post:
consumes:
- application/json
@ -473,7 +482,7 @@ paths:
summary: Push new heartbeats
tags:
- heartbeat
/compat/wakatime/v1/users/{user}/projects:
/api/compat/wakatime/v1/users/{user}/projects:
get:
description: Mimics https://wakatime.com/developers#projects
operationId: get-wakatime-projects
@ -500,7 +509,7 @@ paths:
summary: Retrieve and fitler the user's projects
tags:
- wakatime
/compat/wakatime/v1/users/{user}/stats/{range}:
/api/compat/wakatime/v1/users/{user}/stats/{range}:
get:
description: Mimics https://wakatime.com/developers#stats
operationId: get-wakatimes-tats
@ -539,7 +548,7 @@ paths:
summary: Retrieve statistics for a given user
tags:
- wakatime
/compat/wakatime/v1/users/{user}/summaries:
/api/compat/wakatime/v1/users/{user}/summaries:
get:
description: Mimics https://wakatime.com/developers#summaries.
operationId: get-wakatime-summaries
@ -586,7 +595,7 @@ paths:
summary: Retrieve WakaTime-compatible summaries
tags:
- wakatime
/health:
/api/health:
get:
operationId: get-health
produces:
@ -599,7 +608,7 @@ paths:
summary: Check the application's health status
tags:
- misc
/heartbeat:
/api/heartbeat:
post:
consumes:
- application/json
@ -619,7 +628,7 @@ paths:
summary: Push a new heartbeat
tags:
- heartbeat
/heartbeats:
/api/heartbeats:
post:
consumes:
- application/json
@ -641,7 +650,7 @@ paths:
summary: Push new heartbeats
tags:
- heartbeat
/plugins/errors:
/api/plugins/errors:
post:
consumes:
- application/json
@ -661,7 +670,7 @@ paths:
summary: Push a new diagnostics object
tags:
- diagnostics
/summary:
/api/summary:
get:
operationId: get-summary
parameters:
@ -706,7 +715,7 @@ paths:
summary: Retrieve a summary
tags:
- summary
/users/{user}/heartbeats:
/api/users/{user}/heartbeats:
post:
consumes:
- application/json
@ -726,7 +735,7 @@ paths:
summary: Push a new heartbeat
tags:
- heartbeat
/users/{user}/heartbeats.bulk:
/api/users/{user}/heartbeats.bulk:
post:
consumes:
- application/json
@ -748,7 +757,30 @@ paths:
summary: Push new heartbeats
tags:
- heartbeat
/v1/users/{user}/heartbeats:
/api/users/{user}/statusbar/today:
get:
description: Mimics https://wakatime.com/api/v1/users/current/statusbar/today.
Have no official documentation
operationId: statusbar
parameters:
- description: User ID to fetch data for (or 'current')
in: path
name: user
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.StatusBarViewModel'
security:
- ApiKeyAuth: []
summary: Retrieve summary for statusbar
tags:
- wakatime
/api/v1/users/{user}/heartbeats:
post:
consumes:
- application/json
@ -768,7 +800,7 @@ paths:
summary: Push a new heartbeat
tags:
- heartbeat
/v1/users/{user}/heartbeats.bulk:
/api/v1/users/{user}/heartbeats.bulk:
post:
consumes:
- application/json
@ -790,6 +822,107 @@ paths:
summary: Push new heartbeats
tags:
- heartbeat
/relay:
delete:
operationId: relay-delete
parameters:
- description: Original URL to perform the request to
in: header
name: X-Target-URL
required: true
type: string
responses:
"403":
description: Returned if request path is not whitelisted
schema:
type: string
"502":
description: Returned if upstream host is down
schema:
type: string
summary: Proxy an DELETE API request to another Wakapi instance
tags:
- relay
get:
operationId: relay-get
parameters:
- description: Original URL to perform the request to
in: header
name: X-Target-URL
required: true
type: string
responses:
"403":
description: Returned if request path is not whitelisted
schema:
type: string
"502":
description: Returned if upstream host is down
schema:
type: string
summary: Proxy an GET API request to another Wakapi instance
tags:
- relay
patch:
operationId: relay-patch
parameters:
- description: Original URL to perform the request to
in: header
name: X-Target-URL
required: true
type: string
responses:
"403":
description: Returned if request path is not whitelisted
schema:
type: string
"502":
description: Returned if upstream host is down
schema:
type: string
summary: Proxy an PATCH API request to another Wakapi instance
tags:
- relay
post:
operationId: relay-post
parameters:
- description: Original URL to perform the request to
in: header
name: X-Target-URL
required: true
type: string
responses:
"403":
description: Returned if request path is not whitelisted
schema:
type: string
"502":
description: Returned if upstream host is down
schema:
type: string
summary: Proxy an POST API request to another Wakapi instance
tags:
- relay
put:
operationId: relay-put
parameters:
- description: Original URL to perform the request to
in: header
name: X-Target-URL
required: true
type: string
responses:
"403":
description: Returned if request path is not whitelisted
schema:
type: string
"502":
description: Returned if upstream host is down
schema:
type: string
summary: Proxy an PUT API request to another Wakapi instance
tags:
- relay
securityDefinitions:
ApiKeyAuth:
in: header

32
utils/db.go Normal file
View File

@ -0,0 +1,32 @@
package utils
import (
"github.com/emvi/logbuch"
"gorm.io/gorm"
)
func IsCleanDB(db *gorm.DB) bool {
if db.Dialector.Name() == "sqlite" {
var count int64
if err := db.Raw("SELECT count(*) from sqlite_master WHERE type = 'table'").Scan(&count).Error; err != nil {
logbuch.Error("failed to check if database is clean - '%v'", err)
return false
}
return count == 0
}
logbuch.Warn("IsCleanDB is not yet implemented for dialect '%s'", db.Dialector.Name())
return false
}
func HasConstraints(db *gorm.DB) bool {
if db.Dialector.Name() == "sqlite" {
var count int64
if err := db.Raw("SELECT count(*) from sqlite_master WHERE sql LIKE '%CONSTRAINT%'").Scan(&count).Error; err != nil {
logbuch.Error("failed to check if database has constraints - '%v'", err)
return false
}
return count != 0
}
logbuch.Warn("HasForeignKeyConstraints is not yet implemented for dialect '%s'", db.Dialector.Name())
return false
}

View File

@ -1 +1 @@
1.30.0
1.30.2

View File

@ -46,7 +46,7 @@
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white"><span class="iconify inline" data-icon="fxemoji:satelliteantenna"></span> Host it
</button>
</a>
<a href="https://liberapay.com/muety" target="_blank" rel="noopener noreferrer">
<a href="https://github.com/sponsors/muety" target="_blank" rel="noopener noreferrer">
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white"><span class="iconify inline" data-icon="flat-color-icons:donate"></span> Support it
</button>
</a>
@ -74,6 +74,7 @@
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> &nbsp; Compatible with <a href="https://wakatime.com" target="_blank" rel="noopener noreferrer" class="underline">Wakatime</a></li>
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> &nbsp; <a href="https://prometheus.io" target="_blank" rel="noopener noreferrer" class="underline">Prometheus</a> metrics</li>
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> &nbsp; Lightning fast</li>
<li title="Wakapi.dev is hosted in Germany, none of your data will leave the EU"><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> &nbsp; GDPR-compliant</li>
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> &nbsp; Self-hosted</li>
</ul>
</div>

View File

@ -221,9 +221,9 @@
<div class="flex items-center" action="" method="post">
<div class="text-gray-500 border-1 w-full border-green-700 inline-block my-1 py-1 text-align text-sm"
style="line-height: 1.8">
&#9656;&nbsp;<span class="font-semibold text-white">{{ $label.Key }}:</span>
&#9656;&nbsp;<span class="font-semibold text-white font-mono">{{ $label.Key }}:</span>
{{ range $j, $value := $label.Values }}
<form action="" method="post" class="text-white text-xs bg-gray-900 rounded-full py-1 px-2 font-mono inline-flex justify-between items-center space-x-2">
<form action="" method="post" class="text-white text-xs bg-gray-900 rounded-full py-1 px-2 my-1 inline-flex justify-between items-center space-x-2">
<input type="hidden" name="action" value="delete_label">
<input type="hidden" name="key" value="{{ $label.Key }}">
<input type="hidden" name="value" value="{{ $value }}">

View File

@ -19,6 +19,24 @@
</div>
</div>
<div class="hidden flex bg-gray-800 shadow-md z-10 p-2 absolute top-0 right-0 mt-10 mr-8 border border-green-700 rounded popup mt-24"
id="user-menu-popup" style="min-width: 200px;">
<div class="flex-grow flex flex-col px-2">
<div class="flex flex-col text-xs text-gray-300 mx-1 mb-4 items-center">
<span class="font-semibold">{{ .User.ID }}</span>
{{ if .User.Email }}
<span>({{ .User.Email }})</span>
{{ end }}
</div>
<form action="logout" method="post" class="flex-grow">
<button type="submit" class="py-1 px-3 h-8 rounded bg-green-700 text-white text-sm w-full">
<span>Logout</span>
<span class="iconify inline" data-icon="fxemoji:running"></span>
</button>
</form>
</div>
</div>
<div class="absolute flex top-0 right-0 mr-8 mt-10 py-2">
<div class="mx-1">
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white text-sm"
@ -30,10 +48,12 @@
<span class="iconify inline" data-icon="twemoji:gear"></span>
</a>
</div>
<div class="mx-1">
<form action="logout" method="post">
<button type="submit" class="py-1 px-3 h-8 rounded border border-green-700 text-white text-sm">Logout</button>
</form>
<div class="mx-1 flex items-center">
{{ if avatarUrlTemplate }}
<img src="{{ .User.AvatarURL avatarUrlTemplate }}" width="32px" class="rounded-full border-2 border-green-700 cursor-pointer" onclick="showUserMenuPopup(event)" alt="User Profile Avatar" title="Looks like you, doesn't it?"/>
{{ else }}
<span class="iconify inline cursor-pointer text-gray-500 rounded-full border-2 border-green-700" style="width: 32px; height: 32px" data-icon="ic:round-person" onclick="showUserMenuPopup(event)"></span>
{{ end }}
</div>
</div>