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

Compare commits

...

25 Commits

Author SHA1 Message Date
2b57da224c chore: write authenticated user to logs and sentry 2021-03-26 13:10:10 +01:00
01d51b78b1 chore: include postman collection (resolve #148) [ci-skip] 2021-03-25 15:50:47 +01:00
6b83600acc fix: responsiveness in firefox mobile (resolve #137) 2021-03-25 10:51:36 +01:00
65bbd744b5 chore: exclude static assets from sonarqube [ci skip] 2021-03-24 23:38:38 +01:00
81ca703501 fix: disable recompute caching (resolve #149) 2021-03-24 23:31:04 +01:00
2d1010e9d9 chore: add caching to badge endpoint
chore: add type index for summary items
2021-03-24 21:49:42 +01:00
5ca9a6a8be test: add further tests for heartbeat augmentation (resolve #144) 2021-03-24 20:43:49 +01:00
caf87de887 fix: allow custom mappings with dot (resolve #144) 2021-03-24 19:25:36 +01:00
9fc3c65efe feat: sentry integration (resolve #142) 2021-03-23 22:12:15 +01:00
f73285160d fix: delete language mapping (resolve #143) 2021-03-22 21:20:23 +01:00
1f557d562f chore: remove integrity hashes from assets files since served locally anyway (resolve #141) 2021-03-20 09:55:21 +01:00
3685f3a156 chore: add svelte language mapping 2021-03-13 21:01:24 +01:00
b3afe9bfa2 feat: gui for sample data generator script 2021-03-11 23:54:35 +01:00
9de2c20885 fix: enable strict slash mode for api router 2021-03-09 23:56:38 +01:00
2846748b26 hotfix: remove user agent check for shields.io 2021-03-09 23:15:35 +01:00
f2f6fe1483 Merge pull request #140 from notarock/master
Prevent invalid dates ranges in summary page
2021-03-06 09:05:11 +01:00
17ddd7ca76 Prevent invalid dates ranges in summary page
It is currently possible to enter a "End" date that is before the start
date, or a "start" date that is after the end date. This commit prevent the
user from directly entering an invalid date by setting a "min" and "max"
value on those two date picker.

There are no validation or the server's side, but that shouldn't be a
problem since the invalid date will not create an error, it will simply
not return any data.
2021-03-06 02:38:28 -05:00
292ae41c58 fix: batch insert conflict bug (resolve #139) 2021-03-05 21:39:21 +01:00
4f86f67716 chore: fix success messages 2021-02-21 13:12:29 +01:00
017530ac4a chore: introduce user email addresses (resolve #132) 2021-02-21 13:02:11 +01:00
81d3251856 fix: use compatible tailwind version 2021-02-21 13:02:01 +01:00
16af17fc37 chore: save heartbeats created date 2021-02-21 12:02:19 +01:00
701ed0a3e1 chore: purge tailwind classes (resolve #22) 2021-02-21 11:14:55 +01:00
218c93e975 Merge pull request #136 from notarock/master
Fixed scale label to show seconds instead of minutes
2021-02-21 10:38:52 +01:00
44de057022 Fixed scale label to show seconds instead of minutes 2021-02-21 04:27:45 -05:00
53 changed files with 108164 additions and 653 deletions

View File

@ -90,6 +90,8 @@ $ docker run -d \
**Note:** By default, SQLite is used as a database. To run Wakapi in Docker with MySQL or Postgres, see [Dockerfile](https://github.com/muety/wakapi/blob/master/Dockerfile) and [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for further options.
If you want to run Wakapi on **Kubernetes**, there is [wakapi-helm-chart](https://github.com/andreymaznyak/wakapi-helm-chart) for quick and easy deployment.
### 📦 Option 3: Run a release
```bash
# Download the release and unpack it
@ -176,6 +178,10 @@ You can specify configuration options either via a config file (default: `config
| `db.charset` | `WAKAPI_DB_CHARSET` | `utf8mb4` | Database connection charset (for MySQL only) |
| `db.max_conn` | `WAKAPI_DB_MAX_CONNECTIONS` | `2` | Maximum number of database connections |
| `db.ssl` | `WAKAPI_DB_SSL` | `false` | Whether to use TLS encryption for database connection (Postgres and CockroachDB only) |
| `sentry.dsn` | `WAKAPI_SENTRY_DSN` | | DSN for to integrate [Sentry](https://sentry.io) for error logging and tracing (leave empty to disable) |
| `sentry.enable_tracing` | `WAKAPI_SENTRY_TRACING` | `false` | Whether to enable Sentry request tracing |
| `sentry.sample_rate` | `WAKAPI_SENTRY_SAMPLE_RATE` | `0.75` | Probability of tracing a request in Sentry |
| `sentry.sample_rate_heartbats` | `WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS` | `0.1` | Probability of tracing a heartbeats request in Sentry |
### Supported databases
Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of different relational databases is supported.
@ -249,6 +255,13 @@ However, if you want to expose your wakapi instance to the public anyway, you ne
CGO_FLAGS="-g -O2 -Wno-return-local-addr" go test -json -coverprofile=coverage/coverage.out ./... -run ./...
```
### Building Tailwind
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.
```bash
$ tailwindcss-cli build static/assets/vendor/tailwind.css -o static/assets/vendor/tailwind.dist.css
```
## 🙏 Support
If you like this project, please consider supporting it 🙂. You can donate either through [buying me a coffee](https://buymeacoff.ee/n1try) or becoming a GitHub sponsor. Every little donation is highly appreciated and boosts the developers' motivation to keep improving Wakapi!

View File

@ -14,6 +14,7 @@ app:
custom_languages:
vue: Vue
jsx: JSX
svelte: Svelte
db:
host: # leave blank when using sqlite3
@ -31,4 +32,10 @@ security:
insecure_cookies: false # You need to set this to 'true' when on localhost
cookie_max_age: 172800
allow_signup: true
expose_metrics: false
expose_metrics: false
sentry:
dsn: # leave blank to disable sentry integration
enable_tracing: true # whether to use performance monitoring
sample_rate: 0.75 # probability of tracing a request
sample_rate_heartbeats: 0.1 # probability of tracing a heartbeat request

View File

@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"github.com/emvi/logbuch"
"github.com/getsentry/sentry-go"
"github.com/gorilla/securecookie"
"github.com/jinzhu/configor"
"github.com/markbates/pkger"
@ -92,6 +93,13 @@ type serverConfig struct {
TlsKeyPath string `yaml:"tls_key_path" default:"" env:"WAKAPI_TLS_KEY_PATH"`
}
type sentryConfig struct {
Dsn string `env:"WAKAPI_SENTRY_DSN"`
EnableTracing bool `yaml:"enable_tracing" env:"WAKAPI_SENTRY_TRACING"`
SampleRate float32 `yaml:"sample_rate" default:"0.75" env:"WAKAPI_SENTRY_SAMPLE_RATE"`
SampleRateHeartbeats float32 `yaml:"sample_rate_heartbeats" default:"0.1" env:"WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS"`
}
type Config struct {
Env string `default:"dev" env:"ENVIRONMENT"`
Version string `yaml:"-"`
@ -99,6 +107,7 @@ type Config struct {
Security securityConfig
Db dbConfig
Server serverConfig
Sentry sentryConfig
}
func (c *Config) CreateCookie(name, value, path string) *http.Cookie {
@ -290,6 +299,44 @@ func resolveDbDialect(dbType string) string {
return dbType
}
func initSentry(config sentryConfig, debug bool) {
if err := sentry.Init(sentry.ClientOptions{
Dsn: config.Dsn,
Debug: debug,
TracesSampler: sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) sentry.Sampled {
if !config.EnableTracing {
return sentry.SampledFalse
}
hub := sentry.GetHubFromContext(ctx.Span.Context())
txName := hub.Scope().Transaction()
if strings.HasPrefix(txName, "GET /assets") {
return sentry.SampledFalse
}
if txName == "POST /api/heartbeat" {
return sentry.UniformTracesSampler(config.SampleRateHeartbeats).Sample(ctx)
}
return sentry.UniformTracesSampler(config.SampleRate).Sample(ctx)
}),
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
type principalGetter interface {
GetPrincipal() *models.User
}
if hint.Context != nil {
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
if p := req.Context().Value("principal"); p != nil {
event.User.ID = p.(principalGetter).GetPrincipal().ID
}
}
}
return event
},
}); err != nil {
logbuch.Fatal("failed to initialized sentry %v", err)
}
}
func Set(config *Config) {
cfg = config
}
@ -333,6 +380,11 @@ func Load() *Config {
logbuch.Fatal("you must allow at least one database connection")
}
if config.Sentry.Dsn != "" {
logbuch.Info("enabling sentry integration")
initSentry(config.Sentry, config.IsDev())
}
Set(config)
return Get()
}

View File

@ -22,11 +22,12 @@ func Test_mysqlConnectionString(t *testing.T) {
Password: "test_password",
Name: "test_name",
Dialect: "mysql",
Charset: "utf8mb4",
MaxConn: 10,
}
assert.Equal(t, fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
c.User,
c.Password,
c.Host,

View File

@ -1,25 +1,100 @@
mode: set
github.com/muety/wakapi/models/heartbeat.go:32.34,34.2 1 1
github.com/muety/wakapi/models/heartbeat.go:36.65,37.28 1 1
github.com/muety/wakapi/models/heartbeat.go:40.2,41.45 2 1
github.com/muety/wakapi/models/heartbeat.go:44.2,45.44 2 1
github.com/muety/wakapi/models/heartbeat.go:48.2,48.42 1 1
github.com/muety/wakapi/models/heartbeat.go:37.28,39.3 1 1
github.com/muety/wakapi/models/heartbeat.go:41.45,43.3 1 0
github.com/muety/wakapi/models/heartbeat.go:45.44,47.3 1 0
github.com/muety/wakapi/models/heartbeat.go:51.50,52.11 1 1
github.com/muety/wakapi/models/heartbeat.go:65.2,65.15 1 1
github.com/muety/wakapi/models/heartbeat.go:69.2,69.12 1 1
github.com/muety/wakapi/models/heartbeat.go:53.22,54.18 1 1
github.com/muety/wakapi/models/heartbeat.go:55.21,56.17 1 1
github.com/muety/wakapi/models/heartbeat.go:57.23,58.19 1 1
github.com/muety/wakapi/models/heartbeat.go:59.17,60.26 1 1
github.com/muety/wakapi/models/heartbeat.go:61.22,62.18 1 1
github.com/muety/wakapi/models/heartbeat.go:65.15,67.3 1 1
github.com/muety/wakapi/models/heartbeat.go:72.37,88.2 1 0
github.com/muety/wakapi/models/heartbeat.go:96.41,98.16 2 0
github.com/muety/wakapi/models/heartbeat.go:101.2,102.10 2 0
github.com/muety/wakapi/models/heartbeat.go:98.16,100.3 1 0
github.com/muety/wakapi/models/heartbeat.go:36.65,37.46 1 1
github.com/muety/wakapi/models/heartbeat.go:37.46,38.46 1 1
github.com/muety/wakapi/models/heartbeat.go:38.46,41.4 2 1
github.com/muety/wakapi/models/heartbeat.go:45.50,46.11 1 1
github.com/muety/wakapi/models/heartbeat.go:59.2,59.15 1 1
github.com/muety/wakapi/models/heartbeat.go:63.2,63.12 1 1
github.com/muety/wakapi/models/heartbeat.go:47.22,48.18 1 1
github.com/muety/wakapi/models/heartbeat.go:49.21,50.17 1 1
github.com/muety/wakapi/models/heartbeat.go:51.23,52.19 1 1
github.com/muety/wakapi/models/heartbeat.go:53.17,54.26 1 1
github.com/muety/wakapi/models/heartbeat.go:55.22,56.18 1 1
github.com/muety/wakapi/models/heartbeat.go:59.15,61.3 1 1
github.com/muety/wakapi/models/heartbeat.go:66.37,82.2 1 0
github.com/muety/wakapi/models/heartbeat.go:90.41,92.16 2 0
github.com/muety/wakapi/models/heartbeat.go:95.2,96.10 2 0
github.com/muety/wakapi/models/heartbeat.go:92.16,94.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/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:51.52,57.22 2 0
github.com/muety/wakapi/models/shared.go:73.2,76.12 3 0
github.com/muety/wakapi/models/shared.go:58.14,60.17 2 0
github.com/muety/wakapi/models/shared.go:63.13,65.8 2 0
github.com/muety/wakapi/models/shared.go:66.17,68.8 2 0
github.com/muety/wakapi/models/shared.go:69.10,70.64 1 0
github.com/muety/wakapi/models/shared.go:60.17,62.4 1 0
github.com/muety/wakapi/models/shared.go:79.45,81.2 1 0
github.com/muety/wakapi/models/shared.go:83.51,86.2 2 0
github.com/muety/wakapi/models/shared.go:88.37,91.2 2 0
github.com/muety/wakapi/models/shared.go:93.35,95.2 1 0
github.com/muety/wakapi/models/shared.go:97.34,99.2 1 0
github.com/muety/wakapi/models/summary.go:69.29,71.2 1 1
github.com/muety/wakapi/models/summary.go:73.37,80.2 6 1
github.com/muety/wakapi/models/summary.go:82.35,84.2 1 1
github.com/muety/wakapi/models/summary.go:86.57,94.2 1 1
github.com/muety/wakapi/models/summary.go:96.64,98.2 1 0
github.com/muety/wakapi/models/summary.go:111.33,116.26 4 1
github.com/muety/wakapi/models/summary.go:123.2,123.37 1 1
github.com/muety/wakapi/models/summary.go:127.2,130.33 2 1
github.com/muety/wakapi/models/summary.go:116.26,117.30 1 1
github.com/muety/wakapi/models/summary.go:117.30,119.4 1 1
github.com/muety/wakapi/models/summary.go:123.37,125.3 1 0
github.com/muety/wakapi/models/summary.go:130.33,136.3 1 1
github.com/muety/wakapi/models/summary.go:139.45,144.30 3 1
github.com/muety/wakapi/models/summary.go:153.2,153.30 1 1
github.com/muety/wakapi/models/summary.go:144.30,145.47 1 1
github.com/muety/wakapi/models/summary.go:145.47,146.32 1 1
github.com/muety/wakapi/models/summary.go:149.4,149.9 1 1
github.com/muety/wakapi/models/summary.go:146.32,148.5 1 1
github.com/muety/wakapi/models/summary.go:156.73,158.55 2 1
github.com/muety/wakapi/models/summary.go:163.2,163.16 1 1
github.com/muety/wakapi/models/summary.go:158.55,159.31 1 1
github.com/muety/wakapi/models/summary.go:159.31,161.4 1 1
github.com/muety/wakapi/models/summary.go:166.88,168.55 2 1
github.com/muety/wakapi/models/summary.go:176.2,176.16 1 1
github.com/muety/wakapi/models/summary.go:168.55,169.31 1 1
github.com/muety/wakapi/models/summary.go:169.31,170.23 1 1
github.com/muety/wakapi/models/summary.go:173.4,173.46 1 1
github.com/muety/wakapi/models/summary.go:170.23,171.13 1 1
github.com/muety/wakapi/models/summary.go:179.70,181.8 2 1
github.com/muety/wakapi/models/summary.go:184.2,184.10 1 1
github.com/muety/wakapi/models/summary.go:181.8,183.3 1 1
github.com/muety/wakapi/models/summary.go:187.71,188.63 1 1
github.com/muety/wakapi/models/summary.go:228.2,234.10 6 1
github.com/muety/wakapi/models/summary.go:188.63,191.45 2 1
github.com/muety/wakapi/models/summary.go:200.3,200.31 1 1
github.com/muety/wakapi/models/summary.go:207.3,207.31 1 1
github.com/muety/wakapi/models/summary.go:224.3,224.16 1 1
github.com/muety/wakapi/models/summary.go:191.45,192.32 1 1
github.com/muety/wakapi/models/summary.go:197.4,197.14 1 1
github.com/muety/wakapi/models/summary.go:192.32,193.24 1 1
github.com/muety/wakapi/models/summary.go:193.24,195.6 1 1
github.com/muety/wakapi/models/summary.go:200.31,202.60 1 1
github.com/muety/wakapi/models/summary.go:202.60,204.5 1 1
github.com/muety/wakapi/models/summary.go:207.31,209.60 1 1
github.com/muety/wakapi/models/summary.go:209.60,210.55 1 1
github.com/muety/wakapi/models/summary.go:210.55,212.6 1 1
github.com/muety/wakapi/models/summary.go:212.11,220.6 1 1
github.com/muety/wakapi/models/summary.go:237.33,239.2 1 1
github.com/muety/wakapi/models/summary.go:241.43,243.2 1 1
github.com/muety/wakapi/models/summary.go:245.38,247.2 1 1
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
github.com/muety/wakapi/models/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
@ -30,68 +105,13 @@ github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0
github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0
github.com/muety/wakapi/models/summary.go:68.29,70.2 1 1
github.com/muety/wakapi/models/summary.go:72.37,79.2 6 1
github.com/muety/wakapi/models/summary.go:81.35,83.2 1 1
github.com/muety/wakapi/models/summary.go:85.57,93.2 1 1
github.com/muety/wakapi/models/summary.go:95.64,97.2 1 0
github.com/muety/wakapi/models/summary.go:110.33,115.26 4 1
github.com/muety/wakapi/models/summary.go:122.2,122.37 1 1
github.com/muety/wakapi/models/summary.go:126.2,129.33 2 1
github.com/muety/wakapi/models/summary.go:115.26,116.30 1 1
github.com/muety/wakapi/models/summary.go:116.30,118.4 1 1
github.com/muety/wakapi/models/summary.go:122.37,124.3 1 0
github.com/muety/wakapi/models/summary.go:129.33,135.3 1 1
github.com/muety/wakapi/models/summary.go:138.45,143.30 3 1
github.com/muety/wakapi/models/summary.go:152.2,152.30 1 1
github.com/muety/wakapi/models/summary.go:143.30,144.47 1 1
github.com/muety/wakapi/models/summary.go:144.47,145.32 1 1
github.com/muety/wakapi/models/summary.go:148.4,148.9 1 1
github.com/muety/wakapi/models/summary.go:145.32,147.5 1 1
github.com/muety/wakapi/models/summary.go:155.73,157.55 2 1
github.com/muety/wakapi/models/summary.go:162.2,162.16 1 1
github.com/muety/wakapi/models/summary.go:157.55,158.31 1 1
github.com/muety/wakapi/models/summary.go:158.31,160.4 1 1
github.com/muety/wakapi/models/summary.go:165.88,167.55 2 1
github.com/muety/wakapi/models/summary.go:175.2,175.16 1 1
github.com/muety/wakapi/models/summary.go:167.55,168.31 1 1
github.com/muety/wakapi/models/summary.go:168.31,169.23 1 1
github.com/muety/wakapi/models/summary.go:172.4,172.46 1 1
github.com/muety/wakapi/models/summary.go:169.23,170.13 1 1
github.com/muety/wakapi/models/summary.go:178.70,180.8 2 1
github.com/muety/wakapi/models/summary.go:183.2,183.10 1 1
github.com/muety/wakapi/models/summary.go:180.8,182.3 1 1
github.com/muety/wakapi/models/summary.go:186.71,187.63 1 1
github.com/muety/wakapi/models/summary.go:227.2,233.10 6 1
github.com/muety/wakapi/models/summary.go:187.63,190.45 2 1
github.com/muety/wakapi/models/summary.go:199.3,199.31 1 1
github.com/muety/wakapi/models/summary.go:206.3,206.31 1 1
github.com/muety/wakapi/models/summary.go:223.3,223.16 1 1
github.com/muety/wakapi/models/summary.go:190.45,191.32 1 1
github.com/muety/wakapi/models/summary.go:196.4,196.14 1 1
github.com/muety/wakapi/models/summary.go:191.32,192.24 1 1
github.com/muety/wakapi/models/summary.go:192.24,194.6 1 1
github.com/muety/wakapi/models/summary.go:199.31,201.60 1 1
github.com/muety/wakapi/models/summary.go:201.60,203.5 1 1
github.com/muety/wakapi/models/summary.go:206.31,208.60 1 1
github.com/muety/wakapi/models/summary.go:208.60,209.55 1 1
github.com/muety/wakapi/models/summary.go:209.55,211.6 1 1
github.com/muety/wakapi/models/summary.go:211.11,219.6 1 1
github.com/muety/wakapi/models/summary.go:236.33,238.2 1 1
github.com/muety/wakapi/models/summary.go:240.43,242.2 1 1
github.com/muety/wakapi/models/summary.go:244.38,246.2 1 1
github.com/muety/wakapi/models/user.go:46.43,49.2 1 0
github.com/muety/wakapi/models/user.go:51.33,55.2 1 0
github.com/muety/wakapi/models/user.go:57.45,59.2 1 0
github.com/muety/wakapi/models/user.go:61.45,63.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/user.go:13.13,15.2 1 1
github.com/muety/wakapi/models/user.go:67.43,70.2 1 0
github.com/muety/wakapi/models/user.go:72.33,77.2 1 0
github.com/muety/wakapi/models/user.go:79.41,81.2 1 0
github.com/muety/wakapi/models/user.go:83.45,85.2 1 0
github.com/muety/wakapi/models/user.go:87.45,89.2 1 0
github.com/muety/wakapi/models/user.go:91.39,93.2 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
@ -110,96 +130,21 @@ 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/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/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:51.52,57.22 2 0
github.com/muety/wakapi/models/shared.go:73.2,76.12 3 0
github.com/muety/wakapi/models/shared.go:58.14,60.17 2 0
github.com/muety/wakapi/models/shared.go:63.13,65.8 2 0
github.com/muety/wakapi/models/shared.go:66.17,68.8 2 0
github.com/muety/wakapi/models/shared.go:69.10,70.64 1 0
github.com/muety/wakapi/models/shared.go:60.17,62.4 1 0
github.com/muety/wakapi/models/shared.go:79.45,81.2 1 0
github.com/muety/wakapi/models/shared.go:83.51,86.2 2 0
github.com/muety/wakapi/models/shared.go:88.37,91.2 2 0
github.com/muety/wakapi/models/shared.go:93.35,95.2 1 0
github.com/muety/wakapi/models/shared.go:97.34,99.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:10.48,12.2 1 0
github.com/muety/wakapi/utils/common.go:14.40,16.2 1 0
github.com/muety/wakapi/utils/common.go:18.45,20.2 1 0
github.com/muety/wakapi/utils/common.go:22.24,24.2 1 0
github.com/muety/wakapi/utils/common.go:26.56,29.45 3 1
github.com/muety/wakapi/utils/common.go:32.2,32.40 1 1
github.com/muety/wakapi/utils/common.go:29.45,31.3 1 1
github.com/muety/wakapi/utils/date.go:8.31,10.2 1 0
github.com/muety/wakapi/utils/date.go:12.43,14.2 1 0
github.com/muety/wakapi/utils/date.go:16.30,20.2 3 0
github.com/muety/wakapi/utils/date.go:22.31,25.2 2 0
github.com/muety/wakapi/utils/date.go:27.30,30.2 2 0
github.com/muety/wakapi/utils/date.go:32.67,35.33 2 0
github.com/muety/wakapi/utils/date.go:44.2,44.18 1 0
github.com/muety/wakapi/utils/date.go:35.33,37.19 2 0
github.com/muety/wakapi/utils/date.go:40.3,41.10 2 0
github.com/muety/wakapi/utils/date.go:37.19,39.4 1 0
github.com/muety/wakapi/utils/date.go:47.50,53.2 5 0
github.com/muety/wakapi/utils/date.go:56.79,59.36 3 0
github.com/muety/wakapi/utils/date.go:63.2,63.21 1 0
github.com/muety/wakapi/utils/date.go:67.2,67.21 1 0
github.com/muety/wakapi/utils/date.go:71.2,71.13 1 0
github.com/muety/wakapi/utils/date.go:59.36,62.3 2 0
github.com/muety/wakapi/utils/date.go:63.21,66.3 2 0
github.com/muety/wakapi/utils/date.go:67.21,70.3 2 0
github.com/muety/wakapi/utils/common.go:14.52,16.2 1 0
github.com/muety/wakapi/utils/common.go:18.40,20.2 1 0
github.com/muety/wakapi/utils/common.go:22.44,24.2 1 0
github.com/muety/wakapi/utils/common.go:26.45,28.2 1 0
github.com/muety/wakapi/utils/common.go:30.24,32.2 1 0
github.com/muety/wakapi/utils/common.go:34.56,37.45 3 1
github.com/muety/wakapi/utils/common.go:40.2,40.40 1 1
github.com/muety/wakapi/utils/common.go:37.45,39.3 1 1
github.com/muety/wakapi/utils/http.go:9.73,12.58 3 0
github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
github.com/muety/wakapi/utils/auth.go:37.65,39.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/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
@ -225,161 +170,333 @@ github.com/muety/wakapi/utils/summary.go:62.35,63.43 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:102.2,109.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/config/config.go:103.70,105.2 1 0
github.com/muety/wakapi/config/config.go:107.65,109.2 1 0
github.com/muety/wakapi/config/config.go:111.82,121.2 1 0
github.com/muety/wakapi/config/config.go:123.31,125.2 1 0
github.com/muety/wakapi/config/config.go:127.32,129.2 1 0
github.com/muety/wakapi/config/config.go:131.74,132.19 1 0
github.com/muety/wakapi/config/config.go:133.10,134.34 1 0
github.com/muety/wakapi/config/config.go:134.34,143.4 8 0
github.com/muety/wakapi/config/config.go:147.73,148.33 1 0
github.com/muety/wakapi/config/config.go:148.33,156.17 5 0
github.com/muety/wakapi/config/config.go:160.3,161.13 2 0
github.com/muety/wakapi/config/config.go:156.17,158.4 1 0
github.com/muety/wakapi/config/config.go:165.50,166.19 1 0
github.com/muety/wakapi/config/config.go:179.2,179.12 1 0
github.com/muety/wakapi/config/config.go:167.23,171.5 1 0
github.com/muety/wakapi/config/config.go:172.26,175.5 1 0
github.com/muety/wakapi/config/config.go:176.24,177.48 1 0
github.com/muety/wakapi/config/config.go:182.53,192.2 1 1
github.com/muety/wakapi/config/config.go:194.56,196.16 2 1
github.com/muety/wakapi/config/config.go:200.2,207.3 1 1
github.com/muety/wakapi/config/config.go:196.16,198.3 1 0
github.com/muety/wakapi/config/config.go:210.54,212.2 1 1
github.com/muety/wakapi/config/config.go:214.60,216.2 1 0
github.com/muety/wakapi/config/config.go:218.59,220.2 1 0
github.com/muety/wakapi/config/config.go:222.57,224.2 1 0
github.com/muety/wakapi/config/config.go:226.53,228.2 1 0
github.com/muety/wakapi/config/config.go:230.29,232.2 1 1
github.com/muety/wakapi/config/config.go:234.27,236.16 2 0
github.com/muety/wakapi/config/config.go:239.2,242.16 3 0
github.com/muety/wakapi/config/config.go:246.2,246.41 1 0
github.com/muety/wakapi/config/config.go:236.16,238.3 1 0
github.com/muety/wakapi/config/config.go:242.16,244.3 1 0
github.com/muety/wakapi/config/config.go:249.48,261.16 3 0
github.com/muety/wakapi/config/config.go:264.2,266.16 3 0
github.com/muety/wakapi/config/config.go:270.2,270.55 1 0
github.com/muety/wakapi/config/config.go:274.2,274.15 1 0
github.com/muety/wakapi/config/config.go:261.16,263.3 1 0
github.com/muety/wakapi/config/config.go:266.16,268.3 1 0
github.com/muety/wakapi/config/config.go:270.55,272.3 1 0
github.com/muety/wakapi/config/config.go:277.38,278.43 1 0
github.com/muety/wakapi/config/config.go:281.2,281.15 1 0
github.com/muety/wakapi/config/config.go:278.43,280.3 1 0
github.com/muety/wakapi/config/config.go:284.45,285.27 1 0
github.com/muety/wakapi/config/config.go:288.2,288.15 1 0
github.com/muety/wakapi/config/config.go:285.27,287.3 1 0
github.com/muety/wakapi/config/config.go:291.26,293.2 1 0
github.com/muety/wakapi/config/config.go:295.20,297.2 1 0
github.com/muety/wakapi/config/config.go:299.21,304.96 3 0
github.com/muety/wakapi/config/config.go:308.2,316.52 5 0
github.com/muety/wakapi/config/config.go:320.2,320.47 1 0
github.com/muety/wakapi/config/config.go:326.2,326.70 1 0
github.com/muety/wakapi/config/config.go:330.2,330.28 1 0
github.com/muety/wakapi/config/config.go:334.2,335.14 2 0
github.com/muety/wakapi/config/config.go:304.96,306.3 1 0
github.com/muety/wakapi/config/config.go:316.52,318.3 1 0
github.com/muety/wakapi/config/config.go:320.47,321.14 1 0
github.com/muety/wakapi/config/config.go:321.14,323.4 1 0
github.com/muety/wakapi/config/config.go:326.70,328.3 1 0
github.com/muety/wakapi/config/config.go:330.28,332.3 1 0
github.com/muety/wakapi/utils/summary.go:93.3,94.17 2 0
github.com/muety/wakapi/utils/summary.go:86.17,88.18 2 0
github.com/muety/wakapi/utils/summary.go:88.18,90.5 1 0
github.com/muety/wakapi/utils/summary.go:94.17,96.18 2 0
github.com/muety/wakapi/utils/summary.go:96.18,98.5 1 0
github.com/muety/wakapi/utils/summary.go:112.48,116.51 2 0
github.com/muety/wakapi/utils/summary.go:119.2,119.12 1 0
github.com/muety/wakapi/utils/summary.go:116.51,118.3 1 0
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
github.com/muety/wakapi/utils/auth.go:33.2,34.32 2 0
github.com/muety/wakapi/utils/auth.go:18.54,20.3 1 0
github.com/muety/wakapi/utils/auth.go:24.16,26.3 1 0
github.com/muety/wakapi/utils/auth.go:30.45,32.3 1 0
github.com/muety/wakapi/utils/auth.go:37.65,39.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/date.go:8.31,10.2 1 0
github.com/muety/wakapi/utils/date.go:12.43,14.2 1 0
github.com/muety/wakapi/utils/date.go:16.30,20.2 3 0
github.com/muety/wakapi/utils/date.go:22.31,25.2 2 0
github.com/muety/wakapi/utils/date.go:27.30,30.2 2 0
github.com/muety/wakapi/utils/date.go:32.67,35.33 2 0
github.com/muety/wakapi/utils/date.go:44.2,44.18 1 0
github.com/muety/wakapi/utils/date.go:35.33,37.19 2 0
github.com/muety/wakapi/utils/date.go:40.3,41.10 2 0
github.com/muety/wakapi/utils/date.go:37.19,39.4 1 0
github.com/muety/wakapi/utils/date.go:47.50,53.2 5 0
github.com/muety/wakapi/utils/date.go:56.79,59.36 3 0
github.com/muety/wakapi/utils/date.go:63.2,63.21 1 0
github.com/muety/wakapi/utils/date.go:67.2,67.21 1 0
github.com/muety/wakapi/utils/date.go:71.2,71.13 1 0
github.com/muety/wakapi/utils/date.go:59.36,62.3 2 0
github.com/muety/wakapi/utils/date.go:63.21,66.3 2 0
github.com/muety/wakapi/utils/date.go:67.21,70.3 2 0
github.com/muety/wakapi/utils/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/config/utils.go:5.78,7.22 2 0
github.com/muety/wakapi/config/utils.go:13.2,13.11 1 0
github.com/muety/wakapi/config/utils.go:7.22,8.18 1 0
github.com/muety/wakapi/config/utils.go:11.3,11.12 1 0
github.com/muety/wakapi/config/utils.go:8.18,10.4 1 0
github.com/muety/wakapi/middlewares/authenticate.go:20.91,26.2 1 1
github.com/muety/wakapi/middlewares/authenticate.go:28.90,31.2 2 0
github.com/muety/wakapi/middlewares/authenticate.go:33.90,36.2 2 0
github.com/muety/wakapi/middlewares/authenticate.go:38.71,39.71 1 0
github.com/muety/wakapi/middlewares/authenticate.go:39.71,41.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:44.107,48.16 3 0
github.com/muety/wakapi/middlewares/authenticate.go:52.2,52.31 1 0
github.com/muety/wakapi/middlewares/authenticate.go:68.2,69.29 2 0
github.com/muety/wakapi/middlewares/authenticate.go:48.16,50.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:52.31,53.31 1 0
github.com/muety/wakapi/middlewares/authenticate.go:58.3,58.29 1 0
github.com/muety/wakapi/middlewares/authenticate.go:65.3,65.9 1 0
github.com/muety/wakapi/middlewares/authenticate.go:53.31,56.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:58.29,61.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:61.9,64.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:72.70,73.39 1 0
github.com/muety/wakapi/middlewares/authenticate.go:78.2,78.14 1 0
github.com/muety/wakapi/middlewares/authenticate.go:73.39,74.60 1 0
github.com/muety/wakapi/middlewares/authenticate.go:74.60,76.4 1 0
github.com/muety/wakapi/middlewares/authenticate.go:81.92,83.16 2 1
github.com/muety/wakapi/middlewares/authenticate.go:87.2,90.16 4 1
github.com/muety/wakapi/middlewares/authenticate.go:93.2,93.18 1 1
github.com/muety/wakapi/middlewares/authenticate.go:83.16,85.3 1 1
github.com/muety/wakapi/middlewares/authenticate.go:90.16,92.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:96.92,98.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:102.2,103.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:110.2,110.18 1 0
github.com/muety/wakapi/middlewares/authenticate.go:98.16,100.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:103.16,105.3 1 0
github.com/muety/wakapi/config/config.go:113.70,115.2 1 0
github.com/muety/wakapi/config/config.go:117.65,119.2 1 0
github.com/muety/wakapi/config/config.go:121.82,131.2 1 0
github.com/muety/wakapi/config/config.go:133.31,135.2 1 0
github.com/muety/wakapi/config/config.go:137.32,139.2 1 0
github.com/muety/wakapi/config/config.go:141.74,142.19 1 0
github.com/muety/wakapi/config/config.go:143.10,144.34 1 0
github.com/muety/wakapi/config/config.go:144.34,153.4 8 0
github.com/muety/wakapi/config/config.go:157.73,158.33 1 0
github.com/muety/wakapi/config/config.go:158.33,166.17 5 0
github.com/muety/wakapi/config/config.go:170.3,171.13 2 0
github.com/muety/wakapi/config/config.go:166.17,168.4 1 0
github.com/muety/wakapi/config/config.go:175.50,176.19 1 0
github.com/muety/wakapi/config/config.go:189.2,189.12 1 0
github.com/muety/wakapi/config/config.go:177.23,181.5 1 0
github.com/muety/wakapi/config/config.go:182.26,185.5 1 0
github.com/muety/wakapi/config/config.go:186.24,187.48 1 0
github.com/muety/wakapi/config/config.go:192.53,203.2 1 1
github.com/muety/wakapi/config/config.go:205.56,207.16 2 1
github.com/muety/wakapi/config/config.go:211.2,218.3 1 1
github.com/muety/wakapi/config/config.go:207.16,209.3 1 0
github.com/muety/wakapi/config/config.go:221.54,223.2 1 1
github.com/muety/wakapi/config/config.go:225.60,227.2 1 0
github.com/muety/wakapi/config/config.go:229.59,231.2 1 0
github.com/muety/wakapi/config/config.go:233.57,235.2 1 0
github.com/muety/wakapi/config/config.go:237.53,239.2 1 0
github.com/muety/wakapi/config/config.go:241.29,243.2 1 1
github.com/muety/wakapi/config/config.go:245.27,247.16 2 0
github.com/muety/wakapi/config/config.go:250.2,253.16 3 0
github.com/muety/wakapi/config/config.go:257.2,257.41 1 0
github.com/muety/wakapi/config/config.go:247.16,249.3 1 0
github.com/muety/wakapi/config/config.go:253.16,255.3 1 0
github.com/muety/wakapi/config/config.go:260.48,272.16 3 0
github.com/muety/wakapi/config/config.go:275.2,277.16 3 0
github.com/muety/wakapi/config/config.go:281.2,281.55 1 0
github.com/muety/wakapi/config/config.go:285.2,285.15 1 0
github.com/muety/wakapi/config/config.go:272.16,274.3 1 0
github.com/muety/wakapi/config/config.go:277.16,279.3 1 0
github.com/muety/wakapi/config/config.go:281.55,283.3 1 0
github.com/muety/wakapi/config/config.go:288.38,289.43 1 0
github.com/muety/wakapi/config/config.go:292.2,292.15 1 0
github.com/muety/wakapi/config/config.go:289.43,291.3 1 0
github.com/muety/wakapi/config/config.go:295.45,296.27 1 0
github.com/muety/wakapi/config/config.go:299.2,299.15 1 0
github.com/muety/wakapi/config/config.go:296.27,298.3 1 0
github.com/muety/wakapi/config/config.go:302.50,306.91 1 0
github.com/muety/wakapi/config/config.go:306.91,307.29 1 0
github.com/muety/wakapi/config/config.go:311.4,314.48 3 0
github.com/muety/wakapi/config/config.go:317.4,317.39 1 0
github.com/muety/wakapi/config/config.go:320.4,320.69 1 0
github.com/muety/wakapi/config/config.go:307.29,309.5 1 0
github.com/muety/wakapi/config/config.go:314.48,316.5 1 0
github.com/muety/wakapi/config/config.go:317.39,319.5 1 0
github.com/muety/wakapi/config/config.go:322.79,326.27 2 0
github.com/muety/wakapi/config/config.go:333.4,333.16 1 0
github.com/muety/wakapi/config/config.go:326.27,327.84 1 0
github.com/muety/wakapi/config/config.go:327.84,328.57 1 0
github.com/muety/wakapi/config/config.go:328.57,330.7 1 0
github.com/muety/wakapi/config/config.go:335.17,337.3 1 0
github.com/muety/wakapi/config/config.go:340.26,342.2 1 0
github.com/muety/wakapi/config/config.go:344.20,346.2 1 0
github.com/muety/wakapi/config/config.go:348.21,353.96 3 0
github.com/muety/wakapi/config/config.go:357.2,365.52 5 0
github.com/muety/wakapi/config/config.go:369.2,369.47 1 0
github.com/muety/wakapi/config/config.go:375.2,375.70 1 0
github.com/muety/wakapi/config/config.go:379.2,379.28 1 0
github.com/muety/wakapi/config/config.go:383.2,383.29 1 0
github.com/muety/wakapi/config/config.go:388.2,389.14 2 0
github.com/muety/wakapi/config/config.go:353.96,355.3 1 0
github.com/muety/wakapi/config/config.go:365.52,367.3 1 0
github.com/muety/wakapi/config/config.go:369.47,370.14 1 0
github.com/muety/wakapi/config/config.go:370.14,372.4 1 0
github.com/muety/wakapi/config/config.go:375.70,377.3 1 0
github.com/muety/wakapi/config/config.go:379.28,381.3 1 0
github.com/muety/wakapi/config/config.go:383.29,386.3 2 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
github.com/muety/wakapi/middlewares/authenticate.go:37.71,38.71 1 0
github.com/muety/wakapi/middlewares/authenticate.go:38.71,40.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:43.107,47.16 3 0
github.com/muety/wakapi/middlewares/authenticate.go:51.2,51.31 1 0
github.com/muety/wakapi/middlewares/authenticate.go:67.2,68.12 2 0
github.com/muety/wakapi/middlewares/authenticate.go:47.16,49.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:51.31,52.31 1 0
github.com/muety/wakapi/middlewares/authenticate.go:57.3,57.29 1 0
github.com/muety/wakapi/middlewares/authenticate.go:64.3,64.9 1 0
github.com/muety/wakapi/middlewares/authenticate.go:52.31,55.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:57.29,60.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:60.9,63.4 2 0
github.com/muety/wakapi/middlewares/authenticate.go:71.70,72.39 1 0
github.com/muety/wakapi/middlewares/authenticate.go:77.2,77.14 1 0
github.com/muety/wakapi/middlewares/authenticate.go:72.39,73.60 1 0
github.com/muety/wakapi/middlewares/authenticate.go:73.60,75.4 1 0
github.com/muety/wakapi/middlewares/authenticate.go:80.92,82.16 2 1
github.com/muety/wakapi/middlewares/authenticate.go:86.2,89.16 4 1
github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.18 1 1
github.com/muety/wakapi/middlewares/authenticate.go:82.16,84.3 1 1
github.com/muety/wakapi/middlewares/authenticate.go:89.16,91.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:95.92,97.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:101.2,102.16 2 0
github.com/muety/wakapi/middlewares/authenticate.go:109.2,109.18 1 0
github.com/muety/wakapi/middlewares/authenticate.go:97.16,99.3 1 0
github.com/muety/wakapi/middlewares/authenticate.go:102.16,104.3 1 0
github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0
github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0
github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0
github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0
github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0
github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0
github.com/muety/wakapi/middlewares/logging.go:19.105,20.43 1 0
github.com/muety/wakapi/middlewares/logging.go:20.43,26.3 1 0
github.com/muety/wakapi/middlewares/logging.go:29.80,38.44 7 0
github.com/muety/wakapi/middlewares/logging.go:44.2,53.3 1 0
github.com/muety/wakapi/middlewares/logging.go:38.44,39.38 1 0
github.com/muety/wakapi/middlewares/logging.go:39.38,41.4 1 0
github.com/muety/wakapi/middlewares/logging.go:56.41,58.14 2 0
github.com/muety/wakapi/middlewares/logging.go:61.2,61.14 1 0
github.com/muety/wakapi/middlewares/logging.go:64.2,64.11 1 0
github.com/muety/wakapi/middlewares/logging.go:58.14,60.3 1 0
github.com/muety/wakapi/middlewares/logging.go:61.14,63.3 1 0
github.com/muety/wakapi/middlewares/logging.go:95.52,97.2 1 0
github.com/muety/wakapi/middlewares/logging.go:109.45,110.20 1 0
github.com/muety/wakapi/middlewares/logging.go:110.20,114.3 3 0
github.com/muety/wakapi/middlewares/logging.go:116.54,119.18 3 0
github.com/muety/wakapi/middlewares/logging.go:126.2,127.15 2 0
github.com/muety/wakapi/middlewares/logging.go:119.18,122.17 2 0
github.com/muety/wakapi/middlewares/logging.go:122.17,124.4 1 0
github.com/muety/wakapi/middlewares/logging.go:129.42,130.20 1 0
github.com/muety/wakapi/middlewares/logging.go:130.20,132.3 1 0
github.com/muety/wakapi/middlewares/logging.go:134.36,136.2 1 0
github.com/muety/wakapi/middlewares/logging.go:137.42,139.2 1 0
github.com/muety/wakapi/middlewares/logging.go:140.40,142.2 1 0
github.com/muety/wakapi/middlewares/logging.go:143.52,145.2 1 0
github.com/muety/wakapi/middlewares/logging.go:20.102,21.43 1 0
github.com/muety/wakapi/middlewares/logging.go:21.43,27.3 1 0
github.com/muety/wakapi/middlewares/logging.go:30.80,39.44 7 0
github.com/muety/wakapi/middlewares/logging.go:45.2,54.3 1 0
github.com/muety/wakapi/middlewares/logging.go:39.44,40.38 1 0
github.com/muety/wakapi/middlewares/logging.go:40.38,42.4 1 0
github.com/muety/wakapi/middlewares/logging.go:57.41,59.14 2 0
github.com/muety/wakapi/middlewares/logging.go:62.2,62.14 1 0
github.com/muety/wakapi/middlewares/logging.go:65.2,65.11 1 0
github.com/muety/wakapi/middlewares/logging.go:59.14,61.3 1 0
github.com/muety/wakapi/middlewares/logging.go:62.14,64.3 1 0
github.com/muety/wakapi/middlewares/logging.go:68.41,69.42 1 0
github.com/muety/wakapi/middlewares/logging.go:72.2,72.12 1 0
github.com/muety/wakapi/middlewares/logging.go:69.42,71.3 1 0
github.com/muety/wakapi/middlewares/logging.go:103.52,105.2 1 0
github.com/muety/wakapi/middlewares/logging.go:117.45,118.20 1 0
github.com/muety/wakapi/middlewares/logging.go:118.20,122.3 3 0
github.com/muety/wakapi/middlewares/logging.go:124.54,127.18 3 0
github.com/muety/wakapi/middlewares/logging.go:134.2,135.15 2 0
github.com/muety/wakapi/middlewares/logging.go:127.18,130.17 2 0
github.com/muety/wakapi/middlewares/logging.go:130.17,132.4 1 0
github.com/muety/wakapi/middlewares/logging.go:137.42,138.20 1 0
github.com/muety/wakapi/middlewares/logging.go:138.20,140.3 1 0
github.com/muety/wakapi/middlewares/logging.go:142.36,144.2 1 0
github.com/muety/wakapi/middlewares/logging.go:145.42,147.2 1 0
github.com/muety/wakapi/middlewares/logging.go:148.40,150.2 1 0
github.com/muety/wakapi/middlewares/logging.go:151.52,153.2 1 0
github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0
github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0
github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0
github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0
github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0
github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0
github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0
github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0
github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0
github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0
github.com/muety/wakapi/middlewares/sentry.go:14.60,15.43 1 0
github.com/muety/wakapi/middlewares/sentry.go:15.43,19.3 1 0
github.com/muety/wakapi/middlewares/sentry.go:22.78,25.54 3 0
github.com/muety/wakapi/middlewares/sentry.go:25.54,26.43 1 0
github.com/muety/wakapi/middlewares/sentry.go:26.43,28.4 1 0
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
github.com/muety/wakapi/services/heartbeat.go:29.80,31.2 1 0
github.com/muety/wakapi/services/heartbeat.go:33.53,35.2 1 0
github.com/muety/wakapi/services/heartbeat.go:37.76,39.2 1 0
github.com/muety/wakapi/services/heartbeat.go:41.96,43.2 1 0
github.com/muety/wakapi/services/heartbeat.go:45.111,47.16 2 0
github.com/muety/wakapi/services/heartbeat.go:50.2,50.43 1 0
github.com/muety/wakapi/services/heartbeat.go:47.16,49.3 1 0
github.com/muety/wakapi/services/heartbeat.go:53.116,55.2 1 0
github.com/muety/wakapi/services/heartbeat.go:57.78,59.2 1 0
github.com/muety/wakapi/services/heartbeat.go:61.62,63.2 1 0
github.com/muety/wakapi/services/heartbeat.go:65.116,67.16 2 0
github.com/muety/wakapi/services/heartbeat.go:71.2,71.28 1 0
github.com/muety/wakapi/services/heartbeat.go:75.2,75.24 1 0
github.com/muety/wakapi/services/heartbeat.go:67.16,69.3 1 0
github.com/muety/wakapi/services/heartbeat.go:71.28,73.3 1 0
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0
github.com/muety/wakapi/services/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.116,66.2 1 0
github.com/muety/wakapi/services/heartbeat.go:68.78,70.2 1 0
github.com/muety/wakapi/services/heartbeat.go:72.62,74.2 1 0
github.com/muety/wakapi/services/heartbeat.go:76.116,78.16 2 0
github.com/muety/wakapi/services/heartbeat.go:82.2,82.28 1 0
github.com/muety/wakapi/services/heartbeat.go:86.2,86.24 1 0
github.com/muety/wakapi/services/heartbeat.go:78.16,80.3 1 0
github.com/muety/wakapi/services/heartbeat.go:82.28,84.3 1 0
github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
github.com/muety/wakapi/services/user.go:59.61,62.2 2 0
github.com/muety/wakapi/services/user.go:64.48,66.2 1 0
github.com/muety/wakapi/services/user.go:68.102,77.93 2 0
github.com/muety/wakapi/services/user.go:83.2,83.38 1 0
github.com/muety/wakapi/services/user.go:77.93,79.3 1 0
github.com/muety/wakapi/services/user.go:79.8,81.3 1 0
github.com/muety/wakapi/services/user.go:86.73,89.2 2 0
github.com/muety/wakapi/services/user.go:91.78,95.2 3 0
github.com/muety/wakapi/services/user.go:97.99,100.2 2 0
github.com/muety/wakapi/services/user.go:102.106,105.96 3 0
github.com/muety/wakapi/services/user.go:110.2,110.68 1 0
github.com/muety/wakapi/services/user.go:105.96,107.3 1 0
github.com/muety/wakapi/services/user.go:107.8,109.3 1 0
github.com/muety/wakapi/services/user.go:113.57,116.2 2 0
github.com/muety/wakapi/services/user.go:118.38,120.2 1 0
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
@ -420,48 +537,36 @@ github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/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/summary.go:27.149,35.2 1 1
github.com/muety/wakapi/services/summary.go:39.120,42.52 2 1
github.com/muety/wakapi/services/summary.go:39.136,42.66 2 1
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
github.com/muety/wakapi/services/summary.go:53.2,53.65 1 1
github.com/muety/wakapi/services/summary.go:58.2,59.16 2 1
github.com/muety/wakapi/services/summary.go:64.2,66.30 3 1
github.com/muety/wakapi/services/summary.go:42.52,44.3 1 0
github.com/muety/wakapi/services/summary.go:42.66,44.3 1 0
github.com/muety/wakapi/services/summary.go:47.44,50.3 2 1
github.com/muety/wakapi/services/summary.go:53.65,55.3 1 0
github.com/muety/wakapi/services/summary.go:59.16,61.3 1 0
@ -544,72 +649,10 @@ github.com/muety/wakapi/services/summary.go:324.54,326.3 1 1
github.com/muety/wakapi/services/summary.go:331.59,333.25 2 1
github.com/muety/wakapi/services/summary.go:336.2,336.32 1 1
github.com/muety/wakapi/services/summary.go:333.25,335.3 1 1
github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
github.com/muety/wakapi/services/user.go:37.2,38.15 2 0
github.com/muety/wakapi/services/user.go:28.40,30.3 1 0
github.com/muety/wakapi/services/user.go:33.16,35.3 1 0
github.com/muety/wakapi/services/user.go:41.72,42.37 1 0
github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
github.com/muety/wakapi/services/user.go:59.61,62.2 2 0
github.com/muety/wakapi/services/user.go:64.48,66.2 1 0
github.com/muety/wakapi/services/user.go:68.102,76.93 2 0
github.com/muety/wakapi/services/user.go:82.2,82.38 1 0
github.com/muety/wakapi/services/user.go:76.93,78.3 1 0
github.com/muety/wakapi/services/user.go:78.8,80.3 1 0
github.com/muety/wakapi/services/user.go:85.73,88.2 2 0
github.com/muety/wakapi/services/user.go:90.78,94.2 3 0
github.com/muety/wakapi/services/user.go:96.99,99.2 2 0
github.com/muety/wakapi/services/user.go:101.106,104.96 3 0
github.com/muety/wakapi/services/user.go:109.2,109.68 1 0
github.com/muety/wakapi/services/user.go:104.96,106.3 1 0
github.com/muety/wakapi/services/user.go:106.8,108.3 1 0
github.com/muety/wakapi/services/user.go:112.57,115.2 2 0
github.com/muety/wakapi/services/user.go:117.38,119.2 1 0
github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
github.com/muety/wakapi/services/misc.go:23.126,30.2 1 0
github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0

View File

@ -210,6 +210,7 @@
"Stan": "#b2011d",
"Standard ML": "#dc566d",
"SuperCollider": "#46390b",
"Svelte": "#ff3e00",
"Swift": "#ffac45",
"SystemVerilog": "#DAE1C2",
"Tcl": "#e4cc98",

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.13
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/emvi/logbuch v1.1.1
github.com/getsentry/sentry-go v0.10.0
github.com/go-co-op/gocron v0.3.3
github.com/go-openapi/spec v0.20.2 // indirect
github.com/gorilla/handlers v1.4.2

85
go.sum
View File

@ -1,7 +1,11 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
@ -9,10 +13,12 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -28,6 +34,7 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -42,6 +49,7 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -59,31 +67,46 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/emvi/logbuch v1.1.1 h1:poBGNbHy/nB95oNoqLKAaJoBrcKxTO0W9DhMijKEkkU=
github.com/emvi/logbuch v1.1.1/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-co-op/gocron v0.3.3 h1:QnarcMZWWKrEP25uCbtDiLsnnGw+PhCjL3wNITdWJOs=
github.com/go-co-op/gocron v0.3.3/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
@ -118,6 +141,9 @@ github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -134,12 +160,14 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -156,6 +184,7 @@ github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlI
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@ -182,8 +211,14 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
@ -251,11 +286,21 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -266,6 +311,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -301,7 +348,10 @@ github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -317,6 +367,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
@ -335,8 +386,10 @@ github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2f
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@ -355,6 +408,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -395,10 +449,13 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -438,14 +495,29 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
@ -479,6 +551,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -504,12 +577,15 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
@ -543,6 +619,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -570,10 +647,12 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -625,9 +704,13 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@ -635,12 +718,14 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=

23
main.go
View File

@ -117,9 +117,7 @@ func main() {
defer sqlDb.Close()
// Migrate database schema
migrations.RunPreMigrations(db, config)
runDatabaseMigrations()
migrations.RunCustomPostMigrations(db, config)
migrations.Run(db, config)
// Repositories
aliasRepository = repositories.NewAliasRepository(db)
@ -167,14 +165,15 @@ func main() {
// Setup Routers
router := mux.NewRouter()
rootRouter := router.PathPrefix("/").Subrouter()
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter := router.PathPrefix("/api").Subrouter().StrictSlash(true)
// Globally used middlewares
recoveryMiddleware := handlers.RecoveryHandler()
loggingMiddleware := middlewares.NewLoggingMiddleware(logbuch.Info, []string{"/assets"})
// Router configs
router.Use(loggingMiddleware, recoveryMiddleware)
router.Use(middlewares.NewPrincipalMiddleware())
router.Use(middlewares.NewLoggingMiddleware(logbuch.Info, []string{"/assets"}))
router.Use(handlers.RecoveryHandler())
if config.Sentry.Dsn != "" {
router.Use(middlewares.NewSentryMiddleware())
}
// Route registrations
homeHandler.RegisterRoutes(rootRouter)
@ -268,9 +267,3 @@ func listen(handler http.Handler) {
<-make(chan interface{}, 1)
}
func runDatabaseMigrations() {
if err := config.GetMigrationFunc(config.Db.Dialect)(db); err != nil {
logbuch.Fatal(err.Error())
}
}

View File

@ -1,7 +1,6 @@
package middlewares
import (
"context"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/services"
@ -65,8 +64,8 @@ func (m *AuthenticateMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
return
}
ctx := context.WithValue(r.Context(), models.UserKey, user)
next(w, r.WithContext(ctx))
SetPrincipal(r, user)
next(w, r)
}
func (m *AuthenticateMiddleware) isOptional(requestPath string) bool {

View File

@ -6,7 +6,7 @@ import (
"fmt"
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/middlewares"
"io"
"io/ioutil"
"net/http"
@ -39,7 +39,7 @@ func (m *WakatimeRelayMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
return
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
if user == nil || user.WakatimeApiKey == "" {
return
}

View File

@ -43,13 +43,14 @@ func (lg *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
lg.logFunc(
"[request] status=%d, method=%s, uri=%s, duration=%v, bytes=%d, addr=%s",
"[request] status=%d, method=%s, uri=%s, duration=%v, bytes=%d, addr=%s, user=%s",
ww.Status(),
r.Method,
r.URL.String(),
duration,
ww.BytesWritten(),
readUserIP(r),
readUserID(r),
)
}
@ -64,6 +65,13 @@ func readUserIP(r *http.Request) string {
return ip
}
func readUserID(r *http.Request) string {
if user := GetPrincipal(r); user != nil {
return user.ID
}
return "-"
}
// The below writer-wrapping code has been lifted from
// https://github.com/zenazn/goji/blob/master/web/middleware/logger.go - because
// it does exactly what is needed, and it's unlikely to change in any

64
middlewares/principal.go Normal file
View File

@ -0,0 +1,64 @@
package middlewares
import (
"context"
"github.com/muety/wakapi/models"
"net/http"
)
const keyPrincipal = "principal"
type PrincipalContainer struct {
principal *models.User
}
func (c *PrincipalContainer) SetPrincipal(user *models.User) {
c.principal = user
}
func (c *PrincipalContainer) GetPrincipal() *models.User {
return c.principal
}
// This middleware is a bit of a dirty workaround to the fact that a http.Request's context
// does not allow to pass values from an inner to an outer middleware. Calling WithContext() on a
// request shallow-copies the whole request itself and therefore, in a chain of handler1(handler2()),
// handler 1 will not have access to values handler 2 writes to its context. In addition, Context.WithValue
// returns a new context with the old context as a parent.
//
// As a concrete example, SentryMiddleware as well as LoggingMiddleware should be quite the outer layers,
// while AuthenticationMiddleware is on the very inside of the chain. However, we still want sentry or the
// logger to have access to the user object populated by the auth. middleware, if present.
//
// This middleware shall be included as the outermost layers and it injects a stateful container that does
// nothing but conditionally hold a reference to an authenticated user object.
//
// Other reference: https://stackoverflow.com/questions/55972869/send-errors-to-sentry-with-golang-and-mux
type PrincipalMiddleware struct {
handler http.Handler
}
func NewPrincipalMiddleware() func(handler http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return &PrincipalMiddleware{handler: h}
}
}
func (p *PrincipalMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), keyPrincipal, &PrincipalContainer{})
p.handler.ServeHTTP(w, r.WithContext(ctx))
}
func SetPrincipal(r *http.Request, user *models.User) {
if p := r.Context().Value(keyPrincipal); p != nil {
p.(*PrincipalContainer).SetPrincipal(user)
}
}
func GetPrincipal(r *http.Request) *models.User {
if p := r.Context().Value(keyPrincipal); p != nil {
return p.(*PrincipalContainer).GetPrincipal()
}
return nil
}

30
middlewares/sentry.go Normal file
View File

@ -0,0 +1,30 @@
package middlewares
import (
"context"
"github.com/getsentry/sentry-go"
sentryhttp "github.com/getsentry/sentry-go/http"
"net/http"
)
type SentryMiddleware struct {
handler http.Handler
}
func NewSentryMiddleware() func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return sentryhttp.New(sentryhttp.Options{
Repanic: true,
}).Handle(&SentryMiddleware{handler: h})
}
}
func (h *SentryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "-", "-")
h.handler.ServeHTTP(w, r.WithContext(ctx))
if hub := sentry.GetHubFromContext(ctx); hub != nil {
if user := GetPrincipal(r); user != nil {
hub.Scope().SetUser(sentry.User{ID: user.ID})
}
}
}

View File

@ -0,0 +1,41 @@
package migrations
import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"gorm.io/gorm"
)
func init() {
const name = "20210221-add_created_date_column"
f := migrationFunc{
name: name,
f: func(db *gorm.DB, cfg *config.Config) error {
condition := "key = ?"
if cfg.Db.Dialect == config.SQLDialectMysql {
condition = "`key` = ?"
}
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
logbuch.Info("no need to migrate '%s'", name)
return nil
}
if err := db.Exec("UPDATE heartbeats SET created_at = time WHERE TRUE").Error; err != nil {
return err
}
if err := db.Create(&models.KeyStringValue{
Key: name,
Value: "done",
}).Error; err != nil {
return err
}
return nil
},
}
registerPostMigration(f)
}

View File

@ -28,9 +28,17 @@ func registerPostMigration(f migrationFunc) {
postMigrations = append(postMigrations, f)
}
// NOTE: Currently, migrations themselves keep track
// of whether they have run, yet or not, because some
// simply run on every start.
func Run(db *gorm.DB, cfg *config.Config) {
RunPreMigrations(db, cfg)
RunSchemaMigrations(db, cfg)
RunPostMigrations(db, cfg)
}
func RunSchemaMigrations(db *gorm.DB, cfg *config.Config) {
if err := cfg.GetMigrationFunc(cfg.Db.Dialect)(db); err != nil {
logbuch.Fatal(err.Error())
}
}
func RunPreMigrations(db *gorm.DB, cfg *config.Config) {
sort.Sort(preMigrations)
@ -43,7 +51,7 @@ func RunPreMigrations(db *gorm.DB, cfg *config.Config) {
}
}
func RunCustomPostMigrations(db *gorm.DB, cfg *config.Config) {
func RunPostMigrations(db *gorm.DB, cfg *config.Config) {
sort.Sort(postMigrations)
for _, m := range postMigrations {

View File

@ -4,29 +4,29 @@ import (
"fmt"
"github.com/emvi/logbuch"
"github.com/mitchellh/hashstructure/v2"
"regexp"
"strings"
"time"
)
type Heartbeat struct {
ID uint `gorm:"primary_key" hash:"ignore"`
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
Type string `json:"type"`
Category string `json:"category"`
Project string `json:"project"`
Branch string `json:"branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime
OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime
Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user" swaggertype:"primitive,number"`
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
Origin string `json:"-" hash:"ignore"`
OriginId string `json:"-" hash:"ignore"`
languageRegex *regexp.Regexp `hash:"ignore"`
ID uint `gorm:"primary_key" hash:"ignore"`
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
Type string `json:"type"`
Category string `json:"category"`
Project string `json:"project"`
Branch string `json:"branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime
OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime
Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently
Time CustomTime `json:"time" gorm:"type:timestamp; index:idx_time,idx_time_user" swaggertype:"primitive,number"`
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
Origin string `json:"-" hash:"ignore"`
OriginId string `json:"-" hash:"ignore"`
CreatedAt CustomTime `json:"created_at" gorm:"type:timestamp" swaggertype:"primitive,number" hash:"ignore"` // https://gorm.io/docs/conventions.html#CreatedAt
}
func (h *Heartbeat) Valid() bool {
@ -34,18 +34,12 @@ func (h *Heartbeat) Valid() bool {
}
func (h *Heartbeat) Augment(languageMappings map[string]string) {
if h.languageRegex == nil {
h.languageRegex = regexp.MustCompile(`^.+\.(.+)$`)
for ending, value := range languageMappings {
if strings.HasSuffix(h.Entity, "."+ending) {
h.Language = value
return
}
}
groups := h.languageRegex.FindAllStringSubmatch(h.Entity, -1)
if len(groups) == 0 || len(groups[0]) != 2 {
return
}
ending := groups[0][1]
if _, ok := languageMappings[ending]; !ok {
return
}
h.Language, _ = languageMappings[ending]
}
func (h *Heartbeat) GetKey(t uint8) (key string) {

View File

@ -26,17 +26,24 @@ func TestHeartbeat_Valid_MissingUser(t *testing.T) {
func TestHeartbeat_Augment(t *testing.T) {
testMappings := map[string]string{
"py": "Python3",
"py": "Python3",
"foo": "Foo Script",
"blade.php": "Blade",
}
sut := &Heartbeat{
sut1, sut2 := &Heartbeat{
Entity: "~/dev/file.py",
Language: "Python",
}, &Heartbeat{
Entity: "~/dev/file.blade.php",
Language: "unknown",
}
sut.Augment(testMappings)
sut1.Augment(testMappings)
sut2.Augment(testMappings)
assert.Equal(t, "Python3", sut.Language)
assert.Equal(t, "Python3", sut1.Language)
assert.Equal(t, "Blade", sut2.Language)
}
func TestHeartbeat_GetKey(t *testing.T) {

View File

@ -35,7 +35,7 @@ type SummaryItem struct {
ID uint `json:"-" gorm:"primary_key"`
Summary *Summary `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
SummaryID uint `json:"-"`
Type uint8 `json:"-"`
Type uint8 `json:"-" gorm:"index:idx_type"`
Key string `json:"key"`
Total time.Duration `json:"total" swaggertype:"primitive,integer"`
}

View File

@ -1,8 +1,23 @@
package models
import "regexp"
const (
MailPattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+"
)
var (
mailRegex *regexp.Regexp
)
func init() {
mailRegex = regexp.MustCompile(MailPattern)
}
type User struct {
ID string `json:"id" gorm:"primary_key"`
ApiKey string `json:"api_key" gorm:"unique"`
Email string `json:"email"`
Password string `json:"-"`
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
@ -24,6 +39,7 @@ type Login struct {
type Signup struct {
Username string `schema:"username"`
Email string `schema:"email"`
Password string `schema:"password"`
PasswordRepeat string `schema:"password_repeat"`
}
@ -34,6 +50,10 @@ type CredentialsReset struct {
PasswordRepeat string `schema:"password_repeat"`
}
type UserDataUpdate struct {
Email string `schema:"email"`
}
type TimeByUser struct {
User string
Time CustomTime
@ -45,20 +65,29 @@ type CountByUser struct {
}
func (c *CredentialsReset) IsValid() bool {
return validatePassword(c.PasswordNew) &&
return ValidatePassword(c.PasswordNew) &&
c.PasswordNew == c.PasswordRepeat
}
func (s *Signup) IsValid() bool {
return validateUsername(s.Username) &&
validatePassword(s.Password) &&
return ValidateUsername(s.Username) &&
ValidateEmail(s.Email) &&
ValidatePassword(s.Password) &&
s.Password == s.PasswordRepeat
}
func validateUsername(username string) bool {
func (r *UserDataUpdate) IsValid() bool {
return ValidateEmail(r.Email)
}
func ValidateUsername(username string) bool {
return len(username) >= 1 && username != "current"
}
func validatePassword(password string) bool {
func ValidatePassword(password string) bool {
return len(password) >= 6
}
func ValidateEmail(email string) bool {
return email == "" || mailRegex.Match([]byte(email))
}

View File

@ -0,0 +1,346 @@
{
"info": {
"_postman_id": "3dcc346d-a9a8-4699-8a52-459eb978b382",
"name": "Wakapi",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Misc",
"item": [
{
"name": "Get health",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE_URL}}/api/health",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"health"
]
}
},
"response": []
},
{
"name": "Get metrics",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/metrics",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"metrics"
]
}
},
"response": []
}
]
},
{
"name": "Heartbeats",
"item": [
{
"name": "Create heartbeat",
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
},
{
"key": "X-Machine-Name",
"value": "devmachine",
"type": "text"
},
{
"key": "User-Agent",
"value": "wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0",
"type": "text"
}
],
"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\": 1616680499.113417\n}]",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE_URL}}/api/heartbeat",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"heartbeat"
]
}
},
"response": []
}
]
},
{
"name": "Summary",
"item": [
{
"name": "Get summary",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"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": "Shields",
"item": [
{
"name": "Get Shields data",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/compat/shields/v1/n1try/interval:today/language:Go",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"compat",
"shields",
"v1",
"n1try",
"interval:today",
"language:Go"
]
}
},
"response": []
}
]
},
{
"name": "WakaTime",
"item": [
{
"name": "Get all time",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/all_time_since_today",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"compat",
"wakatime",
"v1",
"users",
"current",
"all_time_since_today"
]
}
},
"response": []
},
{
"name": "Get stats",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/stats",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"compat",
"wakatime",
"v1",
"users",
"current",
"stats"
]
}
},
"response": []
},
{
"name": "Get stats with range",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/stats/last_7_days",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"compat",
"wakatime",
"v1",
"users",
"current",
"stats",
"last_7_days"
]
}
},
"response": []
},
{
"name": "Get summaries",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Basic {{TOKEN}}",
"type": "text"
}
],
"url": {
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/summaries?start=2020-03-01T15:04:05Z&end=2020-03-31T15:04:05Z",
"host": [
"{{BASE_URL}}"
],
"path": [
"api",
"compat",
"wakatime",
"v1",
"users",
"current",
"summaries"
],
"query": [
{
"key": "start",
"value": "2020-03-01T15:04:05Z"
},
{
"key": "end",
"value": "2020-03-31T15:04:05Z"
}
]
}
},
"response": []
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"const apiKey = pm.variables.get('API_KEY')",
"",
"if (!apiKey) {",
" throw new Error('no api key given')",
"}",
"",
"const token = base64encode(apiKey)",
"pm.variables.set('TOKEN', token)",
"",
"function base64encode(str) {",
" return Buffer.from(str, 'utf-8').toString('base64')",
"}"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "BASE_URL",
"value": "http://localhost:3000"
},
{
"key": "API_KEY",
"value": ""
}
]
}

