mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
101fdfb957 | |||
1a808f9197 | |||
ee31212cdd | |||
712949afc7 | |||
9dbc2039fc | |||
f3b738b250 | |||
cf3d293688 | |||
0fbb554fc3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ build
|
||||
*.db
|
||||
config*.yml
|
||||
!config.default.yml
|
||||
!testing/config.testing.yml
|
||||
pkged.go
|
||||
package.json
|
||||
yarn.lock
|
||||
|
42
README.md
42
README.md
@ -24,7 +24,7 @@
|
||||
<span> | </span>
|
||||
<a href="#-features">Features</a>
|
||||
<span> | </span>
|
||||
<a href="#-how-to-use">How to use</a>
|
||||
<a href="#%EF%B8%8F-how-to-use">How to use</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/muety/wakapi/issues">Issues</a>
|
||||
<span> | </span>
|
||||
@ -45,6 +45,7 @@
|
||||
* [API Endpoints](#-api-endpoints)
|
||||
* [Integrations](#-integrations)
|
||||
* [Best Practices](#-best-practices)
|
||||
* [Tests](#-tests)
|
||||
* [Developer Notes](#-developer-notes)
|
||||
* [Support](#-support)
|
||||
* [FAQs](#-faqs)
|
||||
@ -59,6 +60,7 @@ I'd love to get some community feedback from active Wakapi users. If you want, p
|
||||
* ✅ Built by developers for developers
|
||||
* ✅ Statistics for projects, languages, editors, hosts and operating systems
|
||||
* ✅ Badges
|
||||
* ✅ Weekly E-Mail Reports
|
||||
* ✅ REST API
|
||||
* ✅ Partially compatible with WakaTime
|
||||
* ✅ WakaTime integration
|
||||
@ -131,8 +133,8 @@ Wakapi relies on the open-source [WakaTime](https://github.com/wakatime/wakatime
|
||||
```ini
|
||||
[settings]
|
||||
|
||||
# Your Wakapi server URL or 'https://wakapi.dev/api/heartbeat' when using the cloud server
|
||||
api_url = http://localhost:3000/api/heartbeat
|
||||
# Your Wakapi server URL or 'https://wakapi.dev/api' when using the cloud server
|
||||
api_url = http://localhost:3000/api
|
||||
|
||||
# Your Wakapi API key (get it from the web interface after having created an account)
|
||||
api_key = 406fe41f-6d69-4183-a4cc-121e0c524c2b
|
||||
@ -282,12 +284,40 @@ Preview:
|
||||
It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS).
|
||||
However, if you want to expose your wakapi instance to the public anyway, you need to set `server.listen_ipv4` to `0.0.0.0` in `config.yml`
|
||||
|
||||
## 🤓 Developer Notes
|
||||
### Running tests
|
||||
## 🧪 Tests
|
||||
### Unit Tests
|
||||
Unit tests are supposed to test business logic on a fine-grained level. They are implemented as part of the application, using Go's [testing](https://pkg.go.dev/testing?utm_source=godoc) package alongside [stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify).
|
||||
|
||||
#### How to run
|
||||
```bash
|
||||
CGO_FLAGS="-g -O2 -Wno-return-local-addr" go test -json -coverprofile=coverage/coverage.out ./... -run ./...
|
||||
$ CGO_FLAGS="-g -O2 -Wno-return-local-addr" go test -json -coverprofile=coverage/coverage.out ./... -run ./...
|
||||
```
|
||||
|
||||
### API Tests
|
||||
API tests are implemented as black box tests, which interact with a fully-fledged, standalone Wakapi through HTTP requests. They are supposed to check Wakapi's web stack and endpoints, including response codes, headers and data on a syntactical level, rather than checking the actual content that is returned.
|
||||
|
||||
Our API (or end-to-end, in some way) tests are implemented as a [Postman](https://www.postman.com/) collection and can be run either from inside Postman, or using [newman](https://www.npmjs.com/package/newman) as a command-line runner.
|
||||
|
||||
To get a predictable environment, tests are run against a fresh and clean Wakapi instance with a SQLite database that is populated with nothing but some seed data (see [data.sql](testing/data.sql)). It is usually recommended for software tests to be [safe](https://www.restapitutorial.com/lessons/idempotency.html), stateless and without side effects. In contrary to that paradigm, our API tests strictly require a fixed execution order (which Postman assures) and their assertions may rely on specific previous tests having succeeded.
|
||||
|
||||
#### Prerequisites (Linux only)
|
||||
```bash
|
||||
# 1. sqlite (cli)
|
||||
$ sudo apt install sqlite # Fedora: sudo dnf install sqlite
|
||||
|
||||
# 2. screen
|
||||
$ sudo apt install screen # Fedora: sudo dnf install screen
|
||||
|
||||
# 3. newman
|
||||
$ npm install -g newman
|
||||
```
|
||||
|
||||
#### How to run (Linux only)
|
||||
```bash
|
||||
$ ./testing/run_api_tests.sh
|
||||
```
|
||||
|
||||
## 🤓 Developer Notes
|
||||
### Building web assets
|
||||
To keep things minimal, Wakapi does not contain a `package.json`, `node_modules` or any sort of frontend build step. Instead, all JS and CSS assets are included as static files and checked in to Git. This way we can avoid requiring NodeJS to build Wakapi. However, for [TailwindCSS](https://tailwindcss.com/docs/installation#building-for-production) it makes sense to run it through a "build" step to benefit from purging and significantly reduce it in size. To only require this at the time of development, the compiled asset is checked in to Git as well. Similarly, [Iconify](https://iconify.design/docs/icon-bundles/) bundles are also created at development time and checked in to the repo.
|
||||
|
||||
|
@ -13,6 +13,7 @@ app:
|
||||
aggregation_time: '02:15' # time at which to run daily aggregation batch jobs
|
||||
report_time_weekly: 'fri,18:00' # time at which to fan out weekly reports (format: '<weekday)>,<daytime>')
|
||||
inactive_days: 7 # time of previous days within a user must have logged in to be considered active
|
||||
import_batch_size: 50 # maximum number of heartbeats to insert into the database within one transaction
|
||||
custom_languages:
|
||||
vue: Vue
|
||||
jsx: JSX
|
||||
|
@ -64,7 +64,7 @@ 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:"100" env:"WAKAPI_IMPORT_BATCH_SIZE"`
|
||||
ImportBatchSize int `yaml:"import_batch_size" default:"50" env:"WAKAPI_IMPORT_BATCH_SIZE"`
|
||||
InactiveDays int `yaml:"inactive_days" default:"7" env:"WAKAPI_INACTIVE_DAYS"`
|
||||
CustomLanguages map[string]string `yaml:"custom_languages"`
|
||||
Colors map[string]map[string]string `yaml:"-"`
|
||||
@ -341,22 +341,27 @@ func Load(version string) *Config {
|
||||
}
|
||||
}
|
||||
|
||||
if config.Server.ListenIpV4 == "" && config.Server.ListenIpV6 == "" {
|
||||
logbuch.Fatal("either of listen_ipv4 or listen_ipv6 must be set")
|
||||
}
|
||||
|
||||
if config.Db.MaxConn <= 0 {
|
||||
logbuch.Fatal("you must allow at least one database connection")
|
||||
}
|
||||
|
||||
if config.Sentry.Dsn != "" {
|
||||
logbuch.Info("enabling sentry integration")
|
||||
initSentry(config.Sentry, config.IsDev())
|
||||
}
|
||||
|
||||
// some validation checks
|
||||
if config.Server.ListenIpV4 == "" && config.Server.ListenIpV6 == "" {
|
||||
logbuch.Fatal("either of listen_ipv4 or listen_ipv6 must be set")
|
||||
}
|
||||
if config.Db.MaxConn <= 0 {
|
||||
logbuch.Fatal("you must allow at least one database connection")
|
||||
}
|
||||
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
|
||||
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
||||
}
|
||||
if _, err := time.Parse("15:04", config.App.GetWeeklyReportTime()); err != nil {
|
||||
logbuch.Fatal("invalid interval set for report_time_weekly")
|
||||
}
|
||||
if _, err := time.Parse("15:04", config.App.AggregationTime); err != nil {
|
||||
logbuch.Fatal("invalid interval set for aggregation_time")
|
||||
}
|
||||
|
||||
Set(config)
|
||||
return Get()
|
||||
|
@ -1,22 +1,4 @@
|
||||
mode: set
|
||||
github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
|
||||
github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
|
||||
github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
|
||||
github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
|
||||
github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
|
||||
github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
|
||||
github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
|
||||
github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
|
||||
github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
|
||||
github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
|
||||
github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
|
||||
github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
|
||||
github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
|
||||
github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
|
||||
github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
|
||||
github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
|
||||
github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
|
||||
github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:32.34,34.2 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:36.65,38.46 2 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:38.46,39.108 1 1
|
||||
@ -34,6 +16,17 @@ github.com/muety/wakapi/models/heartbeat.go:67.37,83.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:91.41,93.16 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:96.2,97.10 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:93.16,95.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/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/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
|
||||
@ -53,6 +46,54 @@ github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 1
|
||||
github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 1
|
||||
github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 1
|
||||
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
|
||||
github.com/muety/wakapi/models/shared.go:35.52,37.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:39.52,42.16 3 0
|
||||
github.com/muety/wakapi/models/shared.go:45.2,47.12 3 0
|
||||
github.com/muety/wakapi/models/shared.go:42.16,44.3 1 0
|
||||
github.com/muety/wakapi/models/shared.go:50.52,56.22 2 0
|
||||
github.com/muety/wakapi/models/shared.go:71.2,74.12 3 0
|
||||
github.com/muety/wakapi/models/shared.go:57.14,61.17 2 0
|
||||
github.com/muety/wakapi/models/shared.go:64.17,66.8 2 0
|
||||
github.com/muety/wakapi/models/shared.go:67.10,68.64 1 0
|
||||
github.com/muety/wakapi/models/shared.go:61.17,63.4 1 0
|
||||
github.com/muety/wakapi/models/shared.go:77.51,80.2 2 0
|
||||
github.com/muety/wakapi/models/shared.go:82.45,84.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:86.37,88.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:90.35,92.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:94.34,96.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
|
||||
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
|
||||
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
|
||||
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
|
||||
github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
|
||||
github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
|
||||
github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
|
||||
github.com/muety/wakapi/models/filters.go:20.17,21.27 1 0
|
||||
github.com/muety/wakapi/models/filters.go:22.23,23.33 1 0
|
||||
github.com/muety/wakapi/models/filters.go:24.21,25.31 1 0
|
||||
github.com/muety/wakapi/models/filters.go:26.22,27.32 1 0
|
||||
github.com/muety/wakapi/models/filters.go:32.47,33.21 1 1
|
||||
github.com/muety/wakapi/models/filters.go:44.2,44.21 1 1
|
||||
github.com/muety/wakapi/models/filters.go:33.21,35.3 1 1
|
||||
github.com/muety/wakapi/models/filters.go:35.8,35.23 1 1
|
||||
github.com/muety/wakapi/models/filters.go:35.23,37.3 1 0
|
||||
github.com/muety/wakapi/models/filters.go:37.8,37.29 1 1
|
||||
github.com/muety/wakapi/models/filters.go:37.29,39.3 1 1
|
||||
github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
|
||||
github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
|
||||
github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
|
||||
github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
|
||||
github.com/muety/wakapi/models/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:70.29,72.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:74.37,81.2 6 1
|
||||
github.com/muety/wakapi/models/summary.go:83.35,85.2 1 1
|
||||
@ -119,219 +160,6 @@ github.com/muety/wakapi/models/user.go:115.45,117.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:119.45,121.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:123.39,125.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:127.39,130.2 2 0
|
||||
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
|
||||
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
|
||||
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
|
||||
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
|
||||
github.com/muety/wakapi/models/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/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/shared.go:35.52,37.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:39.52,42.16 3 0
|
||||
github.com/muety/wakapi/models/shared.go:45.2,47.12 3 0
|
||||
github.com/muety/wakapi/models/shared.go:42.16,44.3 1 0
|
||||
github.com/muety/wakapi/models/shared.go:50.52,56.22 2 0
|
||||
github.com/muety/wakapi/models/shared.go:71.2,74.12 3 0
|
||||
github.com/muety/wakapi/models/shared.go:57.14,61.17 2 0
|
||||
github.com/muety/wakapi/models/shared.go:64.17,66.8 2 0
|
||||
github.com/muety/wakapi/models/shared.go:67.10,68.64 1 0
|
||||
github.com/muety/wakapi/models/shared.go:61.17,63.4 1 0
|
||||
github.com/muety/wakapi/models/shared.go:77.51,80.2 2 0
|
||||
github.com/muety/wakapi/models/shared.go:82.45,84.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:86.37,88.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:90.35,92.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:94.34,96.2 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:37.65,39.85 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:39.85,41.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:47.94,49.16 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:53.2,53.107 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:57.2,57.22 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:49.16,51.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:53.107,55.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:60.56,64.2 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:66.55,69.16 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:72.2,72.16 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:69.16,71.3 1 0
|
||||
github.com/muety/wakapi/utils/color.go:8.90,10.32 2 0
|
||||
github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0
|
||||
github.com/muety/wakapi/utils/color.go:10.32,11.50 1 0
|
||||
github.com/muety/wakapi/utils/color.go:11.50,13.4 1 0
|
||||
github.com/muety/wakapi/utils/common.go:18.73,19.58 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.2,22.87 1 0
|
||||
github.com/muety/wakapi/utils/common.go:25.2,25.64 1 0
|
||||
github.com/muety/wakapi/utils/common.go:19.58,21.3 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.87,24.3 1 0
|
||||
github.com/muety/wakapi/utils/common.go:28.40,30.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:32.44,34.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:36.49,38.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:40.45,42.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:44.24,46.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:48.56,51.45 3 1
|
||||
github.com/muety/wakapi/utils/common.go:54.2,54.40 1 1
|
||||
github.com/muety/wakapi/utils/common.go:51.45,53.3 1 1
|
||||
github.com/muety/wakapi/utils/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/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:8.41,10.16 2 0
|
||||
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
|
||||
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
|
||||
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
|
||||
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
|
||||
github.com/muety/wakapi/utils/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.41,61.21 2 1
|
||||
github.com/muety/wakapi/utils/date.go:64.2,64.36 1 1
|
||||
github.com/muety/wakapi/utils/date.go:61.21,63.3 1 1
|
||||
github.com/muety/wakapi/utils/date.go:68.63,70.2 1 0
|
||||
github.com/muety/wakapi/utils/date.go:73.62,79.2 5 0
|
||||
github.com/muety/wakapi/utils/date.go:82.67,85.33 2 1
|
||||
github.com/muety/wakapi/utils/date.go:94.2,94.18 1 1
|
||||
github.com/muety/wakapi/utils/date.go:85.33,87.19 2 1
|
||||
github.com/muety/wakapi/utils/date.go:90.3,91.10 2 1
|
||||
github.com/muety/wakapi/utils/date.go:87.19,89.4 1 1
|
||||
github.com/muety/wakapi/utils/date.go:97.50,103.2 5 0
|
||||
github.com/muety/wakapi/utils/date.go:106.79,109.36 3 1
|
||||
github.com/muety/wakapi/utils/date.go:113.2,113.21 1 1
|
||||
github.com/muety/wakapi/utils/date.go:117.2,117.21 1 1
|
||||
github.com/muety/wakapi/utils/date.go:121.2,121.13 1 1
|
||||
github.com/muety/wakapi/utils/date.go:109.36,112.3 2 0
|
||||
github.com/muety/wakapi/utils/date.go:113.21,116.3 2 1
|
||||
github.com/muety/wakapi/utils/date.go:117.21,120.3 2 1
|
||||
github.com/muety/wakapi/utils/summary.go:10.66,11.40 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:16.2,16.48 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:11.40,12.27 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:12.27,14.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:19.88,22.2 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:24.95,26.16 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:29.2,29.38 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:26.16,28.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:32.105,35.18 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:70.2,70.22 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:36.28,37.26 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:38.32,40.24 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:41.31,42.29 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:43.31,45.27 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:46.32,47.30 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:48.32,50.28 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:51.31,52.29 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:53.32,54.44 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:55.41,57.42 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:58.33,59.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:60.33,61.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:62.35,63.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:64.26,65.21 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:66.10,67.39 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:73.73,80.56 5 0
|
||||
github.com/muety/wakapi/utils/summary.go:96.2,103.8 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:80.56,82.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.8,82.54 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.54,84.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:84.8,86.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:90.3,91.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:86.17,88.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:91.17,93.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:106.48,110.51 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:113.2,113.12 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:110.51,112.3 1 0
|
||||
github.com/muety/wakapi/config/db.go:39.50,40.19 1 0
|
||||
github.com/muety/wakapi/config/db.go:53.2,53.12 1 0
|
||||
github.com/muety/wakapi/config/db.go:41.23,45.5 1 0
|
||||
github.com/muety/wakapi/config/db.go:46.26,49.5 1 0
|
||||
github.com/muety/wakapi/config/db.go:50.24,51.48 1 0
|
||||
github.com/muety/wakapi/config/db.go:56.53,66.2 1 1
|
||||
github.com/muety/wakapi/config/db.go:68.56,70.16 2 1
|
||||
github.com/muety/wakapi/config/db.go:74.2,81.3 1 1
|
||||
github.com/muety/wakapi/config/db.go:70.16,72.3 1 0
|
||||
github.com/muety/wakapi/config/db.go:84.54,86.2 1 1
|
||||
github.com/muety/wakapi/config/eventbus.go:18.13,20.2 1 1
|
||||
github.com/muety/wakapi/config/eventbus.go:22.26,24.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
|
||||
@ -397,19 +225,71 @@ github.com/muety/wakapi/config/config.go:312.20,314.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:316.35,321.96 3 0
|
||||
github.com/muety/wakapi/config/config.go:325.2,334.52 6 0
|
||||
github.com/muety/wakapi/config/config.go:338.2,338.47 1 0
|
||||
github.com/muety/wakapi/config/config.go:344.2,344.70 1 0
|
||||
github.com/muety/wakapi/config/config.go:348.2,348.28 1 0
|
||||
github.com/muety/wakapi/config/config.go:352.2,352.29 1 0
|
||||
github.com/muety/wakapi/config/config.go:357.2,357.94 1 0
|
||||
github.com/muety/wakapi/config/config.go:361.2,362.14 2 0
|
||||
github.com/muety/wakapi/config/config.go:344.2,344.29 1 0
|
||||
github.com/muety/wakapi/config/config.go:350.2,350.70 1 0
|
||||
github.com/muety/wakapi/config/config.go:353.2,353.28 1 0
|
||||
github.com/muety/wakapi/config/config.go:356.2,356.94 1 0
|
||||
github.com/muety/wakapi/config/config.go:359.2,359.81 1 0
|
||||
github.com/muety/wakapi/config/config.go:362.2,362.75 1 0
|
||||
github.com/muety/wakapi/config/config.go:366.2,367.14 2 0
|
||||
github.com/muety/wakapi/config/config.go:321.96,323.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:334.52,336.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:338.47,339.14 1 0
|
||||
github.com/muety/wakapi/config/config.go:339.14,341.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:344.70,346.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:348.28,350.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:352.29,355.3 2 0
|
||||
github.com/muety/wakapi/config/config.go:357.94,359.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:344.29,347.3 2 0
|
||||
github.com/muety/wakapi/config/config.go:350.70,352.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:353.28,355.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:356.94,358.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:359.81,361.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:362.75,364.3 1 0
|
||||
github.com/muety/wakapi/config/db.go:39.50,40.19 1 0
|
||||
github.com/muety/wakapi/config/db.go:53.2,53.12 1 0
|
||||
github.com/muety/wakapi/config/db.go:41.23,45.5 1 0
|
||||
github.com/muety/wakapi/config/db.go:46.26,49.5 1 0
|
||||
github.com/muety/wakapi/config/db.go:50.24,51.48 1 0
|
||||
github.com/muety/wakapi/config/db.go:56.53,66.2 1 1
|
||||
github.com/muety/wakapi/config/db.go:68.56,70.16 2 1
|
||||
github.com/muety/wakapi/config/db.go:74.2,81.3 1 1
|
||||
github.com/muety/wakapi/config/db.go:70.16,72.3 1 0
|
||||
github.com/muety/wakapi/config/db.go:84.54,86.2 1 1
|
||||
github.com/muety/wakapi/config/eventbus.go:18.13,20.2 1 1
|
||||
github.com/muety/wakapi/config/eventbus.go:22.26,24.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/middlewares/authenticate.go:19.91,25.2 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:27.90,30.2 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:32.90,35.2 2 0
|
||||
@ -493,80 +373,136 @@ 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/services/language_mapping.go:18.118,24.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
|
||||
github.com/muety/wakapi/services/misc.go:23.126,30.2 1 0
|
||||
github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
|
||||
github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
|
||||
github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
|
||||
github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
|
||||
github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
|
||||
github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
|
||||
github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
|
||||
github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
|
||||
github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
|
||||
github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
|
||||
github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
|
||||
github.com/muety/wakapi/services/misc.go:81.24,82.151 1 0
|
||||
github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
|
||||
github.com/muety/wakapi/services/misc.go:82.151,84.4 1 0
|
||||
github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
|
||||
github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
|
||||
github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
|
||||
github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
|
||||
github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
|
||||
github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
|
||||
github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
|
||||
github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:21.73,28.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:30.74,31.40 1 0
|
||||
github.com/muety/wakapi/services/user.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/user.go:40.2,41.15 2 0
|
||||
github.com/muety/wakapi/services/user.go:31.40,33.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:44.72,45.37 1 0
|
||||
github.com/muety/wakapi/services/user.go:49.2,50.16 2 0
|
||||
github.com/muety/wakapi/services/user.go:54.2,55.15 2 0
|
||||
github.com/muety/wakapi/services/user.go:45.37,47.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:50.16,52.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:58.76,60.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:62.86,64.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:66.58,68.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:70.86,72.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:74.61,77.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:79.48,81.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:83.102,93.93 2 0
|
||||
github.com/muety/wakapi/services/user.go:99.2,99.38 1 0
|
||||
github.com/muety/wakapi/services/user.go:93.93,95.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:95.8,97.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:102.73,106.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:108.78,112.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:114.99,117.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:119.106,122.96 3 0
|
||||
github.com/muety/wakapi/services/user.go:127.2,127.68 1 0
|
||||
github.com/muety/wakapi/services/user.go:122.96,124.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:124.8,126.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:130.85,132.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:134.57,141.2 4 0
|
||||
github.com/muety/wakapi/services/user.go:143.38,145.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:147.57,152.2 1 0
|
||||
github.com/muety/wakapi/utils/color.go:8.90,10.32 2 0
|
||||
github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0
|
||||
github.com/muety/wakapi/utils/color.go:10.32,11.50 1 0
|
||||
github.com/muety/wakapi/utils/color.go:11.50,13.4 1 0
|
||||
github.com/muety/wakapi/utils/common.go:18.73,19.58 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.2,22.87 1 0
|
||||
github.com/muety/wakapi/utils/common.go:25.2,25.64 1 0
|
||||
github.com/muety/wakapi/utils/common.go:19.58,21.3 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.87,24.3 1 0
|
||||
github.com/muety/wakapi/utils/common.go:28.40,30.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:32.44,34.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:36.49,38.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:40.45,42.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:44.24,46.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:48.56,51.45 3 1
|
||||
github.com/muety/wakapi/utils/common.go:54.2,54.40 1 1
|
||||
github.com/muety/wakapi/utils/common.go:51.45,53.3 1 1
|
||||
github.com/muety/wakapi/utils/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.41,61.21 2 1
|
||||
github.com/muety/wakapi/utils/date.go:64.2,64.36 1 1
|
||||
github.com/muety/wakapi/utils/date.go:61.21,63.3 1 1
|
||||
github.com/muety/wakapi/utils/date.go:68.63,70.2 1 0
|
||||
github.com/muety/wakapi/utils/date.go:73.62,79.2 5 0
|
||||
github.com/muety/wakapi/utils/date.go:82.67,85.33 2 1
|
||||
github.com/muety/wakapi/utils/date.go:94.2,94.18 1 1
|
||||
github.com/muety/wakapi/utils/date.go:85.33,87.19 2 1
|
||||
github.com/muety/wakapi/utils/date.go:90.3,91.10 2 1
|
||||
github.com/muety/wakapi/utils/date.go:87.19,89.4 1 1
|
||||
github.com/muety/wakapi/utils/date.go:97.50,103.2 5 0
|
||||
github.com/muety/wakapi/utils/date.go:106.79,109.36 3 1
|
||||
github.com/muety/wakapi/utils/date.go:113.2,113.21 1 1
|
||||
github.com/muety/wakapi/utils/date.go:117.2,117.21 1 1
|
||||
github.com/muety/wakapi/utils/date.go:121.2,121.13 1 1
|
||||
github.com/muety/wakapi/utils/date.go:109.36,112.3 2 0
|
||||
github.com/muety/wakapi/utils/date.go:113.21,116.3 2 1
|
||||
github.com/muety/wakapi/utils/date.go:117.21,120.3 2 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/summary.go:10.66,11.40 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:16.2,16.48 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:11.40,12.27 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:12.27,14.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:19.88,22.2 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:24.95,26.16 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:29.2,29.38 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:26.16,28.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:32.105,35.18 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:70.2,70.22 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:36.28,37.26 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:38.32,40.24 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:41.31,42.29 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:43.31,45.27 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:46.32,47.30 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:48.32,50.28 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:51.31,52.29 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:53.32,54.44 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:55.41,57.42 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:58.33,59.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:60.33,61.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:62.35,63.45 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:64.26,65.21 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:66.10,67.39 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:73.73,80.56 5 0
|
||||
github.com/muety/wakapi/utils/summary.go:96.2,103.8 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:80.56,82.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.8,82.54 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.54,84.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:84.8,86.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:90.3,91.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:86.17,88.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:91.17,93.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:106.48,110.51 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:113.2,113.12 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:110.51,112.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:37.65,39.85 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:43.2,44.30 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:39.85,41.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:47.94,49.16 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:53.2,53.107 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:57.2,57.22 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:49.16,51.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:53.107,55.3 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:60.56,64.2 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:66.55,69.16 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:72.2,72.16 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:69.16,71.3 1 0
|
||||
github.com/muety/wakapi/utils/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/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:8.41,10.16 2 0
|
||||
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
|
||||
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
|
||||
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
|
||||
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
|
||||
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
|
||||
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
|
||||
@ -602,27 +538,6 @@ github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
|
||||
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
|
||||
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:29.80,34.32 3 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:41.2,41.55 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:34.32,35.36 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:35.36,38.4 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:44.53,46.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:48.76,50.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:52.96,54.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:56.111,58.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:61.2,61.43 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:64.92,66.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:68.116,70.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:72.78,74.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:76.62,78.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:80.116,82.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:86.2,86.28 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:90.2,90.24 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:82.16,84.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:86.28,88.3 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
|
||||
@ -630,6 +545,66 @@ 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/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:21.73,28.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:30.74,31.40 1 0
|
||||
github.com/muety/wakapi/services/user.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/user.go:40.2,41.15 2 0
|
||||
github.com/muety/wakapi/services/user.go:31.40,33.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:44.72,45.37 1 0
|
||||
github.com/muety/wakapi/services/user.go:49.2,50.16 2 0
|
||||
github.com/muety/wakapi/services/user.go:54.2,55.15 2 0
|
||||
github.com/muety/wakapi/services/user.go:45.37,47.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:50.16,52.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:58.76,60.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:62.86,64.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:66.58,68.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:70.86,72.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:74.61,77.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:79.48,81.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:83.102,93.93 2 0
|
||||
github.com/muety/wakapi/services/user.go:99.2,99.38 1 0
|
||||
github.com/muety/wakapi/services/user.go:93.93,95.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:95.8,97.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:102.73,106.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:108.78,112.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:114.99,117.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:119.106,122.96 3 0
|
||||
github.com/muety/wakapi/services/user.go:127.2,127.68 1 0
|
||||
github.com/muety/wakapi/services/user.go:122.96,124.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:124.8,126.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:130.85,132.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:134.57,141.2 4 0
|
||||
github.com/muety/wakapi/services/user.go:143.38,145.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:147.57,152.2 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:29.142,37.2 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:46.43,48.37 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:52.2,54.19 3 0
|
||||
@ -680,28 +655,78 @@ github.com/muety/wakapi/services/aggregation.go:176.27,178.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:181.83,196.41 5 0
|
||||
github.com/muety/wakapi/services/aggregation.go:196.41,206.3 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:209.34,212.2 2 0
|
||||
github.com/muety/wakapi/services/report.go:24.122,35.33 3 0
|
||||
github.com/muety/wakapi/services/report.go:41.2,41.12 1 0
|
||||
github.com/muety/wakapi/services/report.go:35.33,36.31 1 0
|
||||
github.com/muety/wakapi/services/report.go:36.31,38.4 1 0
|
||||
github.com/muety/wakapi/services/report.go:44.38,48.16 3 0
|
||||
github.com/muety/wakapi/services/report.go:52.2,53.26 2 0
|
||||
github.com/muety/wakapi/services/report.go:48.16,50.3 1 0
|
||||
github.com/muety/wakapi/services/report.go:53.26,55.3 1 0
|
||||
github.com/muety/wakapi/services/report.go:60.61,65.65 3 0
|
||||
github.com/muety/wakapi/services/report.go:73.2,73.65 1 0
|
||||
github.com/muety/wakapi/services/report.go:85.2,85.24 1 0
|
||||
github.com/muety/wakapi/services/report.go:65.65,70.3 4 0
|
||||
github.com/muety/wakapi/services/report.go:73.65,83.3 4 0
|
||||
github.com/muety/wakapi/services/report.go:88.80,89.22 1 0
|
||||
github.com/muety/wakapi/services/report.go:93.2,93.29 1 0
|
||||
github.com/muety/wakapi/services/report.go:98.2,102.16 4 0
|
||||
github.com/muety/wakapi/services/report.go:107.2,114.65 2 0
|
||||
github.com/muety/wakapi/services/report.go:119.2,120.12 2 0
|
||||
github.com/muety/wakapi/services/report.go:89.22,91.3 1 0
|
||||
github.com/muety/wakapi/services/report.go:93.29,96.3 2 0
|
||||
github.com/muety/wakapi/services/report.go:102.16,105.3 2 0
|
||||
github.com/muety/wakapi/services/report.go:114.65,117.3 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:21.141,28.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:30.72,33.2 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:35.80,40.32 3 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:48.2,48.55 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:40.32,41.36 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:45.3,45.43 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:41.36,44.4 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:51.53,53.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:55.76,57.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:59.96,61.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:63.111,65.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:68.2,68.43 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:65.16,67.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:71.92,73.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:75.116,77.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:79.78,81.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:83.104,85.54 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:89.2,90.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:93.2,94.21 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:85.54,87.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:90.16,92.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:97.62,99.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:101.116,103.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:107.2,107.28 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:111.2,111.24 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:103.16,105.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:107.28,109.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:114.96,116.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:118.107,120.55 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:120.55,121.58 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:121.58,125.4 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:129.85,135.2 5 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
|
||||
github.com/muety/wakapi/services/misc.go: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/summary.go:28.149,36.2 1 1
|
||||
github.com/muety/wakapi/services/summary.go:40.136,43.66 2 1
|
||||
github.com/muety/wakapi/services/summary.go:48.2,48.44 1 1
|
||||
|
2
main.go
2
main.go
@ -168,6 +168,7 @@ func main() {
|
||||
wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(userService, summaryService)
|
||||
wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(userService, summaryService)
|
||||
wakatimeV1UsersHandler := wtV1Routes.NewUsersHandler(userService, heartbeatService)
|
||||
wakatimeV1ProjectsHandler := wtV1Routes.NewProjectsHandler(userService, heartbeatService)
|
||||
shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService)
|
||||
|
||||
// MVC Handlers
|
||||
@ -207,6 +208,7 @@ func main() {
|
||||
wakatimeV1SummariesHandler.RegisterRoutes(apiRouter)
|
||||
wakatimeV1StatsHandler.RegisterRoutes(apiRouter)
|
||||
wakatimeV1UsersHandler.RegisterRoutes(apiRouter)
|
||||
wakatimeV1ProjectsHandler.RegisterRoutes(apiRouter)
|
||||
shieldV1BadgeHandler.RegisterRoutes(apiRouter)
|
||||
|
||||
// Static Routes
|
||||
|
@ -55,6 +55,11 @@ func (m *HeartbeatServiceMock) GetLatestByOriginAndUser(s string, user *models.U
|
||||
return args.Get(0).(*models.Heartbeat), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *HeartbeatServiceMock) GetEntitySetByUser(u uint8, user *models.User) ([]string, error) {
|
||||
args := m.Called(u, user)
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *HeartbeatServiceMock) DeleteBefore(time time.Time) error {
|
||||
args := m.Called(time)
|
||||
return args.Error(0)
|
||||
|
11
models/compat/wakatime/v1/project.go
Normal file
11
models/compat/wakatime/v1/project.go
Normal file
@ -0,0 +1,11 @@
|
||||
package v1
|
||||
|
||||
type ProjectsViewModel struct {
|
||||
Data []*Project `json:"data"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Repository string `json:"repository"`
|
||||
}
|
@ -31,9 +31,6 @@ type StatsData struct {
|
||||
func NewStatsFrom(summary *models.Summary, filters *models.Filters) *StatsViewModel {
|
||||
totalTime := summary.TotalTime()
|
||||
numDays := int(summary.ToTime.T().Sub(summary.FromTime.T()).Hours() / 24)
|
||||
if math.IsInf(float64(numDays), 0) {
|
||||
numDays = 0
|
||||
}
|
||||
|
||||
data := &StatsData{
|
||||
Username: summary.UserID,
|
||||
@ -45,6 +42,10 @@ func NewStatsFrom(summary *models.Summary, filters *models.Filters) *StatsViewMo
|
||||
DaysIncludingHolidays: numDays,
|
||||
}
|
||||
|
||||
if math.IsInf(data.DailyAverage, 0) || math.IsNaN(data.DailyAverage) {
|
||||
data.DailyAverage = 0
|
||||
}
|
||||
|
||||
editors := make([]*SummariesEntry, len(summary.Editors))
|
||||
for i, e := range summary.Editors {
|
||||
editors[i] = convertEntry(e, summary.TotalTimeBy(models.SummaryEditor))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@ -63,6 +64,7 @@ func (r *HeartbeatRepository) GetLatestByOriginAndUser(origin string, user *mode
|
||||
}
|
||||
|
||||
func (r *HeartbeatRepository) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
|
||||
// https://stackoverflow.com/a/20765152/3112139
|
||||
var heartbeats []*models.Heartbeat
|
||||
if err := r.db.
|
||||
Where(&models.Heartbeat{UserID: user.ID}).
|
||||
@ -125,9 +127,8 @@ func (r *HeartbeatRepository) CountByUsers(users []*models.User) ([]*models.Coun
|
||||
}
|
||||
|
||||
if err := r.db.
|
||||
Model(&models.User{}).
|
||||
Select("users.id as user, count(heartbeats.id) as count").
|
||||
Joins("left join heartbeats on users.id = heartbeats.user_id").
|
||||
Model(&models.Heartbeat{}).
|
||||
Select("user_id as user, count(id) as count").
|
||||
Where("user_id in ?", userIds).
|
||||
Group("user").
|
||||
Find(&counts).Error; err != nil {
|
||||
@ -136,6 +137,24 @@ func (r *HeartbeatRepository) CountByUsers(users []*models.User) ([]*models.Coun
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func (r HeartbeatRepository) GetEntitySetByUser(entityType uint8, user *models.User) ([]string, error) {
|
||||
columns := []string{"project", "language", "editor", "operating_system", "machine"}
|
||||
if int(entityType) >= len(columns) {
|
||||
// invalid entity type
|
||||
return nil, errors.New("invalid entity type")
|
||||
}
|
||||
|
||||
var results []string
|
||||
if err := r.db.
|
||||
Model(&models.Heartbeat{}).
|
||||
Distinct(columns[entityType]).
|
||||
Where(&models.Heartbeat{UserID: user.ID}).
|
||||
Find(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *HeartbeatRepository) DeleteBefore(t time.Time) error {
|
||||
if err := r.db.
|
||||
Where("time <= ?", t.Local()).
|
||||
|
@ -27,6 +27,7 @@ type IHeartbeatRepository interface {
|
||||
Count() (int64, error)
|
||||
CountByUser(*models.User) (int64, error)
|
||||
CountByUsers([]*models.User) ([]*models.CountByUser, error)
|
||||
GetEntitySetByUser(uint8, *models.User) ([]string, error)
|
||||
DeleteBefore(time.Time) error
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
customMiddleware "github.com/muety/wakapi/middlewares/custom"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
@ -34,12 +35,14 @@ type heartbeatResponseVm struct {
|
||||
}
|
||||
|
||||
func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
|
||||
r := router.PathPrefix("/heartbeat").Subrouter()
|
||||
r := router.PathPrefix("").Subrouter()
|
||||
r.Use(
|
||||
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
|
||||
customMiddleware.NewWakatimeRelayMiddleware().Handler,
|
||||
)
|
||||
r.Path("").Methods(http.MethodPost).HandlerFunc(h.Post)
|
||||
r.PathPrefix("/heartbeat").Methods(http.MethodPost).HandlerFunc(h.Post)
|
||||
r.PathPrefix("/v1/users/{user}/heartbeats").Methods(http.MethodPost).HandlerFunc(h.Post)
|
||||
r.PathPrefix("/compat/wakatime/v1/users/{user}/heartbeats").Methods(http.MethodPost).HandlerFunc(h.Post)
|
||||
}
|
||||
|
||||
// @Summary Push a new heartbeat
|
||||
@ -51,8 +54,12 @@ func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Success 201
|
||||
// @Router /heartbeat [post]
|
||||
func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
var heartbeats []*models.Heartbeat
|
||||
user := middlewares.GetPrincipal(r)
|
||||
opSys, editor, _ := utils.ParseUserAgent(r.Header.Get("User-Agent"))
|
||||
machineName := r.Header.Get("X-Machine-Name")
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
@ -45,18 +46,14 @@ func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Success 200 {object} v1.AllTimeViewModel
|
||||
// @Router /compat/wakatime/v1/users/{user}/all_time_since_today [get]
|
||||
func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
values, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
|
||||
requestedUser := vars["user"]
|
||||
authorizedUser := middlewares.GetPrincipal(r)
|
||||
|
||||
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(authorizedUser)
|
||||
summary, err, status := h.loadUserSummary(user)
|
||||
if err != nil {
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
73
routes/compat/wakatime/v1/projects.go
Normal file
73
routes/compat/wakatime/v1/projects.go
Normal file
@ -0,0 +1,73 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ProjectsHandler struct {
|
||||
config *conf.Config
|
||||
userSrvc services.IUserService
|
||||
heartbeatSrvc services.IHeartbeatService
|
||||
}
|
||||
|
||||
func NewProjectsHandler(userService services.IUserService, heartbeatsService services.IHeartbeatService) *ProjectsHandler {
|
||||
return &ProjectsHandler{
|
||||
userSrvc: userService,
|
||||
heartbeatSrvc: heartbeatsService,
|
||||
config: conf.Get(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProjectsHandler) RegisterRoutes(router *mux.Router) {
|
||||
r := router.PathPrefix("/compat/wakatime/v1/users/{user}/projects").Subrouter()
|
||||
r.Use(
|
||||
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
|
||||
)
|
||||
r.Path("").Methods(http.MethodGet).HandlerFunc(h.Get)
|
||||
}
|
||||
|
||||
// @Summary Retrieve and fitler the user's projects
|
||||
// @Description Mimics https://wakatime.com/developers#projects
|
||||
// @ID get-wakatime-projects
|
||||
// @Tags wakatime
|
||||
// @Produce json
|
||||
// @Param user path string true "User ID to fetch data for (or 'current')"
|
||||
// @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]
|
||||
func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
results, err := h.heartbeatSrvc.GetEntitySetByUser(models.SummaryProject, user)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("something went wrong"))
|
||||
conf.Log().Request(r).Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query().Get("q")
|
||||
|
||||
projects := make([]*v1.Project, 0, len(results))
|
||||
for _, p := range results {
|
||||
if strings.HasPrefix(p, q) {
|
||||
projects = append(projects, &v1.Project{ID: p, Name: p})
|
||||
}
|
||||
}
|
||||
|
||||
vm := &v1.ProjectsViewModel{Data: projects}
|
||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
@ -36,7 +37,7 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
|
||||
r.Path("").Methods(http.MethodGet).HandlerFunc(h.Get)
|
||||
}
|
||||
|
||||
// TODO: Support parameters: project, branches, timeout, writes_only, timezone
|
||||
// TODO: Support parameters: project, branches, timeout, writes_only
|
||||
// See https://wakatime.com/developers#summaries.
|
||||
// Timezone can be specified via an offset suffix (e.g. +02:00) in date strings.
|
||||
// Requires https://github.com/muety/wakapi/issues/108.
|
||||
@ -54,13 +55,9 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Success 200 {object} v1.SummariesViewModel
|
||||
// @Router /compat/wakatime/v1/users/{user}/summaries [get]
|
||||
func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
requestedUser := vars["user"]
|
||||
authorizedUser := middlewares.GetPrincipal(r)
|
||||
|
||||
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
_, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
summaries, err, status := h.loadUserSummaries(r)
|
||||
@ -82,35 +79,42 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
||||
user := middlewares.GetPrincipal(r)
|
||||
params := r.URL.Query()
|
||||
rangeParam, startParam, endParam := params.Get("range"), params.Get("start"), params.Get("end")
|
||||
rangeParam, startParam, endParam, tzParam := params.Get("range"), params.Get("start"), params.Get("end"), params.Get("timezone")
|
||||
|
||||
timezone := user.TZ()
|
||||
if tzParam != "" {
|
||||
if tz, err := time.LoadLocation(tzParam); err == nil {
|
||||
timezone = tz
|
||||
}
|
||||
}
|
||||
|
||||
var start, end time.Time
|
||||
if rangeParam != "" {
|
||||
// range param takes precedence
|
||||
if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(rangeParam, user.TZ()); err == nil {
|
||||
if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(rangeParam, timezone); err == nil {
|
||||
start, end = parsedFrom, parsedTo
|
||||
} else {
|
||||
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
|
||||
}
|
||||
} else if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(startParam, user.TZ()); err == nil && startParam == endParam {
|
||||
} else if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(startParam, timezone); err == nil && startParam == endParam {
|
||||
// also accept start param to be a range param
|
||||
start, end = parsedFrom, parsedTo
|
||||
} else {
|
||||
// eventually, consider start and end params a date
|
||||
var err error
|
||||
|
||||
start, err = utils.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), user.TZ())
|
||||
start, err = utils.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), timezone)
|
||||
if err != nil {
|
||||
return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
end, err = utils.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), user.TZ())
|
||||
end, err = utils.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), timezone)
|
||||
if err != nil {
|
||||
return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest
|
||||
}
|
||||
}
|
||||
|
||||
// wakatime iterprets end date as "inclusive", wakapi usually as "exclusive"
|
||||
// wakatime interprets end date as "inclusive", wakapi usually as "exclusive"
|
||||
// i.e. for wakatime, an interval 2021-04-29 - 2021-04-29 is actually 2021-04-29 - 2021-04-30,
|
||||
// while for wakapi it would be empty
|
||||
// see https://github.com/muety/wakapi/issues/192
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||
routeutils "github.com/muety/wakapi/routes/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
@ -42,18 +43,13 @@ func (h *UsersHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Success 200 {object} v1.UserViewModel
|
||||
// @Router /compat/wakatime/v1/users/{user} [get]
|
||||
func (h *UsersHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
requestedUser := vars["user"]
|
||||
authorizedUser := middlewares.GetPrincipal(r)
|
||||
|
||||
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
wakapiUser, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||
if err != nil {
|
||||
return // response was already sent by util function
|
||||
}
|
||||
|
||||
user := v1.NewFromUser(authorizedUser)
|
||||
if hb, err := h.heartbeatSrvc.GetLatestByUser(authorizedUser); err == nil {
|
||||
user := v1.NewFromUser(wakapiUser)
|
||||
if hb, err := h.heartbeatSrvc.GetLatestByUser(wakapiUser); err == nil {
|
||||
user = user.WithLatestHeartbeat(hb)
|
||||
} else {
|
||||
conf.Log().Request(r).Error("%v", err)
|
||||
|
@ -450,6 +450,13 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
|
||||
|
||||
h.regenerateSummaries(user)
|
||||
|
||||
if !user.HasData {
|
||||
user.HasData = true
|
||||
if _, err := h.userSrvc.Update(user); err != nil {
|
||||
conf.Log().Request(r).Error("failed to set 'has_data' flag for user %s – %v", user.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if user.Email != "" {
|
||||
if err := h.mailSrvc.SendImportNotification(user, time.Now().Sub(start), int(countAfter-countBefore)); err != nil {
|
||||
conf.Log().Request(r).Error("failed to send import notification mail to %s – %v", user.ID, err)
|
||||
|
46
routes/utils/user_utils.go
Normal file
46
routes/utils/user_utils.go
Normal file
@ -0,0 +1,46 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CheckEffectiveUser extracts the requested user from a URL (like '/users/{user}'), compares it with the currently authorized user and writes an HTTP error if they differ.
|
||||
// Fallback can be used to manually set a value for '{user}' if none is present.
|
||||
func CheckEffectiveUser(w http.ResponseWriter, r *http.Request, userService services.IUserService, fallback string) (*models.User, error) {
|
||||
var vars = mux.Vars(r)
|
||||
var authorizedUser, requestedUser *models.User
|
||||
|
||||
if vars["user"] == "" {
|
||||
vars["user"] = fallback
|
||||
}
|
||||
|
||||
authorizedUser = middlewares.GetPrincipal(r)
|
||||
if authorizedUser != nil {
|
||||
if vars["user"] == "current" {
|
||||
vars["user"] = authorizedUser.ID
|
||||
}
|
||||
}
|
||||
|
||||
requestedUser, err := userService.GetUserById(vars["user"])
|
||||
if err != nil {
|
||||
err := errors.New("user not found")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if authorizedUser == nil || authorizedUser.ID != requestedUser.ID {
|
||||
err := errors.New(conf.ErrUnauthorized)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authorizedUser, nil
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secretpassword -e MYSQL_DATABASE=wakapi_local -e MYSQL_USER=wakapi_user -e MYSQL_PASSWORD=wakapi --name wakapi-mysql mysql:5
|
||||
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secretpassword -e MYSQL_DATABASE=wakapi_local -e MYSQL_USER=wakapi_user -e MYSQL_PASSWORD=wakapi --name wakapi-mysql mysql:8
|
@ -1,8 +1,11 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"time"
|
||||
|
||||
"github.com/muety/wakapi/models"
|
||||
@ -10,6 +13,7 @@ import (
|
||||
|
||||
type HeartbeatService struct {
|
||||
config *config.Config
|
||||
cache *cache.Cache
|
||||
repository repositories.IHeartbeatRepository
|
||||
languageMappingSrvc ILanguageMappingService
|
||||
}
|
||||
@ -17,12 +21,14 @@ type HeartbeatService struct {
|
||||
func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, languageMappingService ILanguageMappingService) *HeartbeatService {
|
||||
return &HeartbeatService{
|
||||
config: config.Get(),
|
||||
cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
repository: heartbeatRepo,
|
||||
languageMappingSrvc: languageMappingService,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) Insert(heartbeat *models.Heartbeat) error {
|
||||
srv.updateEntityUserCacheByHeartbeat(heartbeat)
|
||||
return srv.repository.InsertBatch([]*models.Heartbeat{heartbeat})
|
||||
}
|
||||
|
||||
@ -36,6 +42,7 @@ func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
|
||||
filteredHeartbeats = append(filteredHeartbeats, hb)
|
||||
hashes[hb.Hash] = true
|
||||
}
|
||||
srv.updateEntityUserCacheByHeartbeat(hb)
|
||||
}
|
||||
|
||||
return srv.repository.InsertBatch(filteredHeartbeats)
|
||||
@ -73,6 +80,20 @@ func (srv *HeartbeatService) GetFirstByUsers() ([]*models.TimeByUser, error) {
|
||||
return srv.repository.GetFirstByUsers()
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) GetEntitySetByUser(entityType uint8, user *models.User) ([]string, error) {
|
||||
cacheKey := srv.getEntityUserCacheKey(entityType, user)
|
||||
if results, found := srv.cache.Get(cacheKey); found {
|
||||
return utils.SetToStrings(results.(map[string]bool)), nil
|
||||
}
|
||||
|
||||
results, err := srv.repository.GetEntitySetByUser(entityType, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.cache.Set(cacheKey, utils.StringsToSet(results), cache.DefaultExpiration)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) DeleteBefore(t time.Time) error {
|
||||
return srv.repository.DeleteBefore(t)
|
||||
}
|
||||
@ -89,3 +110,26 @@ func (srv *HeartbeatService) augmented(heartbeats []*models.Heartbeat, userId st
|
||||
|
||||
return heartbeats, nil
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) getEntityUserCacheKey(entityType uint8, user *models.User) string {
|
||||
return fmt.Sprintf("entity_set_%d_%s", entityType, user.ID)
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) updateEntityUserCache(entityType uint8, entityKey string, user *models.User) {
|
||||
cacheKey := srv.getEntityUserCacheKey(entityType, user)
|
||||
if entities, found := srv.cache.Get(cacheKey); found {
|
||||
if _, ok := entities.(map[string]bool)[entityKey]; !ok {
|
||||
// new project / language / ..., which is not yet present in cache, arrived as part of a heartbeats
|
||||
// -> invalidate cache
|
||||
srv.cache.Delete(cacheKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -6,31 +6,40 @@ import (
|
||||
"github.com/leandro-lugaresi/hub"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var reportLock = sync.Mutex{}
|
||||
|
||||
// range for random offset to add / subtract when scheduling a new job
|
||||
// to avoid all mails being sent at once, but distributed over 2*offsetIntervalMin minutes
|
||||
const offsetIntervalMin = 15
|
||||
|
||||
type ReportService struct {
|
||||
config *config.Config
|
||||
eventBus *hub.Hub
|
||||
summaryService ISummaryService
|
||||
userService IUserService
|
||||
mailService IMailService
|
||||
schedulersWeekly map[string]*gocron.Scheduler // user id -> scheduler
|
||||
config *config.Config
|
||||
eventBus *hub.Hub
|
||||
summaryService ISummaryService
|
||||
userService IUserService
|
||||
mailService IMailService
|
||||
scheduler *gocron.Scheduler
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
func NewReportService(summaryService ISummaryService, userService IUserService, mailService IMailService) *ReportService {
|
||||
srv := &ReportService{
|
||||
config: config.Get(),
|
||||
eventBus: config.EventBus(),
|
||||
summaryService: summaryService,
|
||||
userService: userService,
|
||||
mailService: mailService,
|
||||
schedulersWeekly: map[string]*gocron.Scheduler{},
|
||||
config: config.Get(),
|
||||
eventBus: config.EventBus(),
|
||||
summaryService: summaryService,
|
||||
userService: userService,
|
||||
mailService: mailService,
|
||||
scheduler: gocron.NewScheduler(time.Local),
|
||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
}
|
||||
|
||||
srv.scheduler.StartAsync()
|
||||
|
||||
sub := srv.eventBus.Subscribe(0, config.EventUserUpdate)
|
||||
go func(sub *hub.Subscription) {
|
||||
for m := range sub.Receiver {
|
||||
@ -62,24 +71,24 @@ func (srv *ReportService) SyncSchedule(u *models.User) bool {
|
||||
defer reportLock.Unlock()
|
||||
|
||||
// unschedule
|
||||
if s, ok := srv.schedulersWeekly[u.ID]; ok && !u.ReportsWeekly {
|
||||
s.Stop()
|
||||
s.Clear()
|
||||
delete(srv.schedulersWeekly, u.ID)
|
||||
if !u.ReportsWeekly {
|
||||
_ = srv.scheduler.RemoveByTag(u.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
// schedule
|
||||
if _, ok := srv.schedulersWeekly[u.ID]; !ok && u.ReportsWeekly {
|
||||
s := gocron.NewScheduler(u.TZ())
|
||||
s.
|
||||
if j := srv.getJobByTag(u.ID); j == nil && u.ReportsWeekly {
|
||||
t, _ := time.ParseInLocation("15:04", srv.config.App.GetWeeklyReportTime(), u.TZ())
|
||||
t = t.Add(time.Duration(srv.rand.Intn(offsetIntervalMin)*srv.rand.Intn(2)) * time.Minute)
|
||||
if _, err := srv.scheduler.
|
||||
Every(1).
|
||||
Week().
|
||||
Weekday(srv.config.App.GetWeeklyReportDay()).
|
||||
At(srv.config.App.GetWeeklyReportTime()).
|
||||
Do(srv.Run, u, 7*24*time.Hour)
|
||||
s.StartAsync()
|
||||
srv.schedulersWeekly[u.ID] = s
|
||||
At(t).
|
||||
Tag(u.ID).
|
||||
Do(srv.Run, u, 7*24*time.Hour); err != nil {
|
||||
config.Log().Error("failed to schedule report job for user '%s' – %v", u.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return u.ReportsWeekly
|
||||
@ -119,3 +128,14 @@ func (srv *ReportService) Run(user *models.User, duration time.Duration) error {
|
||||
logbuch.Info("sent report to user '%s'", user.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *ReportService) getJobByTag(tag string) *gocron.Job {
|
||||
for _, j := range srv.scheduler.Jobs() {
|
||||
for _, t := range j.Tags() {
|
||||
if t == tag {
|
||||
return j
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ type IHeartbeatService interface {
|
||||
GetFirstByUsers() ([]*models.TimeByUser, error)
|
||||
GetLatestByUser(*models.User) (*models.Heartbeat, error)
|
||||
GetLatestByOriginAndUser(string, *models.User) (*models.Heartbeat, error)
|
||||
GetEntitySetByUser(uint8, *models.User) ([]string, error)
|
||||
DeleteBefore(time.Time) error
|
||||
}
|
||||
|
||||
|
@ -160,6 +160,48 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/compat/wakatime/v1/users/{user}/projects": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Mimics https://wakatime.com/developers#projects",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"wakatime"
|
||||
],
|
||||
"summary": "Retrieve and fitler the user's projects",
|
||||
"operationId": "get-wakatime-projects",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID to fetch data for (or 'current')",
|
||||
"name": "user",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Query to filter projects by",
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ProjectsViewModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/compat/wakatime/v1/users/{user}/stats/{range}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -574,6 +616,31 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.Project": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"repository": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ProjectsViewModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1.Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.StatsData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -144,6 +144,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/compat/wakatime/v1/users/{user}/projects": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Mimics https://wakatime.com/developers#projects",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"wakatime"
|
||||
],
|
||||
"summary": "Retrieve and fitler the user's projects",
|
||||
"operationId": "get-wakatime-projects",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID to fetch data for (or 'current')",
|
||||
"name": "user",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Query to filter projects by",
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ProjectsViewModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/compat/wakatime/v1/users/{user}/stats/{range}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -558,6 +600,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.Project": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"repository": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ProjectsViewModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1.Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.StatsData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -117,6 +117,22 @@ definitions:
|
||||
schemaVersion:
|
||||
type: integer
|
||||
type: object
|
||||
v1.Project:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
repository:
|
||||
type: string
|
||||
type: object
|
||||
v1.ProjectsViewModel:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/v1.Project'
|
||||
type: array
|
||||
type: object
|
||||
v1.StatsData:
|
||||
properties:
|
||||
daily_average:
|
||||
@ -392,6 +408,33 @@ paths:
|
||||
summary: Retrieve summary for all time
|
||||
tags:
|
||||
- wakatime
|
||||
/compat/wakatime/v1/users/{user}/projects:
|
||||
get:
|
||||
description: Mimics https://wakatime.com/developers#projects
|
||||
operationId: get-wakatime-projects
|
||||
parameters:
|
||||
- description: User ID to fetch data for (or 'current')
|
||||
in: path
|
||||
name: user
|
||||
required: true
|
||||
type: string
|
||||
- description: Query to filter projects by
|
||||
in: query
|
||||
name: q
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/v1.ProjectsViewModel'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Retrieve and fitler the user's projects
|
||||
tags:
|
||||
- wakatime
|
||||
/compat/wakatime/v1/users/{user}/stats/{range}:
|
||||
get:
|
||||
description: Mimics https://wakatime.com/developers#stats
|
||||
|
879
testing/Wakapi API Tests.postman_collection.json
Normal file
879
testing/Wakapi API Tests.postman_collection.json
Normal file
@ -0,0 +1,879 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "472dcea5-a8b1-4507-8480-61644295c35b",
|
||||
"name": "Wakapi API Tests",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Auth",
|
||||
"item": [
|
||||
{
|
||||
"name": "Sign up user",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Body matches string\", function () {",
|
||||
" pm.expect(pm.response.text()).to.include(\"Account created successfully\");",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "location",
|
||||
"value": "{{TZ}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "testuser",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "email",
|
||||
"value": "testuser@wakapi.dev",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password",
|
||||
"value": "testpassword",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password_repeat",
|
||||
"value": "testpassword",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/signup",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"signup"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Sign up existing user (conflict)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 409\", function () {",
|
||||
" pm.response.to.have.status(409);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Body matches string\", function () {",
|
||||
" pm.expect(pm.response.text()).to.include(\"User already existing\");",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "location",
|
||||
"value": "{{TZ}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "username",
|
||||
"value": "testuser",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "email",
|
||||
"value": "testuser@wakapi.dev",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password",
|
||||
"value": "testpassword",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password_repeat",
|
||||
"value": "testpassword",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/signup",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"signup"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Login",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 302\", function () {",
|
||||
" pm.response.to.have.status(302);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Redirect to summary\", function () {",
|
||||
" pm.expect(pm.response.headers.get(\"Location\")).to.eql(\"/summary\");",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Sets cookie\", function () {",
|
||||
" pm.expect(pm.response.headers.get(\"Set-Cookie\")).to.include(\"wakapi_auth=\");",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true,
|
||||
"followRedirects": false
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "username",
|
||||
"value": "testuser",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password",
|
||||
"value": "testpassword",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/login",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"login"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Login (wrong password)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 401\", function () {",
|
||||
" pm.response.to.have.status(401);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"No redirect\", function () {",
|
||||
" pm.response.to.not.have.header(\"Location\");",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true,
|
||||
"followRedirects": false
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "username",
|
||||
"value": "testuser",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "password",
|
||||
"value": "wrongpassword",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/login",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"login"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Heartbeats",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create heartbeats",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 201\", function () {",
|
||||
" pm.response.to.have.status(201);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Response body is correct\", function () {",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.responses.length).to.eql(2);",
|
||||
" pm.expect(jsonData.responses[0].length).to.eql(2);",
|
||||
" pm.expect(jsonData.responses[1].length).to.eql(2);",
|
||||
" pm.expect(jsonData.responses[0][1]).to.eql(201);",
|
||||
" pm.expect(jsonData.responses[1][1]).to.eql(201);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "[{\n \"entity\": \"/home/user1/dev/proejct1/main.go\",\n \"project\": \"Project 1\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": {{tsNowMinus1Min}}\n},\n{\n \"entity\": \"/home/user1/dev/proejct1/main.go\",\n \"project\": \"Project 1\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": {{tsNowMinus2Min}}\n}]",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/heartbeat",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"heartbeat"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create heartbeats (unauthorized)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 401\", function () {",
|
||||
" pm.response.to.have.status(401);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "[{\n \"entity\": \"/home/user1/dev/proejct1/main.go\",\n \"project\": \"Project 1\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": {{tsNowMinus1Min}}\n}]",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/heartbeat",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"heartbeat"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Summary",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get summary (today)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct user\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.user_id).to.eql(\"writeuser\");",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct summary data\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.projects.length).to.eql(1);",
|
||||
" pm.expect(jsonData.languages.length).to.eql(1);",
|
||||
" pm.expect(jsonData.editors.length).to.eql(1);",
|
||||
" pm.expect(jsonData.operating_systems.length).to.eql(1);",
|
||||
" pm.expect(jsonData.machines.length).to.eql(1);",
|
||||
"});",
|
||||
"",
|
||||
"/*",
|
||||
"// This is something the unit tests are supposed to check",
|
||||
"pm.test(\"Correct summary range\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" const from = moment(jsonData.from)",
|
||||
" const to = moment(jsonData.to)",
|
||||
"",
|
||||
" pm.expect(moment.duration(moment().diff(from.add(2, 'm'))).asSeconds()).to.lt(10); // first heartbeat is now minus 1 min minus some latency",
|
||||
" pm.expect(moment.duration(moment().diff(to.add(1, 'm'))).asSeconds()).to.lt(10); // first heartbeat is now minus 1 min minus some latency",
|
||||
"});",
|
||||
"*/"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?interval=today",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "interval",
|
||||
"value": "today"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summary (last 7 days)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct user\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.user_id).to.eql(\"writeuser\");",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct summary data\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.projects.length).to.eql(1);",
|
||||
" pm.expect(jsonData.languages.length).to.eql(1);",
|
||||
" pm.expect(jsonData.editors.length).to.eql(1);",
|
||||
" pm.expect(jsonData.operating_systems.length).to.eql(1);",
|
||||
" pm.expect(jsonData.machines.length).to.eql(1);",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?interval=last_7_days",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "interval",
|
||||
"value": "last_7_days"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summary (week)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct user\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.user_id).to.eql(\"writeuser\");",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct summary data\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.projects.length).to.eql(1);",
|
||||
" pm.expect(jsonData.languages.length).to.eql(1);",
|
||||
" pm.expect(jsonData.editors.length).to.eql(1);",
|
||||
" pm.expect(jsonData.operating_systems.length).to.eql(1);",
|
||||
" pm.expect(jsonData.machines.length).to.eql(1);",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?start=week",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "start",
|
||||
"value": "week"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summary (range)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct user\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.user_id).to.eql(\"writeuser\");",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct summary data\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.projects.length).to.eql(1);",
|
||||
" pm.expect(jsonData.languages.length).to.eql(1);",
|
||||
" pm.expect(jsonData.editors.length).to.eql(1);",
|
||||
" pm.expect(jsonData.operating_systems.length).to.eql(1);",
|
||||
" pm.expect(jsonData.machines.length).to.eql(1);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct dates\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(moment(jsonData.from).unix()).to.gt(moment(pm.variables.get('tsStartOfDayDate')).unix())",
|
||||
" pm.expect(moment(jsonData.to).unix()).to.gt(moment(pm.variables.get('tsEndOfDayDate')).unix())",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?from={{tsStartOfDayDate}}&to={{tsEndOfTomorrowDate}}",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "from",
|
||||
"value": "{{tsStartOfDayDate}}"
|
||||
},
|
||||
{
|
||||
"key": "to",
|
||||
"value": "{{tsEndOfTomorrowDate}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summary (default tz)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct time zone\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" const targetDateTz = moment(`2021-05-28T00:00:00${pm.variables.get('TZ_OFFSET')}`)",
|
||||
" pm.expect(moment(jsonData.from).isSame(targetDateTz)).to.eql(true)",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true,
|
||||
"disableUrlEncoding": false
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?from=2021-05-28&to=2021-05-28",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "from",
|
||||
"value": "2021-05-28"
|
||||
},
|
||||
{
|
||||
"key": "to",
|
||||
"value": "2021-05-28"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summary (parse tz)",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Correct time zone\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" // when it was midnight in UTC+3, it was still 11 pm in Germany",
|
||||
" const targetDateTz = moment(`2021-05-28T00:00:00${pm.variables.get('TZ_OFFSET')}`).add(-1, 'h')",
|
||||
" pm.expect(moment(jsonData.from).isSame(targetDateTz)).to.eql(true)",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {
|
||||
"tlsPreferServerCiphers": true,
|
||||
"disableCookies": true
|
||||
},
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{WRITEUSER_TOKEN}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?from=2021-05-28T00:00:00%2B03:00&to=2021-05-28T00:00:00%2B03:00",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "from",
|
||||
"value": "2021-05-28T00:00:00%2B03:00"
|
||||
},
|
||||
{
|
||||
"key": "to",
|
||||
"value": "2021-05-28T00:00:00%2B03:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"const now = moment()",
|
||||
"const startOfDay = moment().startOf('day')",
|
||||
"const endOfDay = moment().endOf('day')",
|
||||
"const endOfTomorrow = moment().add(1, 'd').endOf('day')",
|
||||
"",
|
||||
"console.log(`Current timestamp is: ${now.format('x') / 1000}`)",
|
||||
"",
|
||||
"",
|
||||
"// Auth stuff",
|
||||
"const readApiKey = pm.variables.get('READUSER_API_KEY')",
|
||||
"const writeApiKey = pm.variables.get('WRITEUSER_API_KEY')",
|
||||
"",
|
||||
"if (!readApiKey || !writeApiKey) {",
|
||||
" throw new Error('no api key given')",
|
||||
"}",
|
||||
"",
|
||||
"pm.variables.set('READUSER_TOKEN', base64encode(readApiKey))",
|
||||
"pm.variables.set('WRITEUSER_TOKEN', base64encode(writeApiKey))",
|
||||
"",
|
||||
"function base64encode(str) {",
|
||||
" return Buffer.from(str, 'utf-8').toString('base64')",
|
||||
"}",
|
||||
"",
|
||||
"// Heartbeat stuff",
|
||||
"pm.variables.set('tsNow', now.format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus1Min', now.add(-1, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus2Min', now.add(-2, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus3Min', now.add(-3, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsStartOfDay', startOfDay.format('x') / 1000)",
|
||||
"pm.variables.set('tsEndOfDay', endOfDay.format('x') / 1000)",
|
||||
"pm.variables.set('tsEndOfTomorrow', endOfTomorrow.format('x') / 1000)",
|
||||
"pm.variables.set('tsStartOfDayIso', startOfDay.toISOString())",
|
||||
"pm.variables.set('tsEndOfDayIso', endOfDay.toISOString())",
|
||||
"pm.variables.set('tsEndOfTomorrowIso', endOfTomorrow.toISOString())",
|
||||
"pm.variables.set('tsStartOfDayDate', startOfDay.format('YYYY-MM-DD'))",
|
||||
"pm.variables.set('tsEndOfDayDate', endOfDay.format('YYYY-MM-DD'))",
|
||||
"pm.variables.set('tsEndOfTomorrowDate', endOfTomorrow.format('YYYY-MM-DD'))"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "BASE_URL",
|
||||
"value": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"key": "READUSER_API_KEY",
|
||||
"value": "33e7f538-0dce-4eba-8ffe-53db6814ed42"
|
||||
},
|
||||
{
|
||||
"key": "WRITEUSER_API_KEY",
|
||||
"value": "f7aa255c-8647-4d0b-b90f-621c58fd580f"
|
||||
},
|
||||
{
|
||||
"key": "TZ",
|
||||
"value": "Europe/Berlin"
|
||||
},
|
||||
{
|
||||
"key": "TZ_OFFSET",
|
||||
"value": "+02:00"
|
||||
}
|
||||
]
|
||||
}
|
61
testing/config.testing.yml
Normal file
61
testing/config.testing.yml
Normal file
@ -0,0 +1,61 @@
|
||||
env: production
|
||||
|
||||
server:
|
||||
listen_ipv4: 127.0.0.1
|
||||
listen_ipv6:
|
||||
tls_cert_path:
|
||||
tls_key_path:
|
||||
port: 3000
|
||||
base_path: /
|
||||
public_url: http://localhost:3000
|
||||
|
||||
app:
|
||||
aggregation_time: '02:15'
|
||||
report_time_weekly: 'fri,18:00'
|
||||
inactive_days: 7
|
||||
custom_languages:
|
||||
vue: Vue
|
||||
jsx: JSX
|
||||
svelte: Svelte
|
||||
|
||||
db:
|
||||
host:
|
||||
port:
|
||||
user:
|
||||
password:
|
||||
name: wakapi_testing.db
|
||||
dialect: sqlite3
|
||||
charset:
|
||||
max_conn: 2
|
||||
ssl: false
|
||||
automgirate_fail_silently: false
|
||||
|
||||
security:
|
||||
password_salt:
|
||||
insecure_cookies: true
|
||||
cookie_max_age: 172800
|
||||
allow_signup: true
|
||||
expose_metrics: false
|
||||
|
||||
sentry:
|
||||
dsn:
|
||||
enable_tracing: false
|
||||
sample_rate:
|
||||
sample_rate_heartbeats:
|
||||
|
||||
mail:
|
||||
enabled: false
|
||||
provider: smtp
|
||||
sender: Wakapi <noreply@wakapi.dev>
|
||||
|
||||
smtp:
|
||||
host:
|
||||
port:
|
||||
username:
|
||||
password:
|
||||
tls:
|
||||
|
||||
mailwhale:
|
||||
url:
|
||||
client_id:
|
||||
client_secret:
|
4
testing/data.sql
Normal file
4
testing/data.sql
Normal file
@ -0,0 +1,4 @@
|
||||
BEGIN TRANSACTION;
|
||||
INSERT INTO "users" ("id","api_key","email","location","password","created_at","last_logged_in_at","share_data_max_days","share_editors","share_languages","share_projects","share_oss","share_machines","is_admin","has_data","wakatime_api_key","reset_token","reports_weekly") VALUES ('readuser','33e7f538-0dce-4eba-8ffe-53db6814ed42','','Europe/Berlin','$2a$10$RCyfAFdlZdFJVWbxKz4f2uJ/MospiE1EFAIjvRizC4Nop9GfjgKzW','2021-05-28 12:34:25','2021-05-28 14:34:34.178+02:00',0,0,0,0,0,0,0,0,'','',0);
|
||||
INSERT INTO "users" ("id","api_key","email","location","password","created_at","last_logged_in_at","share_data_max_days","share_editors","share_languages","share_projects","share_oss","share_machines","is_admin","has_data","wakatime_api_key","reset_token","reports_weekly") VALUES ('writeuser','f7aa255c-8647-4d0b-b90f-621c58fd580f','','Europe/Berlin','$2a$10$vsksPpiXZE9/xG9pRrZP.eKkbe/bGWW4wpPoXqvjiImZqMbN5c4Km','2021-05-28 12:34:56','2021-05-28 14:35:05.118+02:00',0,0,0,0,0,0,0,1,'','',0);
|
||||
COMMIT;
|
40
testing/run_api_tests.sh
Executable file
40
testing/run_api_tests.sh
Executable file
@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -f "wakapi" ]; then
|
||||
echo "Wakapi executable not found. Run 'go build' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v newman &> /dev/null
|
||||
then
|
||||
echo "Newman could not be found. Run 'npm install -g newman' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "Creating database and schema ..."
|
||||
sqlite3 wakapi_testing.db < schema.sql
|
||||
|
||||
echo "Importing seed data ..."
|
||||
sqlite3 wakapi_testing.db < data.sql
|
||||
|
||||
echo "Running Wakapi testing instance in background ..."
|
||||
screen -S wakapi_testing -dm bash -c "../wakapi -config config.testing.yml"
|
||||
|
||||
echo "Waiting for Wakapi to come up ..."
|
||||
until $(curl --output /dev/null --silent --get --fail http://localhost:3000/api/health); do
|
||||
printf '.'
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
echo "Running test collection ..."
|
||||
newman run "Wakapi API Tests.postman_collection.json"
|
||||
|
||||
echo "Shutting down Wakapi ..."
|
||||
screen -S wakapi_testing -X quit
|
||||
|
||||
echo "Deleting database ..."
|
||||
rm wakapi_testing.db
|
147
testing/schema.sql
Normal file
147
testing/schema.sql
Normal file
@ -0,0 +1,147 @@
|
||||
BEGIN TRANSACTION;
|
||||
DROP TABLE IF EXISTS "users";
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text,
|
||||
"api_key" text UNIQUE,
|
||||
"email" text,
|
||||
"password" text,
|
||||
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
"last_logged_in_at" timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
"share_data_max_days" integer DEFAULT 0,
|
||||
"share_editors" numeric DEFAULT false,
|
||||
"share_languages" numeric DEFAULT false,
|
||||
"share_projects" numeric DEFAULT false,
|
||||
"share_oss" numeric DEFAULT false,
|
||||
"share_machines" numeric DEFAULT false,
|
||||
"is_admin" numeric DEFAULT false,
|
||||
"has_data" numeric DEFAULT false,
|
||||
"wakatime_api_key" text,
|
||||
"reset_token" text,
|
||||
"location" text,
|
||||
"reports_weekly" numeric DEFAULT false,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP TABLE IF EXISTS "key_string_values";
|
||||
CREATE TABLE IF NOT EXISTS "key_string_values" (
|
||||
"key" text,
|
||||
"value" text,
|
||||
PRIMARY KEY("key")
|
||||
);
|
||||
DROP TABLE IF EXISTS "summary_items";
|
||||
CREATE TABLE IF NOT EXISTS "summary_items" (
|
||||
"id" integer,
|
||||
"summary_id" integer,
|
||||
"type" integer,
|
||||
"key" text,
|
||||
"total" integer,
|
||||
CONSTRAINT "fk_summaries_languages" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "fk_summary_items_summary" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "fk_summaries_machines" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "fk_summaries_projects" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "fk_summaries_operating_systems" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "fk_summaries_editors" FOREIGN KEY("summary_id") REFERENCES "summaries"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP TABLE IF EXISTS "aliases";
|
||||
CREATE TABLE IF NOT EXISTS "aliases" (
|
||||
"id" integer,
|
||||
"type" integer NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"key" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
CONSTRAINT "fk_aliases_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP TABLE IF EXISTS "heartbeats";
|
||||
CREATE TABLE IF NOT EXISTS "heartbeats" (
|
||||
"id" integer,
|
||||
"user_id" text NOT NULL,
|
||||
"entity" text NOT NULL,
|
||||
"type" text,
|
||||
"category" text,
|
||||
"project" text,
|
||||
"branch" text,
|
||||
"language" text,
|
||||
"is_write" numeric,
|
||||
"editor" text,
|
||||
"operating_system" text,
|
||||
"machine" text,
|
||||
"time" timestamp,
|
||||
"hash" varchar(17),
|
||||
"origin" text,
|
||||
"origin_id" text,
|
||||
"created_at" timestamp,
|
||||
CONSTRAINT "fk_heartbeats_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP TABLE IF EXISTS "summaries";
|
||||
CREATE TABLE IF NOT EXISTS "summaries" (
|
||||
"id" integer,
|
||||
"user_id" text NOT NULL,
|
||||
"from_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"to_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "fk_summaries_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP TABLE IF EXISTS "language_mappings";
|
||||
CREATE TABLE IF NOT EXISTS "language_mappings" (
|
||||
"id" integer,
|
||||
"user_id" text NOT NULL,
|
||||
"extension" varchar(16),
|
||||
"language" varchar(64),
|
||||
CONSTRAINT "fk_language_mappings_user" FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_user_email";
|
||||
CREATE INDEX IF NOT EXISTS "idx_user_email" ON "users" (
|
||||
"email"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_type";
|
||||
CREATE INDEX IF NOT EXISTS "idx_type" ON "summary_items" (
|
||||
"type"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_alias_type_key";
|
||||
CREATE INDEX IF NOT EXISTS "idx_alias_type_key" ON "aliases" (
|
||||
"type",
|
||||
"key"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_alias_user";
|
||||
CREATE INDEX IF NOT EXISTS "idx_alias_user" ON "aliases" (
|
||||
"user_id"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_time";
|
||||
CREATE INDEX IF NOT EXISTS "idx_time" ON "heartbeats" (
|
||||
"time"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_heartbeats_hash";
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_heartbeats_hash" ON "heartbeats" (
|
||||
"hash"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_time_user";
|
||||
CREATE INDEX IF NOT EXISTS "idx_time_user" ON "heartbeats" (
|
||||
"user_id"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_entity";
|
||||
CREATE INDEX IF NOT EXISTS "idx_entity" ON "heartbeats" (
|
||||
"entity"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_language";
|
||||
CREATE INDEX IF NOT EXISTS "idx_language" ON "heartbeats" (
|
||||
"language"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_time_summary_user";
|
||||
CREATE INDEX IF NOT EXISTS "idx_time_summary_user" ON "summaries" (
|
||||
"user_id",
|
||||
"from_time",
|
||||
"to_time"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_language_mapping_composite";
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_language_mapping_composite" ON "language_mappings" (
|
||||
"user_id",
|
||||
"extension"
|
||||
);
|
||||
DROP INDEX IF EXISTS "idx_language_mapping_user";
|
||||
CREATE INDEX IF NOT EXISTS "idx_language_mapping_user" ON "language_mappings" (
|
||||
"user_id"
|
||||
);
|
||||
COMMIT;
|
17
utils/set.go
Normal file
17
utils/set.go
Normal file
@ -0,0 +1,17 @@
|
||||
package utils
|
||||
|
||||
func StringsToSet(slice []string) map[string]bool {
|
||||
set := make(map[string]bool, len(slice))
|
||||
for _, e := range slice {
|
||||
set[e] = true
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func SetToStrings(set map[string]bool) []string {
|
||||
slice := make([]string, 0, len(set))
|
||||
for k := range set {
|
||||
slice = append(slice, k)
|
||||
}
|
||||
return slice
|
||||
}
|
@ -1 +1 @@
|
||||
1.27.0
|
||||
1.27.3
|
||||
|
@ -69,6 +69,7 @@
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> Built by developers for developers</li>
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> Fancy statistics and plots</li>
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> Cool badges for readmes</li>
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> Weekly e-mail reports</li>
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> Intuitive REST API</li>
|
||||
<li><span class="iconify inline text-green-700" data-icon="ant-design:check-square-filled"></span> 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> <a href="https://prometheus.io" target="_blank" rel="noopener noreferrer" class="underline">Prometheus</a> metrics</li>
|
||||
|
@ -192,7 +192,7 @@
|
||||
# <strong>Step 2:</strong> Adapt your config<br>
|
||||
$ vi ~/.wakatime.cfg<br>
|
||||
|
||||
# Set <em>api_url = <span class="with-url-inner">%s/api/heartbeat</span></em><br>
|
||||
# Set <em>api_url = <span class="with-url-inner">%s/api</span></em><br>
|
||||
# Set <em>api_key = <span id="api-key-instruction"></span></em><br><br>
|
||||
|
||||
# <strong>Step 3:</strong> Start coding and then check back here!
|
||||
|
Reference in New Issue
Block a user