View File

@ -109,6 +109,7 @@ func (r *UserRepository) Update(user *models.User) (*models.User, error) {
updateMap := map[string]interface{}{
"api_key": user.ApiKey,
"password": user.Password,
"email": user.Email,
"last_logged_in_at": user.LastLoggedInAt,
"share_data_max_days": user.ShareDataMaxDays,
"share_editors": user.ShareEditors,

View File

@ -53,7 +53,7 @@ func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
// @Router /heartbeat [post]
func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
var heartbeats []*models.Heartbeat
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
opSys, editor, _ := utils.ParseUserAgent(r.Header.Get("User-Agent"))
machineName := r.Header.Get("X-Machine-Name")

View File

@ -68,7 +68,7 @@ func (h *MetricsHandler) RegisterRoutes(router *mux.Router) {
}
func (h *MetricsHandler) Get(w http.ResponseWriter, r *http.Request) {
reqUser := r.Context().Value(models.UserKey).(*models.User)
reqUser := middlewares.GetPrincipal(r)
if reqUser == nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(conf.ErrUnauthorized))
@ -108,7 +108,7 @@ func (h *MetricsHandler) Get(w http.ResponseWriter, r *http.Request) {
func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) {
var metrics mm.Metrics
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve)
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve, false)
if err != nil {
logbuch.Error("failed to retrieve all time summary for user '%s' for metric", user.ID)
return nil, err
@ -116,7 +116,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
from, to := utils.MustResolveIntervalRaw("today")
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve)
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, false)
if err != nil {
logbuch.Error("failed to retrieve today's summary for user '%s' for metric", user.ID)
return nil, err

View File

@ -1,15 +1,16 @@
package v1
import (
"fmt"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/shields/v1"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
"github.com/patrickmn/go-cache"
"net/http"
"regexp"
"strings"
"time"
)
@ -22,12 +23,14 @@ type BadgeHandler struct {
config *conf.Config
userSrvc services.IUserService
summarySrvc services.ISummaryService
cache *cache.Cache
}
func NewBadgeHandler(summaryService services.ISummaryService, userService services.IUserService) *BadgeHandler {
return &BadgeHandler{
summarySrvc: summaryService,
userSrvc: userService,
cache: cache.New(time.Hour, time.Hour),
config: conf.Get(),
}
}
@ -52,11 +55,6 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
intervalReg := regexp.MustCompile(intervalPattern)
entityFilterReg := regexp.MustCompile(entityFilterPattern)
if userAgent := r.Header.Get("user-agent"); !strings.HasPrefix(userAgent, "Shields.io/") && !h.config.IsDev() {
w.WriteHeader(http.StatusForbidden)
return
}
var filterEntity, filterKey string
if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
filterEntity, filterKey = groups[1], groups[2]
@ -101,6 +99,12 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
filters = &models.Filters{}
}
cacheKey := fmt.Sprintf("%s_%v_%s_%s", user.ID, *interval, filterEntity, filterKey)
if cacheResult, ok := h.cache.Get(cacheKey); ok {
utils.RespondJSON(w, http.StatusOK, cacheResult.(*v1.BadgeData))
return
}
summary, err, status := h.loadUserSummary(user, interval)
if err != nil {
w.WriteHeader(status)
@ -109,6 +113,7 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
}
vm := v1.NewBadgeDataFrom(summary, filters)
h.cache.SetDefault(cacheKey, vm)
utils.RespondJSON(w, http.StatusOK, vm)
}
@ -129,7 +134,7 @@ func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.Inter
retrieveSummary = h.summarySrvc.Summarize
}
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
if err != nil {
return nil, err, http.StatusInternalServerError
}

View File

@ -49,7 +49,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
values, _ := url.ParseQuery(r.URL.RawQuery)
requestedUser := vars["user"]
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
authorizedUser := middlewares.GetPrincipal(r)
if requestedUser != authorizedUser.ID && requestedUser != "current" {
w.WriteHeader(http.StatusForbidden)
@ -80,7 +80,7 @@ func (h *AllTimeHandler) loadUserSummary(user *models.User) (*models.Summary, er
retrieveSummary = h.summarySrvc.Summarize
}
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
if err != nil {
return nil, err, http.StatusInternalServerError
}

View File

@ -45,10 +45,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
var vars = mux.Vars(r)
var authorizedUser, requestedUser *models.User
if u := r.Context().Value(models.UserKey); u != nil {
authorizedUser = u.(*models.User)
}
authorizedUser = middlewares.GetPrincipal(r)
if authorizedUser != nil && vars["user"] == "current" {
vars["user"] = authorizedUser.ID
}
@ -117,7 +114,7 @@ func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time)
Recompute: false,
}
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve)
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve, false)
if err != nil {
return nil, err, http.StatusInternalServerError
}

View File

@ -56,7 +56,7 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
requestedUser := vars["user"]
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
authorizedUser := middlewares.GetPrincipal(r)
if requestedUser != authorizedUser.ID && requestedUser != "current" {
w.WriteHeader(http.StatusForbidden)
@ -80,7 +80,7 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
}
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
params := r.URL.Query()
rangeParam, startParam, endParam := params.Get("range"), params.Get("start"), params.Get("end")
@ -121,7 +121,7 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
summaries := make([]*models.Summary, len(intervals))
for i, interval := range intervals {
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve)
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, false)
if err != nil {
return nil, err, http.StatusInternalServerError
}

View File

@ -35,6 +35,9 @@ func loadTemplates() {
"toRunes": utils.ToRunes,
"entityTypes": models.SummaryTypes,
"typeName": typeName,
"isDev": func() bool {
return config.Get().IsDev()
},
"getBasePath": func() string {
return config.Get().Server.BasePath
},

View File

@ -117,6 +117,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
switch action {
case "change_password":
return h.actionChangePassword
case "update_user":
return h.actionUpdateUser
case "reset_apikey":
return h.actionResetApiKey
case "delete_alias":
@ -141,12 +143,40 @@ func (h *SettingsHandler) dispatchAction(action string) action {
return nil
}
func (h *SettingsHandler) actionUpdateUser(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := middlewares.GetPrincipal(r)
var payload models.UserDataUpdate
if err := r.ParseForm(); err != nil {
return http.StatusBadRequest, "", "missing parameters"
}
if err := credentialsDecoder.Decode(&payload, r.PostForm); err != nil {
return http.StatusBadRequest, "", "missing parameters"
}
if !payload.IsValid() {
return http.StatusBadRequest, "", "invalid parameters"
}
user.Email = payload.Email
if _, err := h.userSrvc.Update(user); err != nil {
return http.StatusInternalServerError, "", conf.ErrInternalServerError
}
return http.StatusOK, "user updated successfully", ""
}
func (h *SettingsHandler) actionChangePassword(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
var credentials models.CredentialsReset
if err := r.ParseForm(); err != nil {
@ -193,7 +223,7 @@ func (h *SettingsHandler) actionResetApiKey(w http.ResponseWriter, r *http.Reque
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
if _, err := h.userSrvc.ResetApiKey(user); err != nil {
return http.StatusInternalServerError, "", conf.ErrInternalServerError
}
@ -208,7 +238,7 @@ func (h *SettingsHandler) actionUpdateSharing(w http.ResponseWriter, r *http.Req
}
var err error
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
defer h.userSrvc.FlushCache()
@ -235,7 +265,7 @@ func (h *SettingsHandler) actionDeleteAlias(w http.ResponseWriter, r *http.Reque
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
aliasKey := r.PostFormValue("key")
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
if err != nil {
@ -255,7 +285,7 @@ func (h *SettingsHandler) actionAddAlias(w http.ResponseWriter, r *http.Request)
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
aliasKey := r.PostFormValue("key")
aliasValue := r.PostFormValue("value")
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
@ -283,19 +313,20 @@ func (h *SettingsHandler) actionDeleteLanguageMapping(w http.ResponseWriter, r *
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
id, err := strconv.Atoi(r.PostFormValue("mapping_id"))
if err != nil {
return http.StatusInternalServerError, "", "could not delete mapping"
}
if mapping, err := h.languageMappingSrvc.GetById(uint(id)); err != nil || mapping == nil {
mapping, err := h.languageMappingSrvc.GetById(uint(id))
if err != nil || mapping == nil {
return http.StatusNotFound, "", "mapping not found"
} else if mapping.UserID != user.ID {
return http.StatusForbidden, "", "not allowed to delete mapping"
}
if err := h.languageMappingSrvc.Delete(&models.LanguageMapping{ID: uint(id)}); err != nil {
if err := h.languageMappingSrvc.Delete(mapping); err != nil {
return http.StatusInternalServerError, "", "could not delete mapping"
}
@ -306,7 +337,7 @@ func (h *SettingsHandler) actionAddLanguageMapping(w http.ResponseWriter, r *htt
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
extension := r.PostFormValue("extension")
language := r.PostFormValue("language")
@ -332,7 +363,7 @@ func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
apiKey := r.PostFormValue("api_key")
// Healthcheck, if a new API key is set, i.e. the feature is activated
@ -352,7 +383,7 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
if user.WakatimeApiKey == "" {
return http.StatusForbidden, "", "not connected to wakatime"
}
@ -420,13 +451,13 @@ func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *ht
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
go func(user *models.User) {
if err := h.regenerateSummaries(user); err != nil {
logbuch.Error("failed to regenerate summaries for user '%s' %v", user.ID, err)
}
}(middlewares.GetPrincipal(r))
if err := h.regenerateSummaries(user); err != nil {
return http.StatusInternalServerError, "", "failed to regenerate summaries"
}
return http.StatusOK, "summaries are being regenerated this may take a few seconds", ""
return http.StatusAccepted, "summaries are being regenerated this may take a up to a couple of minutes, please come back later", ""
}
func (h *SettingsHandler) actionDeleteUser(w http.ResponseWriter, r *http.Request) (int, string, string) {
@ -434,7 +465,7 @@ func (h *SettingsHandler) actionDeleteUser(w http.ResponseWriter, r *http.Reques
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
go func(user *models.User) {
logbuch.Info("deleting user '%s' shortly", user.ID)
time.Sleep(5 * time.Minute)
@ -493,7 +524,7 @@ func (h *SettingsHandler) regenerateSummaries(user *models.User) error {
}
func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel {
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
mappings, _ := h.languageMappingSrvc.GetByUser(user.ID)
aliases, _ := h.aliasSrvc.GetByUser(user.ID)
aliasMap := make(map[string][]*models.Alias)

View File

@ -53,7 +53,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
return
}
user := r.Context().Value(models.UserKey).(*models.User)
user := middlewares.GetPrincipal(r)
if user == nil {
w.WriteHeader(http.StatusUnauthorized)
templates[conf.SummaryTemplate].Execute(w, h.buildViewModel(r).WithError("unauthorized"))

View File

@ -18,7 +18,7 @@ func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summ
retrieveSummary = ss.Summarize
}
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
if err != nil {
return nil, err, http.StatusInternalServerError
}

View File

@ -1,113 +0,0 @@
#!/usr/bin/python3
import argparse
import base64
import random
import string
import sys
from datetime import datetime, timedelta
from typing import List, Union
import requests
from tqdm import tqdm
MACHINE = "devmachine"
UA = 'wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0'
LANGUAGES = {
'Go': 'go',
'Java': 'java',
'JavaScript': 'js',
'Python': 'py'
}
class Heartbeat:
def __init__(
self,
entity: str,
project: str,
language: str,
time: float,
is_write: bool = True,
branch: str = 'master',
type: str = 'file'
):
self.entity: str = entity
self.project: str = project
self.language: str = language
self.time: float = time
self.is_write: bool = is_write
self.branch: str = branch
self.type: str = type
self.category: Union[str, None] = None
def generate_data(n: int, n_projects: int = 5, n_past_hours: int = 24) -> List[Heartbeat]:
data: List[Heartbeat] = []
now: datetime = datetime.today()
projects: List[str] = [randomword(random.randint(5, 10)) for _ in range(n_projects)]
languages: List[str] = list(LANGUAGES.keys())
for _ in range(n):
p: str = random.choice(projects)
l: str = random.choice(languages)
f: str = randomword(random.randint(2, 8))
delta: timedelta = timedelta(
hours=random.randint(0, n_past_hours - 1),
minutes=random.randint(0, 59),
seconds=random.randint(0, 59),
milliseconds=random.randint(0, 999),
microseconds=random.randint(0, 999)
)
data.append(Heartbeat(
entity=f'/home/me/dev/{p}/{f}.{LANGUAGES[l]}',
project=p,
language=l,
time=(now - delta).timestamp()
))
return data
def post_data_sync(data: List[Heartbeat], url: str, api_key: str):
encoded_key: str = str(base64.b64encode(api_key.encode('utf-8')), 'utf-8')
for h in tqdm(data):
r = requests.post(url, json=[h.__dict__], headers={
'User-Agent': UA,
'Authorization': f'Basic {encoded_key}',
'X-Machine-Name': MACHINE,
})
if r.status_code != 201:
print(r.text)
sys.exit(1)
def randomword(length: int) -> str:
letters = string.ascii_lowercase + 'äöü💩' # test utf8 and utf8mb4 characters as well
return ''.join(random.choice(letters) for _ in range(length))
def parse_arguments():
parser = argparse.ArgumentParser(description='Wakapi test data insertion script.')
parser.add_argument('-n', type=int, default=20, help='total number of random heartbeats to generate and insert')
parser.add_argument('-u', '--url', type=str, default='http://localhost:3000/api/heartbeat',
help='url of your api\'s heartbeats endpoint')
parser.add_argument('-k', '--apikey', type=str, required=True,
help='your api key (to get one, go to the web interface, create a new user, log in and copy the key)')
parser.add_argument('-p', '--projects', type=int, default=5, help='number of different fake projects to generate')
parser.add_argument('-o', '--offset', type=int, default=24,
help='negative time offset in hours from now for to be used as an interval within which to generate heartbeats for')
parser.add_argument('-s', '--seed', type=int, default=2020,
help='a seed for initializing the pseudo-random number generator')
return parser.parse_args()
if __name__ == '__main__':
args = parse_arguments()
random.seed(args.seed)
data: List[Heartbeat] = generate_data(args.n, args.projects, args.offset)
post_data_sync(data, args.url, args.apikey)

271
scripts/sample_data.py Normal file
View File

@ -0,0 +1,271 @@
#!/usr/bin/python3
import argparse
import base64
import random
import string
import sys
from datetime import datetime, timedelta
from typing import List, Union, Callable
import requests
from tqdm import tqdm
MACHINE = "devmachine"
UA = 'wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0'
LANGUAGES = {
'Go': 'go',
'Java': 'java',
'JavaScript': 'js',
'Python': 'py'
}
class Heartbeat:
def __init__(
self,
entity: str,
project: str,
language: str,
time: float,
is_write: bool = True,
branch: str = 'master',
type: str = 'file'
):
self.entity: str = entity
self.project: str = project
self.language: str = language
self.time: float = time
self.is_write: bool = is_write
self.branch: str = branch
self.type: str = type
self.category: Union[str, None] = None
class ConfigParams:
def __init__(self):
self.api_url = ''
self.api_key = ''
self.n = 0
self.n_projects = 0
self.offset = 0
self.seed = 0
def generate_data(n: int, n_projects: int = 5, n_past_hours: int = 24) -> List[Heartbeat]:
data: List[Heartbeat] = []
now: datetime = datetime.today()
projects: List[str] = [randomword(random.randint(5, 10)) for _ in range(n_projects)]
languages: List[str] = list(LANGUAGES.keys())
for _ in range(n):
p: str = random.choice(projects)
l: str = random.choice(languages)
f: str = randomword(random.randint(2, 8))
delta: timedelta = timedelta(
hours=random.randint(0, n_past_hours - 1),
minutes=random.randint(0, 59),
seconds=random.randint(0, 59),
milliseconds=random.randint(0, 999),
microseconds=random.randint(0, 999)
)
data.append(Heartbeat(
entity=f'/home/me/dev/{p}/{f}.{LANGUAGES[l]}',
project=p,
language=l,
time=(now - delta).timestamp()
))
return data
def post_data_sync(data: List[Heartbeat], url: str, api_key: str):
encoded_key: str = str(base64.b64encode(api_key.encode('utf-8')), 'utf-8')
for h in data:
r = requests.post(url, json=[h.__dict__], headers={
'User-Agent': UA,
'Authorization': f'Basic {encoded_key}',
'X-Machine-Name': MACHINE,
})
if r.status_code != 201:
print(r.text)
sys.exit(1)
def make_gui(callback: Callable[[ConfigParams, Callable[[int], None]], None]) -> ('QApplication', 'QWidget'):
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QHBoxLayout, QVBoxLayout, QGroupBox, QLabel, \
QLineEdit, QSpinBox, QProgressBar, QPushButton
# Main app
app = QApplication([])
window = QWidget()
window.setWindowTitle('Wakapi Sample Data Generator')
window.setFixedSize(window.sizeHint())
window.setMinimumWidth(350)
container_layout = QVBoxLayout()
# Top Controls
form_layout_1 = QFormLayout()
url_input_label = QLabel('URL:')
url_input = QLineEdit()
url_input.setPlaceholderText('Wakatime API Url')
url_input.setText('http://localhost:3000/api')
api_key_input_label = QLabel('API Key:')
api_key_input = QLineEdit()
api_key_input.setPlaceholderText(f'{"x"*8}-{"x"*4}-{"x"*4}-{"x"*4}-{"x"*12}')
form_layout_1.addRow(url_input_label, url_input)
form_layout_1.addRow(api_key_input_label, api_key_input)
# Middle controls
form_layout_2 = QFormLayout()
params_container = QGroupBox('Parameters')
params_container.setLayout(form_layout_2)
heartbeats_input_label = QLabel('# Heartbeats')
heartbeats_input = QSpinBox()
heartbeats_input.setMaximum(2147483647)
heartbeats_input.setValue(100)
projects_input_label = QLabel('# Projects:')
projects_input = QSpinBox()
projects_input.setMinimum(1)
projects_input.setValue(5)
offset_input_label = QLabel('Time Offset (hrs):')
offset_input = QSpinBox()
offset_input.setMinimum(-2147483647)
offset_input.setMaximum(0)
offset_input.setValue(-12)
seed_input_label = QLabel('Random Seed:')
seed_input = QSpinBox()
seed_input.setMaximum(2147483647)
seed_input.setValue(1337)
form_layout_2.addRow(heartbeats_input_label, heartbeats_input)
form_layout_2.addRow(projects_input_label, projects_input)
form_layout_2.addRow(offset_input_label, offset_input)
form_layout_2.addRow(seed_input_label, seed_input)
# Bottom controls
bottom_layout = QHBoxLayout()
start_button = QPushButton('Generate')
progress_bar = QProgressBar()
bottom_layout.addWidget(progress_bar)
bottom_layout.addWidget(start_button)
# Wiring up
container_layout.addLayout(form_layout_1)
container_layout.addWidget(params_container)
container_layout.addLayout(bottom_layout)
container_layout.setStretch(1, 1)
window.setLayout(container_layout)
# Done dialog
done_dialog = QWidget()
done_dialog.setWindowTitle('Done')
done_ok_button = QPushButton('Ok')
done_layout = QVBoxLayout()
done_layout.addWidget(QLabel('Done!'), alignment=Qt.AlignCenter)
done_layout.addWidget(done_ok_button, alignment=Qt.AlignCenter)
done_dialog.setFixedSize(done_dialog.sizeHint())
done_ok_button.clicked.connect(done_dialog.close)
done_dialog.setLayout(done_layout)
# Logic
def parse_params() -> ConfigParams:
params = ConfigParams()
params.api_url = url_input.text()
params.api_key = api_key_input.text()
params.n = heartbeats_input.value()
params.n_projects = projects_input.value()
params.offset = offset_input.value()
params.seed = seed_input.value()
return params
def update_progress(inc=1):
current = progress_bar.value()
updated = current + inc
if updated == progress_bar.maximum() - 1:
progress_bar.setValue(0)
start_button.setEnabled(True)
done_dialog.show()
return
progress_bar.setValue(updated)
def call_back():
params = parse_params()
progress_bar.setMaximum(params.n)
start_button.setEnabled(False)
callback(params, update_progress)
start_button.clicked.connect(call_back)
return app, window
def parse_arguments():
parser = argparse.ArgumentParser(description='Wakapi test data insertion script.')
parser.add_argument('--headless', default=False, help='do not show a gui', action='store_true')
parser.add_argument('-n', type=int, default=20, help='total number of random heartbeats to generate and insert')
parser.add_argument('-u', '--url', type=str, default='http://localhost:3000/api',
help='url of your api\'s heartbeats endpoint')
parser.add_argument('-k', '--apikey', type=str,
help='your api key (to get one, go to the web interface, create a new user, log in and copy the key)')
parser.add_argument('-p', '--projects', type=int, default=5, help='number of different fake projects to generate')
parser.add_argument('-o', '--offset', type=int, default=24,
help='negative time offset in hours from now for to be used as an interval within which to generate heartbeats for')
parser.add_argument('-s', '--seed', type=int, default=2020,
help='a seed for initializing the pseudo-random number generator')
return parser.parse_args()
def args_to_params(parsed_args: argparse.Namespace) -> (ConfigParams, bool):
params = ConfigParams()
params.n = parsed_args.n
params.n_projects = parsed_args.projects
params.offset = parsed_args.offset
params.seed = parsed_args.seed
params.api_url = parsed_args.url
params.api_key = parsed_args.apikey
return params, not parsed_args.headless
def randomword(length: int) -> str:
letters = string.ascii_lowercase + 'äöü💩' # test utf8 and utf8mb4 characters as well
return ''.join(random.choice(letters) for _ in range(length))
def run(params: ConfigParams, update_progress: Callable[[int], None]):
random.seed(params.seed)
data: List[Heartbeat] = generate_data(
params.n,
params.n_projects,
params.offset * -1 if params.offset < 0 else params.offset
)
for d in data:
post_data_sync([d], f'{params.api_url}/heartbeats', params.api_key)
update_progress(1)
if __name__ == '__main__':
params, show_gui = args_to_params(parse_arguments())
if show_gui:
app, window = make_gui(callback=run)
window.show()
app.exec()
else:
pbar = tqdm(total=params.n)
run(params, pbar.update)

View File

@ -27,7 +27,18 @@ func (srv *HeartbeatService) Insert(heartbeat *models.Heartbeat) error {
}
func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
return srv.repository.InsertBatch(heartbeats)
hashes := make(map[string]bool)
// https://github.com/muety/wakapi/issues/139
filteredHeartbeats := make([]*models.Heartbeat, 0, len(heartbeats))
for _, hb := range heartbeats {
if _, ok := hashes[hb.Hash]; !ok {
filteredHeartbeats = append(filteredHeartbeats, hb)
hashes[hb.Hash] = true
}
}
return srv.repository.InsertBatch(filteredHeartbeats)
}
func (srv *HeartbeatService) Count() (int64, error) {

View File

@ -79,7 +79,7 @@ func (srv *MiscService) runCountTotalTime() error {
func (srv *MiscService) countTotalTimeWorker(jobs <-chan *CountTotalTimeJob, results chan<- *CountTotalTimeResult) {
for job := range jobs {
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve); err != nil {
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve, false); err != nil {
logbuch.Error("failed to count total for user %s: %v", job.UserID, err)
} else {
logbuch.Info("successfully counted total for user %s", job.UserID)

View File

@ -53,7 +53,7 @@ type ILanguageMappingService interface {
}
type ISummaryService interface {
Aliased(time.Time, time.Time, *models.User, SummaryRetriever) (*models.Summary, error)
Aliased(time.Time, time.Time, *models.User, SummaryRetriever, bool) (*models.Summary, error)
Retrieve(time.Time, time.Time, *models.User) (*models.Summary, error)
Summarize(time.Time, time.Time, *models.User) (*models.Summary, error)
GetLatestByUser() ([]*models.TimeByUser, error)

View File

@ -36,10 +36,10 @@ func NewSummaryService(summaryRepo repositories.ISummaryRepository, heartbeatSer
// Public summary generation methods
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever) (*models.Summary, error) {
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever, skipCache bool) (*models.Summary, error) {
// Check cache
cacheKey := srv.getHash(from.String(), to.String(), user.ID, "--aliased")
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
if cacheResult, ok := srv.cache.Get(cacheKey); ok && !skipCache {
return cacheResult.(*models.Summary), nil
}

View File

@ -253,7 +253,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
suite.AliasService.On("GetAliasOrDefault", TestUserId, models.SummaryProject, TestProject1).Return(TestProject2, nil)
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil)
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize)
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, false)
assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), result)

View File

@ -68,6 +68,7 @@ func (srv *UserService) Count() (int64, error) {
func (srv *UserService) CreateOrGet(signup *models.Signup, isAdmin bool) (*models.User, bool, error) {
u := &models.User{
ID: signup.Username,
Email: signup.Email,
ApiKey: uuid.NewV4().String(),
Password: signup.Password,
IsAdmin: isAdmin,

View File

@ -1,3 +1,3 @@
sonar.exclusions=**/*_test.go,.idea/**,.vscode/**,mocks/**
sonar.exclusions=**/*_test.go,.idea/**,.vscode/**,mocks/**,static/**
sonar.tests=.
sonar.go.coverage.reportPaths=coverage/coverage.out

View File

@ -112,7 +112,7 @@ function draw(subselection) {
xAxes: [{
scaleLabel: {
display: true,
labelString: 'Minutes'
labelString: 'Seconds'
}
}]
},

105251
static/assets/vendor/tailwind.css vendored Normal file

File diff suppressed because it is too large Load Diff

1267
static/assets/vendor/tailwind.dist.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
tailwind.config.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
purge: {
enabled: true,
mode: 'all',
content: ['./views/*.tpl.html']
}
}

View File

@ -71,7 +71,7 @@ func ResolveInterval(interval *models.IntervalKey) (err error, from, to time.Tim
}
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
user := r.Context().Value(models.UserKey).(*models.User)
user := extractUser(r)
params := r.URL.Query()
var err error
@ -108,3 +108,13 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
Recompute: recompute,
}, nil
}
func extractUser(r *http.Request) *models.User {
type principalGetter interface {
GetPrincipal() *models.User
}
if p := r.Context().Value("principal"); p != nil {
return p.(principalGetter).GetPrincipal()
}
return nil
}

View File

@ -1 +1 @@
1.24.4
1.25.4

View File

@ -1,2 +1,2 @@
<script src="assets/vendor/seedrandom.min.js" integrity="sha384-bFS5CG904xYIgxBcrDF4KFNXuM7KeSGsSvS/QTaDqMTEdbaaxjg2Y2TSU3Ygs7wG" crossorigin="anonymous"></script>
<script src="assets/vendor/Chart.bundle.min.js" integrity="sha384-mZ3q69BYmd4GxHp59G3RrsaFdWDxVSoqd7oVYuWRm2qiXrduT63lQtlhdD9lKbm3" crossorigin="anonymous"></script>
<script src="assets/vendor/seedrandom.min.js"></script>
<script src="assets/vendor/Chart.bundle.min.js"></script>

View File

@ -7,6 +7,10 @@
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon-16x16.png">
<link rel="manifest" href="assets/site.webmanifest">
<link href="assets/vendor/roboto.css" rel="stylesheet">
<link href="assets/vendor/tailwind.min.css" rel="stylesheet">
{{ if isDev }}
<link href="assets/vendor/tailwind.css" rel="stylesheet">
{{ else }}
<link href="assets/vendor/tailwind.dist.css" rel="stylesheet">
{{ end }}
<link href="assets/app.css" rel="stylesheet">
</head>

View File

@ -35,6 +35,32 @@
<div class="flex flex-col flex-grow max-w-2xl mt-8">
<details class="my-8 pb-8 border-b border-gray-700">
<summary class="cursor-pointer">
<h2 class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"
id="email-heading">
Change E-Mail Address
</h2>
</summary>
<div class="w-full">
<form class="mt-10" action="" method="post">
<input type="hidden" name="action" value="update_user">
<div class="mb-8 flex justify-between items-center space-x-4">
<label class="inline-block text-sm text-gray-500" for="password_old">E-Mail Address</label>
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded flex-grow py-1 px-3"
type="email" id="email"
name="email" placeholder="Enter your e-mail address"
value="{{ .User.Email }}">
<button type="submit"
class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">
Save
</button>
</div>
<div class="text-gray-300 text-sm">E-Mail address is optional, but required for some features that you cannot use else. Also, if you do not add an e-mail address, you will not be able to reset your password in case you forget it.</div>
</form>
</div>
</details>
<details class="mb-8 pb-8 border-b border-gray-700">
<summary class="cursor-pointer">
<h2 class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="password">
Change Password

View File

@ -3,7 +3,7 @@
{{ template "head.tpl.html" . }}
<body class="bg-gray-800 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
<body class="bg-gray-850 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
<div class="w-full flex justify-center">
<div class="flex items-center justify-between max-w-4xl flex-grow">
<div><a href="" class="text-gray-500 text-sm">&larr; Go back</a></div>
@ -37,6 +37,13 @@
type="text" id="username"
name="username" placeholder="Choose a username" minlength="1" required autofocus>
</div>
<div class="mb-8">
<label class="inline-block text-sm mb-1 text-gray-500" for="email">E-Mail</label>
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
type="email" id="email"
name="email" placeholder="Optionally add your e-mail address">
<div class="text-xs text-gray-500 mt-2 italic">E-Mail address is optional, but required for some features that you cannot use else. Also, if you do not add an e-mail address, you will not be able to reset your password in case you forget it.</div>
</div>
<div class="mb-8">
<label class="inline-block text-sm mb-1 text-gray-500" for="password">Password</label>
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"

View File

@ -45,12 +45,12 @@
<form class="text-white flex flex-nowrap items-center justify-center self-center max-w-xl flex-wrap space-x-8">
<div class="flex space-x-1">
<label for="from-date-picker" class="text-gray-300 pl-1">▶️ Start:</label>
<input id="from-date-picker" type="date" name="from" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
<input id="from-date-picker" type="date" name="from" max="{{ .ToTime.T | simpledate }}" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
value="{{ .FromTime.T | simpledate }}" required>
</div>
<div class="flex space-x-1">
<label for="to-date-picker" class="text-gray-300 pl-1">⏹️ End:</label>
<input id="to-date-picker" type="date" name="to" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
<input id="to-date-picker" type="date" name="to" min="{{ .FromTime.T | simpledate }}" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
value="{{ .ToTime.T | simpledate }}" required>
</div>
<div>
@ -88,7 +88,7 @@
</span>
<div class="flex flex-wrap justify-center">
<div class="w-full lg:w-1/2 p-1">
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="project-container" style="height: 300px">
<div class="flex justify-between">
<div class="w-1/4 flex-1"></div>
@ -104,7 +104,7 @@
</div>
</div>
</div>
<div class="w-full lg:w-1/2 p-1">
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="os-container" style="height: 300px">
<div class="flex justify-between">
<div class="w-1/4 flex-1"></div>
@ -120,7 +120,7 @@
</div>
</div>
</div>
<div class="w-full lg:w-1/2 p-1">
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col relative" id="language-container" style="height: 300px">
<div class="flex justify-between">
<div class="w-1/4 flex-1"></div>
@ -136,7 +136,7 @@
</div>
</div>
</div>
<div class="w-full lg:w-1/2 p-1">
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="editor-container" style="height: 300px">
<div class="flex justify-between">
<div class="w-1/4 flex-1"></div>
@ -152,7 +152,7 @@
</div>
</div>
</div>
<div class="w-full lg:w-1/2 p-1">
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="machine-container" style="height: 300px">
<div class="flex justify-between">
<div class="w-1/4 flex-1"></div>
@ -224,6 +224,17 @@
wakapiData.editors = {{ .Editors | json }}
wakapiData.languages = {{ .Languages | json }}
wakapiData.machines = {{ .Machines | json }}
document.getElementById("to-date-picker").onchange = function () {
var input = document.getElementById("from-date-picker");
input.setAttribute("max", this.value);
}
document.getElementById("from-date-picker").onchange = function () {
var input = document.getElementById("to-date-picker");
input.setAttribute("min", this.value);
}
</script>
<script src="assets/app.js"></script>