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

feat: add ui for managing aliases (resolve #91)

This commit is contained in:
Ferdinand Mütsch 2021-01-21 00:26:52 +01:00
parent 16b683fcbd
commit d57c02af7c
20 changed files with 865 additions and 365 deletions

View File

@ -114,24 +114,6 @@ You can view your API Key after logging in to the web interface.
See the [advanced setup instructions](docs/advanced_setup.md). See the [advanced setup instructions](docs/advanced_setup.md).
## 🔵 Customization
### Aliases
There is an option to add aliases for project names, editors, operating systems and languages. For instance, if you want to map two projects `myapp-frontend` and `myapp-backend` two a common project name `myapp-web` in your statistics, you can add project aliases.
At the moment, this can only be done via raw database queries. For the above example, you would need to add two aliases, like this:
```sql
INSERT INTO aliases (`type`, `user_id`, `key`, `value`) VALUES (0, 'your_username', 'myapp-web', 'myapp-frontend');
```
#### Types
* Project ~ type **0**
* Language ~ type **1**
* Editor ~ type **2**
* OS ~ type **3**
* Machine ~ type **4**
## 🔧 API Endpoints ## 🔧 API Endpoints
The following API endpoints are available. A more detailed Swagger documentation is about to come ([#40](https://github.com/muety/wakapi/issues/40)). The following API endpoints are available. A more detailed Swagger documentation is about to come ([#40](https://github.com/muety/wakapi/issues/40)).

View File

@ -1,18 +1,4 @@
mode: set mode: set
github.com/muety/wakapi/models/shared.go:34.52,37.16 3 0
github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
github.com/muety/wakapi/models/shared.go:74.51,77.2 2 0
github.com/muety/wakapi/models/shared.go:79.37,82.2 2 0
github.com/muety/wakapi/models/shared.go:84.35,86.2 1 0
github.com/muety/wakapi/models/shared.go:88.34,90.2 1 0
github.com/muety/wakapi/models/filters.go:16.56,17.16 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:29.2,29.19 1 0
github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0 github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
@ -42,34 +28,6 @@ github.com/muety/wakapi/models/filters.go:53.20,55.3 1 0
github.com/muety/wakapi/models/filters.go:56.22,58.3 1 1 github.com/muety/wakapi/models/filters.go:56.22,58.3 1 1
github.com/muety/wakapi/models/filters.go:59.21,61.3 1 0 github.com/muety/wakapi/models/filters.go:59.21,61.3 1 0
github.com/muety/wakapi/models/filters.go:62.16,64.3 1 0 github.com/muety/wakapi/models/filters.go:62.16,64.3 1 0
github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0
github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0
github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0
github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0
github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0
github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0
github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
github.com/muety/wakapi/models/heartbeat.go:26.34,28.2 1 1
github.com/muety/wakapi/models/heartbeat.go:30.65,31.28 1 1
github.com/muety/wakapi/models/heartbeat.go:34.2,35.45 2 1
github.com/muety/wakapi/models/heartbeat.go:38.2,39.44 2 1
github.com/muety/wakapi/models/heartbeat.go:42.2,42.42 1 1
github.com/muety/wakapi/models/heartbeat.go:31.28,33.3 1 1
github.com/muety/wakapi/models/heartbeat.go:35.45,37.3 1 0
github.com/muety/wakapi/models/heartbeat.go:39.44,41.3 1 0
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/language_mapping.go:11.42,13.2 1 0 github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0 github.com/muety/wakapi/models/language_mapping.go: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/language_mapping.go:19.52,21.2 1 0
@ -127,89 +85,81 @@ github.com/muety/wakapi/models/user.go:34.43,37.2 1 0
github.com/muety/wakapi/models/user.go:39.33,43.2 1 0 github.com/muety/wakapi/models/user.go:39.33,43.2 1 0
github.com/muety/wakapi/models/user.go:45.45,47.2 1 0 github.com/muety/wakapi/models/user.go:45.45,47.2 1 0
github.com/muety/wakapi/models/user.go:49.45,51.2 1 0 github.com/muety/wakapi/models/user.go:49.45,51.2 1 0
github.com/muety/wakapi/config/config.go:77.70,79.2 1 0 github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
github.com/muety/wakapi/config/config.go:81.65,83.2 1 0 github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
github.com/muety/wakapi/config/config.go:85.82,95.2 1 0 github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
github.com/muety/wakapi/config/config.go:97.31,99.2 1 0 github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
github.com/muety/wakapi/config/config.go:101.32,103.2 1 0 github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
github.com/muety/wakapi/config/config.go:105.74,106.19 1 0 github.com/muety/wakapi/models/heartbeat.go:26.34,28.2 1 1
github.com/muety/wakapi/config/config.go:107.10,108.34 1 0 github.com/muety/wakapi/models/heartbeat.go:30.65,31.28 1 1
github.com/muety/wakapi/config/config.go:108.34,117.4 8 0 github.com/muety/wakapi/models/heartbeat.go:34.2,35.45 2 1
github.com/muety/wakapi/config/config.go:121.73,122.33 1 0 github.com/muety/wakapi/models/heartbeat.go:38.2,39.44 2 1
github.com/muety/wakapi/config/config.go:122.33,130.17 5 0 github.com/muety/wakapi/models/heartbeat.go:42.2,42.42 1 1
github.com/muety/wakapi/config/config.go:134.3,135.13 2 0 github.com/muety/wakapi/models/heartbeat.go:31.28,33.3 1 1
github.com/muety/wakapi/config/config.go:130.17,132.4 1 0 github.com/muety/wakapi/models/heartbeat.go:35.45,37.3 1 0
github.com/muety/wakapi/config/config.go:139.50,140.19 1 0 github.com/muety/wakapi/models/heartbeat.go:39.44,41.3 1 0
github.com/muety/wakapi/config/config.go:153.2,153.12 1 0 github.com/muety/wakapi/models/heartbeat.go:45.50,46.11 1 1
github.com/muety/wakapi/config/config.go:141.23,145.5 1 0 github.com/muety/wakapi/models/heartbeat.go:59.2,59.15 1 1
github.com/muety/wakapi/config/config.go:146.26,149.5 1 0 github.com/muety/wakapi/models/heartbeat.go:63.2,63.12 1 1
github.com/muety/wakapi/config/config.go:150.24,151.48 1 0 github.com/muety/wakapi/models/heartbeat.go:47.22,48.18 1 1
github.com/muety/wakapi/config/config.go:156.53,166.2 1 1 github.com/muety/wakapi/models/heartbeat.go:49.21,50.17 1 1
github.com/muety/wakapi/config/config.go:168.56,176.2 1 1 github.com/muety/wakapi/models/heartbeat.go:51.23,52.19 1 1
github.com/muety/wakapi/config/config.go:178.54,180.2 1 1 github.com/muety/wakapi/models/heartbeat.go:53.17,54.26 1 1
github.com/muety/wakapi/config/config.go:182.60,184.2 1 0 github.com/muety/wakapi/models/heartbeat.go:55.22,56.18 1 1
github.com/muety/wakapi/config/config.go:186.59,188.2 1 0 github.com/muety/wakapi/models/heartbeat.go:59.15,61.3 1 1
github.com/muety/wakapi/config/config.go:190.29,192.2 1 1 github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0
github.com/muety/wakapi/config/config.go:194.27,196.16 2 0 github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0
github.com/muety/wakapi/config/config.go:199.2,202.16 3 0 github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0
github.com/muety/wakapi/config/config.go:206.2,206.22 1 0 github.com/muety/wakapi/models/heartbeats.go:19.43,22.2 2 0
github.com/muety/wakapi/config/config.go:196.16,198.3 1 0 github.com/muety/wakapi/models/heartbeats.go:24.41,26.18 1 0
github.com/muety/wakapi/config/config.go:202.16,204.3 1 0 github.com/muety/wakapi/models/heartbeats.go:29.2,29.16 1 0
github.com/muety/wakapi/config/config.go:209.45,219.16 4 0 github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
github.com/muety/wakapi/config/config.go:223.2,223.57 1 0 github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
github.com/muety/wakapi/config/config.go:227.2,227.30 1 0 github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
github.com/muety/wakapi/config/config.go:231.2,231.15 1 0 github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
github.com/muety/wakapi/config/config.go:219.16,221.3 1 0 github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
github.com/muety/wakapi/config/config.go:223.57,225.3 1 0 github.com/muety/wakapi/models/shared.go:34.52,37.16 3 0
github.com/muety/wakapi/config/config.go:227.30,229.3 1 0 github.com/muety/wakapi/models/shared.go:40.2,42.12 3 0
github.com/muety/wakapi/config/config.go:234.38,235.43 1 0 github.com/muety/wakapi/models/shared.go:37.16,39.3 1 0
github.com/muety/wakapi/config/config.go:239.2,239.15 1 0 github.com/muety/wakapi/models/shared.go:46.52,52.22 2 0
github.com/muety/wakapi/config/config.go:235.43,237.3 1 0 github.com/muety/wakapi/models/shared.go:68.2,71.12 3 0
github.com/muety/wakapi/config/config.go:242.26,244.2 1 0 github.com/muety/wakapi/models/shared.go:53.14,55.17 2 0
github.com/muety/wakapi/config/config.go:246.20,248.2 1 0 github.com/muety/wakapi/models/shared.go:58.13,60.8 2 0
github.com/muety/wakapi/config/config.go:250.21,257.96 4 0 github.com/muety/wakapi/models/shared.go:61.17,63.8 2 0
github.com/muety/wakapi/config/config.go:261.2,268.52 4 0 github.com/muety/wakapi/models/shared.go:64.10,65.64 1 0
github.com/muety/wakapi/config/config.go:272.2,272.47 1 0 github.com/muety/wakapi/models/shared.go:55.17,57.4 1 0
github.com/muety/wakapi/config/config.go:278.2,278.70 1 0 github.com/muety/wakapi/models/shared.go:74.51,77.2 2 0
github.com/muety/wakapi/config/config.go:282.2,283.14 2 0 github.com/muety/wakapi/models/shared.go:79.37,82.2 2 0
github.com/muety/wakapi/config/config.go:257.96,259.3 1 0 github.com/muety/wakapi/models/shared.go:84.35,86.2 1 0
github.com/muety/wakapi/config/config.go:268.52,270.3 1 0 github.com/muety/wakapi/models/shared.go:88.34,90.2 1 0
github.com/muety/wakapi/config/config.go:272.47,273.14 1 0 github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
github.com/muety/wakapi/config/config.go:273.14,275.4 1 0 github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
github.com/muety/wakapi/config/config.go:278.70,280.3 1 0 github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
github.com/muety/wakapi/config/legacy.go:13.33,14.57 1 0 github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
github.com/muety/wakapi/config/legacy.go:14.57,16.3 1 0 github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
github.com/muety/wakapi/config/legacy.go:16.8,16.16 1 0 github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
github.com/muety/wakapi/config/legacy.go:16.16,18.47 2 0 github.com/muety/wakapi/utils/auth.go:18.79,20.54 2 0
github.com/muety/wakapi/config/legacy.go:21.3,21.128 1 0 github.com/muety/wakapi/utils/auth.go:24.2,26.16 3 0
github.com/muety/wakapi/config/legacy.go:18.47,20.4 1 0 github.com/muety/wakapi/utils/auth.go:30.2,32.45 3 0
github.com/muety/wakapi/config/legacy.go:25.48,26.54 1 0 github.com/muety/wakapi/utils/auth.go:35.2,36.32 2 0
github.com/muety/wakapi/config/legacy.go:31.2,31.18 1 0 github.com/muety/wakapi/utils/auth.go:20.54,22.3 1 0
github.com/muety/wakapi/config/legacy.go:26.54,28.3 1 0 github.com/muety/wakapi/utils/auth.go:26.16,28.3 1 0
github.com/muety/wakapi/config/legacy.go:28.8,28.32 1 0 github.com/muety/wakapi/utils/auth.go:32.45,34.3 1 0
github.com/muety/wakapi/config/legacy.go:28.32,30.3 1 0 github.com/muety/wakapi/utils/auth.go:39.65,41.54 2 0
github.com/muety/wakapi/config/legacy.go:34.34,37.16 2 0 github.com/muety/wakapi/utils/auth.go:45.2,46.30 2 0
github.com/muety/wakapi/config/legacy.go:40.2,41.16 2 0 github.com/muety/wakapi/utils/auth.go:41.54,43.3 1 0
github.com/muety/wakapi/config/legacy.go:45.2,57.16 11 0 github.com/muety/wakapi/utils/auth.go:49.97,51.16 2 0
github.com/muety/wakapi/config/legacy.go:61.2,61.18 1 0 github.com/muety/wakapi/utils/auth.go:55.2,55.104 1 0
github.com/muety/wakapi/config/legacy.go:65.2,69.16 5 0 github.com/muety/wakapi/utils/auth.go:59.2,59.19 1 0
github.com/muety/wakapi/config/legacy.go:73.2,75.23 3 0 github.com/muety/wakapi/utils/auth.go:51.16,53.3 1 0
github.com/muety/wakapi/config/legacy.go:80.2,82.33 3 0 github.com/muety/wakapi/utils/auth.go:55.104,57.3 1 0
github.com/muety/wakapi/config/legacy.go:87.2,114.16 3 0 github.com/muety/wakapi/utils/auth.go:62.30,64.2 1 0
github.com/muety/wakapi/config/legacy.go:119.2,119.78 1 0 github.com/muety/wakapi/utils/auth.go:66.56,70.2 3 0
github.com/muety/wakapi/config/legacy.go:123.2,123.12 1 0 github.com/muety/wakapi/utils/auth.go:73.53,75.2 1 0
github.com/muety/wakapi/config/legacy.go:37.16,39.3 1 0 github.com/muety/wakapi/utils/auth.go:77.55,80.16 3 0
github.com/muety/wakapi/config/legacy.go:41.16,43.3 1 0 github.com/muety/wakapi/utils/auth.go:83.2,83.16 1 0
github.com/muety/wakapi/config/legacy.go:57.16,59.3 1 0 github.com/muety/wakapi/utils/auth.go:80.16,82.3 1 0
github.com/muety/wakapi/config/legacy.go:61.18,63.3 1 0 github.com/muety/wakapi/utils/auth.go:86.43,91.2 4 0
github.com/muety/wakapi/config/legacy.go:69.16,71.3 1 0
github.com/muety/wakapi/config/legacy.go:75.23,77.3 1 0
github.com/muety/wakapi/config/legacy.go:82.33,84.3 1 0
github.com/muety/wakapi/config/legacy.go:114.16,116.3 1 0
github.com/muety/wakapi/config/legacy.go:119.78,121.3 1 0
github.com/muety/wakapi/config/utils.go:3.60,5.22 2 0
github.com/muety/wakapi/config/utils.go:8.2,8.11 1 0
github.com/muety/wakapi/config/utils.go:5.22,7.3 1 0
github.com/muety/wakapi/utils/color.go:8.93,10.41 2 0 github.com/muety/wakapi/utils/color.go:8.93,10.41 2 0
github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0 github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0
github.com/muety/wakapi/utils/color.go:10.41,11.50 1 0 github.com/muety/wakapi/utils/color.go:10.41,11.50 1 0
@ -217,9 +167,10 @@ github.com/muety/wakapi/utils/color.go:11.50,13.4 1 0
github.com/muety/wakapi/utils/common.go:9.48,11.2 1 0 github.com/muety/wakapi/utils/common.go:9.48,11.2 1 0
github.com/muety/wakapi/utils/common.go:13.40,15.2 1 0 github.com/muety/wakapi/utils/common.go:13.40,15.2 1 0
github.com/muety/wakapi/utils/common.go:17.45,19.2 1 0 github.com/muety/wakapi/utils/common.go:17.45,19.2 1 0
github.com/muety/wakapi/utils/common.go:21.56,24.45 3 1 github.com/muety/wakapi/utils/common.go:21.24,23.2 1 0
github.com/muety/wakapi/utils/common.go:27.2,27.40 1 1 github.com/muety/wakapi/utils/common.go:25.56,28.45 3 1
github.com/muety/wakapi/utils/common.go:24.45,26.3 1 1 github.com/muety/wakapi/utils/common.go:31.2,31.40 1 1
github.com/muety/wakapi/utils/common.go:28.45,30.3 1 1
github.com/muety/wakapi/utils/date.go:8.31,10.2 1 0 github.com/muety/wakapi/utils/date.go: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: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:16.30,20.2 3 0
@ -264,31 +215,96 @@ github.com/muety/wakapi/utils/summary.go:49.8,51.17 2 0
github.com/muety/wakapi/utils/summary.go:55.3,56.17 2 0 github.com/muety/wakapi/utils/summary.go:55.3,56.17 2 0
github.com/muety/wakapi/utils/summary.go:51.17,53.4 1 0 github.com/muety/wakapi/utils/summary.go:51.17,53.4 1 0
github.com/muety/wakapi/utils/summary.go:56.17,58.4 1 0 github.com/muety/wakapi/utils/summary.go:56.17,58.4 1 0
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0 github.com/muety/wakapi/config/config.go:83.70,85.2 1 0
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0 github.com/muety/wakapi/config/config.go:87.65,89.2 1 0
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0 github.com/muety/wakapi/config/config.go:91.82,101.2 1 0
github.com/muety/wakapi/utils/auth.go:18.79,20.54 2 0 github.com/muety/wakapi/config/config.go:103.31,105.2 1 0
github.com/muety/wakapi/utils/auth.go:24.2,26.16 3 0 github.com/muety/wakapi/config/config.go:107.32,109.2 1 0
github.com/muety/wakapi/utils/auth.go:30.2,32.45 3 0 github.com/muety/wakapi/config/config.go:111.74,112.19 1 0
github.com/muety/wakapi/utils/auth.go:35.2,36.32 2 0 github.com/muety/wakapi/config/config.go:113.10,114.34 1 0
github.com/muety/wakapi/utils/auth.go:20.54,22.3 1 0 github.com/muety/wakapi/config/config.go:114.34,123.4 8 0
github.com/muety/wakapi/utils/auth.go:26.16,28.3 1 0 github.com/muety/wakapi/config/config.go:127.73,128.33 1 0
github.com/muety/wakapi/utils/auth.go:32.45,34.3 1 0 github.com/muety/wakapi/config/config.go:128.33,136.17 5 0
github.com/muety/wakapi/utils/auth.go:39.65,41.54 2 0 github.com/muety/wakapi/config/config.go:140.3,141.13 2 0
github.com/muety/wakapi/utils/auth.go:45.2,46.30 2 0 github.com/muety/wakapi/config/config.go:136.17,138.4 1 0
github.com/muety/wakapi/utils/auth.go:41.54,43.3 1 0 github.com/muety/wakapi/config/config.go:145.50,146.19 1 0
github.com/muety/wakapi/utils/auth.go:49.97,51.16 2 0 github.com/muety/wakapi/config/config.go:159.2,159.12 1 0
github.com/muety/wakapi/utils/auth.go:55.2,55.104 1 0 github.com/muety/wakapi/config/config.go:147.23,151.5 1 0
github.com/muety/wakapi/utils/auth.go:59.2,59.19 1 0 github.com/muety/wakapi/config/config.go:152.26,155.5 1 0
github.com/muety/wakapi/utils/auth.go:51.16,53.3 1 0 github.com/muety/wakapi/config/config.go:156.24,157.48 1 0
github.com/muety/wakapi/utils/auth.go:55.104,57.3 1 0 github.com/muety/wakapi/config/config.go:162.53,172.2 1 1
github.com/muety/wakapi/utils/auth.go:62.30,64.2 1 0 github.com/muety/wakapi/config/config.go:174.56,176.16 2 1
github.com/muety/wakapi/utils/auth.go:66.56,70.2 3 0 github.com/muety/wakapi/config/config.go:180.2,187.3 1 1
github.com/muety/wakapi/utils/auth.go:73.53,75.2 1 0 github.com/muety/wakapi/config/config.go:176.16,178.3 1 0
github.com/muety/wakapi/utils/auth.go:77.55,80.16 3 0 github.com/muety/wakapi/config/config.go:190.54,192.2 1 1
github.com/muety/wakapi/utils/auth.go:83.2,83.16 1 0 github.com/muety/wakapi/config/config.go:194.60,196.2 1 0
github.com/muety/wakapi/utils/auth.go:80.16,82.3 1 0 github.com/muety/wakapi/config/config.go:198.59,200.2 1 0
github.com/muety/wakapi/utils/auth.go:86.43,91.2 4 0 github.com/muety/wakapi/config/config.go:202.29,204.2 1 1
github.com/muety/wakapi/config/config.go:206.27,208.16 2 0
github.com/muety/wakapi/config/config.go:211.2,214.16 3 0
github.com/muety/wakapi/config/config.go:218.2,218.22 1 0
github.com/muety/wakapi/config/config.go:208.16,210.3 1 0
github.com/muety/wakapi/config/config.go:214.16,216.3 1 0
github.com/muety/wakapi/config/config.go:221.45,231.16 4 0
github.com/muety/wakapi/config/config.go:235.2,235.57 1 0
github.com/muety/wakapi/config/config.go:239.2,239.30 1 0
github.com/muety/wakapi/config/config.go:243.2,243.15 1 0
github.com/muety/wakapi/config/config.go:231.16,233.3 1 0
github.com/muety/wakapi/config/config.go:235.57,237.3 1 0
github.com/muety/wakapi/config/config.go:239.30,241.3 1 0
github.com/muety/wakapi/config/config.go:246.38,247.43 1 0
github.com/muety/wakapi/config/config.go:251.2,251.15 1 0
github.com/muety/wakapi/config/config.go:247.43,249.3 1 0
github.com/muety/wakapi/config/config.go:254.45,255.27 1 0
github.com/muety/wakapi/config/config.go:258.2,258.15 1 0
github.com/muety/wakapi/config/config.go:255.27,257.3 1 0
github.com/muety/wakapi/config/config.go:261.26,263.2 1 0
github.com/muety/wakapi/config/config.go:265.20,267.2 1 0
github.com/muety/wakapi/config/config.go:269.21,276.96 4 0
github.com/muety/wakapi/config/config.go:280.2,288.52 5 0
github.com/muety/wakapi/config/config.go:292.2,292.47 1 0
github.com/muety/wakapi/config/config.go:298.2,298.70 1 0
github.com/muety/wakapi/config/config.go:302.2,303.14 2 0
github.com/muety/wakapi/config/config.go:276.96,278.3 1 0
github.com/muety/wakapi/config/config.go:288.52,290.3 1 0
github.com/muety/wakapi/config/config.go:292.47,293.14 1 0
github.com/muety/wakapi/config/config.go:293.14,295.4 1 0
github.com/muety/wakapi/config/config.go:298.70,300.3 1 0
github.com/muety/wakapi/config/legacy.go:13.33,14.57 1 0
github.com/muety/wakapi/config/legacy.go:14.57,16.3 1 0
github.com/muety/wakapi/config/legacy.go:16.8,16.16 1 0
github.com/muety/wakapi/config/legacy.go:16.16,18.47 2 0
github.com/muety/wakapi/config/legacy.go:21.3,21.128 1 0
github.com/muety/wakapi/config/legacy.go:18.47,20.4 1 0
github.com/muety/wakapi/config/legacy.go:25.48,26.54 1 0
github.com/muety/wakapi/config/legacy.go:31.2,31.18 1 0
github.com/muety/wakapi/config/legacy.go:26.54,28.3 1 0
github.com/muety/wakapi/config/legacy.go:28.8,28.32 1 0
github.com/muety/wakapi/config/legacy.go:28.32,30.3 1 0
github.com/muety/wakapi/config/legacy.go:34.34,37.16 2 0
github.com/muety/wakapi/config/legacy.go:40.2,41.16 2 0
github.com/muety/wakapi/config/legacy.go:45.2,57.16 11 0
github.com/muety/wakapi/config/legacy.go:61.2,61.18 1 0
github.com/muety/wakapi/config/legacy.go:65.2,69.16 5 0
github.com/muety/wakapi/config/legacy.go:73.2,75.23 3 0
github.com/muety/wakapi/config/legacy.go:80.2,82.33 3 0
github.com/muety/wakapi/config/legacy.go:87.2,114.16 3 0
github.com/muety/wakapi/config/legacy.go:119.2,119.78 1 0
github.com/muety/wakapi/config/legacy.go:123.2,123.12 1 0
github.com/muety/wakapi/config/legacy.go:37.16,39.3 1 0
github.com/muety/wakapi/config/legacy.go:41.16,43.3 1 0
github.com/muety/wakapi/config/legacy.go:57.16,59.3 1 0
github.com/muety/wakapi/config/legacy.go:61.18,63.3 1 0
github.com/muety/wakapi/config/legacy.go:69.16,71.3 1 0
github.com/muety/wakapi/config/legacy.go:75.23,77.3 1 0
github.com/muety/wakapi/config/legacy.go:82.33,84.3 1 0
github.com/muety/wakapi/config/legacy.go:114.16,116.3 1 0
github.com/muety/wakapi/config/legacy.go:119.78,121.3 1 0
github.com/muety/wakapi/config/utils.go:3.60,5.22 2 0
github.com/muety/wakapi/config/utils.go:8.2,8.11 1 0
github.com/muety/wakapi/config/utils.go:5.22,7.3 1 0
github.com/muety/wakapi/middlewares/logging.go:11.48,13.2 1 0
github.com/muety/wakapi/middlewares/logging.go:15.66,17.2 1 0
github.com/muety/wakapi/middlewares/authenticate.go:27.116,34.2 1 1 github.com/muety/wakapi/middlewares/authenticate.go:27.116,34.2 1 1
github.com/muety/wakapi/middlewares/authenticate.go:36.71,37.71 1 0 github.com/muety/wakapi/middlewares/authenticate.go:36.71,37.71 1 0
github.com/muety/wakapi/middlewares/authenticate.go:37.71,39.3 1 0 github.com/muety/wakapi/middlewares/authenticate.go:37.71,39.3 1 0
@ -324,104 +340,38 @@ github.com/muety/wakapi/middlewares/authenticate.go:127.2,127.65 1 0
github.com/muety/wakapi/middlewares/authenticate.go:119.32,120.58 1 0 github.com/muety/wakapi/middlewares/authenticate.go:119.32,120.58 1 0
github.com/muety/wakapi/middlewares/authenticate.go:125.3,125.15 1 0 github.com/muety/wakapi/middlewares/authenticate.go:125.3,125.15 1 0
github.com/muety/wakapi/middlewares/authenticate.go:120.58,124.4 3 0 github.com/muety/wakapi/middlewares/authenticate.go:120.58,124.4 3 0
github.com/muety/wakapi/middlewares/logging.go:11.48,13.2 1 0 github.com/muety/wakapi/services/misc.go:23.126,30.2 1 0
github.com/muety/wakapi/middlewares/logging.go:15.66,17.2 1 0 github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0 github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0 github.com/muety/wakapi/services/misc.go:44.48,46.3 1 0
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0 github.com/muety/wakapi/services/misc.go:53.51,59.40 4 0
github.com/muety/wakapi/services/aggregation.go:42.37,44.3 1 0 github.com/muety/wakapi/services/misc.go:63.2,66.56 2 0
github.com/muety/wakapi/services/aggregation.go:51.67,55.40 3 0 github.com/muety/wakapi/services/misc.go:77.2,77.12 1 0
github.com/muety/wakapi/services/aggregation.go:59.2,59.50 1 0 github.com/muety/wakapi/services/misc.go:59.40,61.3 1 0
github.com/muety/wakapi/services/aggregation.go:64.2,64.60 1 0 github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
github.com/muety/wakapi/services/aggregation.go:70.2,70.35 1 0 github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
github.com/muety/wakapi/services/aggregation.go:55.40,57.3 1 0 github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
github.com/muety/wakapi/services/aggregation.go:59.50,61.3 1 0 github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
github.com/muety/wakapi/services/aggregation.go:64.60,68.3 3 0 github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
github.com/muety/wakapi/services/aggregation.go:73.109,74.24 1 0 github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
github.com/muety/wakapi/services/aggregation.go:74.24,75.111 1 0 github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
github.com/muety/wakapi/services/aggregation.go:75.111,77.4 1 0 github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
github.com/muety/wakapi/services/aggregation.go:77.9,80.4 2 0 github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
github.com/muety/wakapi/services/aggregation.go:84.80,85.33 1 0 github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
github.com/muety/wakapi/services/aggregation.go:85.33,86.60 1 0 github.com/muety/wakapi/services/misc.go:106.2,109.17 1 0
github.com/muety/wakapi/services/aggregation.go:86.60,88.4 1 0 github.com/muety/wakapi/services/misc.go:113.2,116.17 1 0
github.com/muety/wakapi/services/aggregation.go:92.100,96.59 3 0 github.com/muety/wakapi/services/misc.go:101.30,104.3 2 0
github.com/muety/wakapi/services/aggregation.go:111.2,112.16 2 0 github.com/muety/wakapi/services/misc.go:109.17,111.3 1 0
github.com/muety/wakapi/services/aggregation.go:118.2,119.16 2 0 github.com/muety/wakapi/services/misc.go:116.17,118.3 1 0
github.com/muety/wakapi/services/aggregation.go:125.2,126.44 2 0
github.com/muety/wakapi/services/aggregation.go:131.2,131.41 1 0
github.com/muety/wakapi/services/aggregation.go:145.2,145.12 1 0
github.com/muety/wakapi/services/aggregation.go:96.59,99.3 2 0
github.com/muety/wakapi/services/aggregation.go:99.8,99.47 1 0
github.com/muety/wakapi/services/aggregation.go:99.47,101.30 2 0
github.com/muety/wakapi/services/aggregation.go:101.30,102.43 1 0
github.com/muety/wakapi/services/aggregation.go:102.43,104.5 1 0
github.com/muety/wakapi/services/aggregation.go:106.8,108.3 1 0
github.com/muety/wakapi/services/aggregation.go:112.16,115.3 2 0
github.com/muety/wakapi/services/aggregation.go:119.16,122.3 2 0
github.com/muety/wakapi/services/aggregation.go:126.44,128.3 1 0
github.com/muety/wakapi/services/aggregation.go:131.41,132.21 1 0
github.com/muety/wakapi/services/aggregation.go:132.21,136.4 1 0
github.com/muety/wakapi/services/aggregation.go:136.9,136.62 1 0
github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
github.com/muety/wakapi/services/alias.go:16.77,21.2 1 1
github.com/muety/wakapi/services/alias.go:25.63,27.16 2 1
github.com/muety/wakapi/services/alias.go:30.2,30.12 1 1
github.com/muety/wakapi/services/alias.go:27.16,29.3 1 1
github.com/muety/wakapi/services/alias.go:33.108,34.32 1 1
github.com/muety/wakapi/services/alias.go:40.2,41.46 2 1
github.com/muety/wakapi/services/alias.go:46.2,46.19 1 1
github.com/muety/wakapi/services/alias.go:34.32,35.53 1 1
github.com/muety/wakapi/services/alias.go:35.53,37.4 1 1
github.com/muety/wakapi/services/alias.go:41.46,42.48 1 1
github.com/muety/wakapi/services/alias.go:42.48,44.4 1 1
github.com/muety/wakapi/services/alias.go:49.60,50.43 1 1
github.com/muety/wakapi/services/alias.go:53.2,53.14 1 1
github.com/muety/wakapi/services/alias.go:50.43,52.3 1 1
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
github.com/muety/wakapi/services/heartbeat.go:25.80,27.2 1 0
github.com/muety/wakapi/services/heartbeat.go:29.111,31.16 2 0
github.com/muety/wakapi/services/heartbeat.go:34.2,34.43 1 0
github.com/muety/wakapi/services/heartbeat.go:31.16,33.3 1 0
github.com/muety/wakapi/services/heartbeat.go:37.78,39.2 1 0
github.com/muety/wakapi/services/heartbeat.go:41.62,43.2 1 0
github.com/muety/wakapi/services/heartbeat.go:45.116,47.16 2 0
github.com/muety/wakapi/services/heartbeat.go:51.2,51.28 1 0
github.com/muety/wakapi/services/heartbeat.go:55.2,55.24 1 0
github.com/muety/wakapi/services/heartbeat.go:47.16,49.3 1 0
github.com/muety/wakapi/services/heartbeat.go:51.28,53.3 1 0
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
github.com/muety/wakapi/services/key_value.go:25.72,27.2 1 0
github.com/muety/wakapi/services/key_value.go:29.60,31.2 1 0
github.com/muety/wakapi/services/language_mapping.go:17.118,23.2 1 0
github.com/muety/wakapi/services/language_mapping.go:25.86,27.2 1 0
github.com/muety/wakapi/services/language_mapping.go:29.96,30.53 1 0
github.com/muety/wakapi/services/language_mapping.go:34.2,35.16 2 0
github.com/muety/wakapi/services/language_mapping.go:38.2,39.22 2 0
github.com/muety/wakapi/services/language_mapping.go:30.53,32.3 1 0
github.com/muety/wakapi/services/language_mapping.go:35.16,37.3 1 0
github.com/muety/wakapi/services/language_mapping.go:42.92,45.16 3 0
github.com/muety/wakapi/services/language_mapping.go:49.2,49.33 1 0
github.com/muety/wakapi/services/language_mapping.go:52.2,52.22 1 0
github.com/muety/wakapi/services/language_mapping.go:45.16,47.3 1 0
github.com/muety/wakapi/services/language_mapping.go:49.33,51.3 1 0
github.com/muety/wakapi/services/language_mapping.go:55.109,57.16 2 0
github.com/muety/wakapi/services/language_mapping.go:61.2,62.20 2 0
github.com/muety/wakapi/services/language_mapping.go:57.16,59.3 1 0
github.com/muety/wakapi/services/language_mapping.go:65.82,69.2 3 0
github.com/muety/wakapi/services/language_mapping.go:71.74,74.2 1 0
github.com/muety/wakapi/services/summary.go:27.149,35.2 1 1 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.120,42.52 2 1
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1 github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
github.com/muety/wakapi/services/summary.go:53.2,53.66 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: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: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.52,44.3 1 0
github.com/muety/wakapi/services/summary.go:47.44,50.3 2 1 github.com/muety/wakapi/services/summary.go:47.44,50.3 2 1
github.com/muety/wakapi/services/summary.go:53.66,55.3 1 0 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 github.com/muety/wakapi/services/summary.go:59.16,61.3 1 0
github.com/muety/wakapi/services/summary.go:69.101,72.52 2 1 github.com/muety/wakapi/services/summary.go:69.101,72.52 2 1
github.com/muety/wakapi/services/summary.go:77.2,78.16 2 1 github.com/muety/wakapi/services/summary.go:77.2,78.16 2 1
@ -517,3 +467,113 @@ github.com/muety/wakapi/services/user.go:64.106,66.96 2 0
github.com/muety/wakapi/services/user.go:71.2,71.68 1 0 github.com/muety/wakapi/services/user.go:71.2,71.68 1 0
github.com/muety/wakapi/services/user.go:66.96,68.3 1 0 github.com/muety/wakapi/services/user.go:66.96,68.3 1 0
github.com/muety/wakapi/services/user.go:68.8,70.3 1 0 github.com/muety/wakapi/services/user.go:68.8,70.3 1 0
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
github.com/muety/wakapi/services/key_value.go:25.72,27.2 1 0
github.com/muety/wakapi/services/key_value.go:29.60,31.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/aggregation.go:24.142,31.2 1 0
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
github.com/muety/wakapi/services/aggregation.go:42.37,44.3 1 0
github.com/muety/wakapi/services/aggregation.go:51.67,55.40 3 0
github.com/muety/wakapi/services/aggregation.go:59.2,59.50 1 0
github.com/muety/wakapi/services/aggregation.go:64.2,64.60 1 0
github.com/muety/wakapi/services/aggregation.go:70.2,70.35 1 0
github.com/muety/wakapi/services/aggregation.go:55.40,57.3 1 0
github.com/muety/wakapi/services/aggregation.go:59.50,61.3 1 0
github.com/muety/wakapi/services/aggregation.go:64.60,68.3 3 0
github.com/muety/wakapi/services/aggregation.go:73.109,74.24 1 0
github.com/muety/wakapi/services/aggregation.go:74.24,75.111 1 0
github.com/muety/wakapi/services/aggregation.go:75.111,77.4 1 0
github.com/muety/wakapi/services/aggregation.go:77.9,80.4 2 0
github.com/muety/wakapi/services/aggregation.go:84.80,85.33 1 0
github.com/muety/wakapi/services/aggregation.go:85.33,86.60 1 0
github.com/muety/wakapi/services/aggregation.go:86.60,88.4 1 0
github.com/muety/wakapi/services/aggregation.go:92.100,96.59 3 0
github.com/muety/wakapi/services/aggregation.go:111.2,112.16 2 0
github.com/muety/wakapi/services/aggregation.go:118.2,119.16 2 0
github.com/muety/wakapi/services/aggregation.go:125.2,126.44 2 0
github.com/muety/wakapi/services/aggregation.go:131.2,131.41 1 0
github.com/muety/wakapi/services/aggregation.go:145.2,145.12 1 0
github.com/muety/wakapi/services/aggregation.go:96.59,99.3 2 0
github.com/muety/wakapi/services/aggregation.go:99.8,99.47 1 0
github.com/muety/wakapi/services/aggregation.go:99.47,101.30 2 0
github.com/muety/wakapi/services/aggregation.go:101.30,102.43 1 0
github.com/muety/wakapi/services/aggregation.go:102.43,104.5 1 0
github.com/muety/wakapi/services/aggregation.go:106.8,108.3 1 0
github.com/muety/wakapi/services/aggregation.go:112.16,115.3 2 0
github.com/muety/wakapi/services/aggregation.go:119.16,122.3 2 0
github.com/muety/wakapi/services/aggregation.go:126.44,128.3 1 0
github.com/muety/wakapi/services/aggregation.go:131.41,132.21 1 0
github.com/muety/wakapi/services/aggregation.go:132.21,136.4 1 0
github.com/muety/wakapi/services/aggregation.go:136.9,136.62 1 0
github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
github.com/muety/wakapi/services/heartbeat.go:25.80,27.2 1 0
github.com/muety/wakapi/services/heartbeat.go:29.111,31.16 2 0
github.com/muety/wakapi/services/heartbeat.go:34.2,34.43 1 0
github.com/muety/wakapi/services/heartbeat.go:31.16,33.3 1 0
github.com/muety/wakapi/services/heartbeat.go:37.78,39.2 1 0
github.com/muety/wakapi/services/heartbeat.go:41.62,43.2 1 0
github.com/muety/wakapi/services/heartbeat.go:45.116,47.16 2 0
github.com/muety/wakapi/services/heartbeat.go:51.2,51.28 1 0
github.com/muety/wakapi/services/heartbeat.go:55.2,55.24 1 0
github.com/muety/wakapi/services/heartbeat.go:47.16,49.3 1 0
github.com/muety/wakapi/services/heartbeat.go:51.28,53.3 1 0

View File

@ -112,7 +112,7 @@ func main() {
summaryHandler := routes.NewSummaryHandler(summaryService) summaryHandler := routes.NewSummaryHandler(summaryService)
healthHandler := routes.NewHealthHandler(db) healthHandler := routes.NewHealthHandler(db)
heartbeatHandler := routes.NewHeartbeatHandler(heartbeatService, languageMappingService) heartbeatHandler := routes.NewHeartbeatHandler(heartbeatService, languageMappingService)
settingsHandler := routes.NewSettingsHandler(userService, summaryService, aggregationService, languageMappingService) settingsHandler := routes.NewSettingsHandler(userService, summaryService, aliasService, aggregationService, languageMappingService)
homeHandler := routes.NewHomeHandler(keyValueService) homeHandler := routes.NewHomeHandler(keyValueService)
loginHandler := routes.NewLoginHandler(userService) loginHandler := routes.NewLoginHandler(userService)
imprintHandler := routes.NewImprintHandler(keyValueService) imprintHandler := routes.NewImprintHandler(keyValueService)
@ -160,6 +160,8 @@ func main() {
// Settings Routes // Settings Routes
settingsRouter.Methods(http.MethodGet).HandlerFunc(settingsHandler.GetIndex) settingsRouter.Methods(http.MethodGet).HandlerFunc(settingsHandler.GetIndex)
settingsRouter.Path("/credentials").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostCredentials) settingsRouter.Path("/credentials").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostCredentials)
settingsRouter.Path("/aliases").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostAlias)
settingsRouter.Path("/aliases/delete").Methods(http.MethodPost).HandlerFunc(settingsHandler.DeleteAlias)
settingsRouter.Path("/language_mappings").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostLanguageMapping) settingsRouter.Path("/language_mappings").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostLanguageMapping)
settingsRouter.Path("/language_mappings/delete").Methods(http.MethodPost).HandlerFunc(settingsHandler.DeleteLanguageMapping) settingsRouter.Path("/language_mappings/delete").Methods(http.MethodPost).HandlerFunc(settingsHandler.DeleteLanguageMapping)
settingsRouter.Path("/reset").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostResetApiKey) settingsRouter.Path("/reset").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostResetApiKey)

View File

@ -13,3 +13,33 @@ func (m *AliasRepositoryMock) GetByUser(s string) ([]*models.Alias, error) {
args := m.Called(s) args := m.Called(s)
return args.Get(0).([]*models.Alias), args.Error(1) return args.Get(0).([]*models.Alias), args.Error(1)
} }
func (m *AliasRepositoryMock) GetByUserAndKey(s string, s2 string) ([]*models.Alias, error) {
args := m.Called(s, s2)
return args.Get(0).([]*models.Alias), args.Error(1)
}
func (m *AliasRepositoryMock) GetByUserAndKeyAndType(s string, s2 string, u uint8) ([]*models.Alias, error) {
args := m.Called(s, s2, u)
return args.Get(0).([]*models.Alias), args.Error(1)
}
func (m *AliasRepositoryMock) GetByUserAndTypeAndValue(s string, u uint8, s2 string) (*models.Alias, error) {
args := m.Called(s, u, s2)
return args.Get(0).(*models.Alias), args.Error(1)
}
func (m *AliasRepositoryMock) Insert(s *models.Alias) (*models.Alias, error) {
args := m.Called(s)
return args.Get(0).(*models.Alias), args.Error(1)
}
func (m *AliasRepositoryMock) Delete(u uint) error {
args := m.Called(u)
return args.Error(0)
}
func (m *AliasRepositoryMock) DeleteBatch(u []uint) error {
args := m.Called(u)
return args.Error(0)
}

View File

@ -1,6 +1,7 @@
package mocks package mocks
import ( import (
"github.com/muety/wakapi/models"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@ -8,7 +9,12 @@ type AliasServiceMock struct {
mock.Mock mock.Mock
} }
func (m *AliasServiceMock) LoadUserAliases(s string) error { func (m *AliasServiceMock) IsInitialized(s string) bool {
args := m.Called(s)
return args.Bool(0)
}
func (m *AliasServiceMock) InitializeUser(s string) error {
args := m.Called(s) args := m.Called(s)
return args.Error(0) return args.Error(0)
} }
@ -18,7 +24,27 @@ func (m *AliasServiceMock) GetAliasOrDefault(s string, u uint8, s2 string) (stri
return args.String(0), args.Error(1) return args.String(0), args.Error(1)
} }
func (m *AliasServiceMock) IsInitialized(s string) bool { func (m *AliasServiceMock) GetByUser(s string) ([]*models.Alias, error) {
args := m.Called(s) args := m.Called(s)
return args.Bool(0) return args.Get(0).([]*models.Alias), args.Error(1)
}
func (m *AliasServiceMock) GetByUserAndKeyAndType(s string, s2 string, u uint8) ([]*models.Alias, error) {
args := m.Called(s, s2, u)
return args.Get(0).([]*models.Alias), args.Error(1)
}
func (m *AliasServiceMock) Create(a *models.Alias) (*models.Alias, error) {
args := m.Called(a)
return args.Get(0).(*models.Alias), args.Error(1)
}
func (m *AliasServiceMock) Delete(s *models.Alias) error {
args := m.Called(s)
return args.Error(0)
}
func (m *AliasServiceMock) DeleteMulti(a []*models.Alias) error {
args := m.Called(a)
return args.Error(0)
} }

View File

@ -8,3 +8,16 @@ type Alias struct {
Key string `gorm:"not null; index:idx_alias_type_key"` Key string `gorm:"not null; index:idx_alias_type_key"`
Value string `gorm:"not null"` Value string `gorm:"not null"`
} }
func (a *Alias) IsValid() bool {
return a.Key != "" && a.Value != "" && a.validateType()
}
func (a *Alias) validateType() bool {
for _, t := range SummaryTypes() {
if a.Type == t {
return true
}
}
return false
}

View File

@ -5,10 +5,17 @@ import "github.com/muety/wakapi/models"
type SettingsViewModel struct { type SettingsViewModel struct {
User *models.User User *models.User
LanguageMappings []*models.LanguageMapping LanguageMappings []*models.LanguageMapping
Aliases []*SettingsVMCombinedAlias
Success string Success string
Error string Error string
} }
type SettingsVMCombinedAlias struct {
Key string
Type uint8
Values []string
}
func (s *SettingsViewModel) WithSuccess(m string) *SettingsViewModel { func (s *SettingsViewModel) WithSuccess(m string) *SettingsViewModel {
s.Success = m s.Success = m
return s return s

View File

@ -1,6 +1,7 @@
package repositories package repositories
import ( import (
"errors"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -22,3 +23,67 @@ func (r *AliasRepository) GetByUser(userId string) ([]*models.Alias, error) {
} }
return aliases, nil return aliases, nil
} }
func (r *AliasRepository) GetByUserAndKey(userId, key string) ([]*models.Alias, error) {
var aliases []*models.Alias
if err := r.db.
Where(&models.Alias{
UserID: userId,
Key: key,
}).
Find(&aliases).Error; err != nil {
return nil, err
}
return aliases, nil
}
func (r *AliasRepository) GetByUserAndKeyAndType(userId, key string, summaryType uint8) ([]*models.Alias, error) {
var aliases []*models.Alias
if err := r.db.
Where(&models.Alias{
UserID: userId,
Key: key,
Type: summaryType,
}).
Find(&aliases).Error; err != nil {
return nil, err
}
return aliases, nil
}
func (r *AliasRepository) GetByUserAndTypeAndValue(userId string, summaryType uint8, value string) (*models.Alias, error) {
alias := &models.Alias{}
if err := r.db.
Where(&models.Alias{
UserID: userId,
Type: summaryType,
Value: value,
}).
First(alias).Error; err != nil {
return nil, err
}
return alias, nil
}
func (r *AliasRepository) Insert(alias *models.Alias) (*models.Alias, error) {
if !alias.IsValid() {
return nil, errors.New("invalid alias")
}
result := r.db.Create(alias)
if err := result.Error; err != nil {
return nil, err
}
return alias, nil
}
func (r *AliasRepository) Delete(id uint) error {
return r.db.
Where("id = ?", id).
Delete(models.Alias{}).Error
}
func (r *AliasRepository) DeleteBatch(ids []uint) error {
return r.db.
Where("id IN ?", ids).
Delete(models.Alias{}).Error
}

View File

@ -5,6 +5,7 @@ import (
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"log"
) )
type KeyValueRepository struct { type KeyValueRepository struct {
@ -40,7 +41,7 @@ func (r *KeyValueRepository) PutString(kv *models.KeyStringValue) error {
} }
if result.RowsAffected != 1 { if result.RowsAffected != 1 {
return errors.New("nothing updated") log.Printf("warning: did not insert key '%s', maybe just updated?\n", kv.Key)
} }
return nil return nil

View File

@ -6,7 +6,13 @@ import (
) )
type IAliasRepository interface { type IAliasRepository interface {
Insert(*models.Alias) (*models.Alias, error)
Delete(uint) error
DeleteBatch([]uint) error
GetByUser(string) ([]*models.Alias, error) GetByUser(string) ([]*models.Alias, error)
GetByUserAndKey(string, string) ([]*models.Alias, error)
GetByUserAndKeyAndType(string, string, uint8) ([]*models.Alias, error)
GetByUserAndTypeAndValue(string, uint8, string) (*models.Alias, error)
} }
type IHeartbeatRepository interface { type IHeartbeatRepository interface {

View File

@ -3,6 +3,7 @@ package routes
import ( import (
"fmt" "fmt"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/utils" "github.com/muety/wakapi/utils"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
@ -19,11 +20,15 @@ var templates map[string]*template.Template
func loadTemplates() { func loadTemplates() {
tplPath := "views" tplPath := "views"
tpls := template.New("").Funcs(template.FuncMap{ tpls := template.New("").Funcs(template.FuncMap{
"json": utils.Json, "json": utils.Json,
"date": utils.FormatDateHuman, "date": utils.FormatDateHuman,
"title": strings.Title, "title": strings.Title,
"capitalize": utils.Capitalize, "join": strings.Join,
"toRunes": utils.ToRunes, "add": utils.Add,
"capitalize": utils.Capitalize,
"toRunes": utils.ToRunes,
"entityTypes": models.SummaryTypes,
"typeName": typeName,
"getBasePath": func() string { "getBasePath": func() string {
return config.Get().Server.BasePath return config.Get().Server.BasePath
}, },
@ -58,3 +63,22 @@ func loadTemplates() {
templates[tplName] = tpl templates[tplName] = tpl
} }
} }
func typeName(t uint8) string {
if t == models.SummaryProject {
return "project"
}
if t == models.SummaryLanguage {
return "language"
}
if t == models.SummaryEditor {
return "editor"
}
if t == models.SummaryOS {
return "operating system"
}
if t == models.SummaryMachine {
return "machine"
}
return "unknown"
}

View File

@ -17,16 +17,18 @@ type SettingsHandler struct {
config *conf.Config config *conf.Config
userSrvc services.IUserService userSrvc services.IUserService
summarySrvc services.ISummaryService summarySrvc services.ISummaryService
aliasSrvc services.IAliasService
aggregationSrvc services.IAggregationService aggregationSrvc services.IAggregationService
languageMappingSrvc services.ILanguageMappingService languageMappingSrvc services.ILanguageMappingService
} }
var credentialsDecoder = schema.NewDecoder() var credentialsDecoder = schema.NewDecoder()
func NewSettingsHandler(userService services.IUserService, summaryService services.ISummaryService, aggregationService services.IAggregationService, languageMappingService services.ILanguageMappingService) *SettingsHandler { func NewSettingsHandler(userService services.IUserService, summaryService services.ISummaryService, aliasService services.IAliasService, aggregationService services.IAggregationService, languageMappingService services.ILanguageMappingService) *SettingsHandler {
return &SettingsHandler{ return &SettingsHandler{
config: conf.Get(), config: conf.Get(),
summarySrvc: summaryService, summarySrvc: summaryService,
aliasSrvc: aliasService,
aggregationSrvc: aggregationService, aggregationSrvc: aggregationService,
languageMappingSrvc: languageMappingService, languageMappingSrvc: languageMappingService,
userSrvc: userService, userSrvc: userService,
@ -161,6 +163,60 @@ func (h *SettingsHandler) PostLanguageMapping(w http.ResponseWriter, r *http.Req
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("mapping added successfully")) templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("mapping added successfully"))
} }
func (h *SettingsHandler) DeleteAlias(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
aliasKey := r.PostFormValue("key")
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
if err != nil {
aliasType = 99 // nothing will be found later on
}
if aliases, err := h.aliasSrvc.GetByUserAndKeyAndType(user.ID, aliasKey, uint8(aliasType)); err != nil {
w.WriteHeader(http.StatusNotFound)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("aliases not found"))
return
} else if err := h.aliasSrvc.DeleteMulti(aliases); err != nil {
w.WriteHeader(http.StatusInternalServerError)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("could not delete aliases"))
return
}
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("aliases deleted successfully"))
}
func (h *SettingsHandler) PostAlias(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
aliasKey := r.PostFormValue("key")
aliasValue := r.PostFormValue("value")
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
if err != nil {
aliasType = 99 // Alias.IsValid() will return false later on
}
alias := &models.Alias{
UserID: user.ID,
Key: aliasKey,
Value: aliasValue,
Type: uint8(aliasType),
}
if _, err := h.aliasSrvc.Create(alias); err != nil {
w.WriteHeader(http.StatusBadRequest)
// TODO: distinguish between bad request, conflict and server error
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("invalid input"))
return
}
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("alias added successfully"))
}
func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
@ -220,9 +276,34 @@ func (h *SettingsHandler) PostRegenerateSummaries(w http.ResponseWriter, r *http
func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel { func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel {
user := r.Context().Value(models.UserKey).(*models.User) user := r.Context().Value(models.UserKey).(*models.User)
mappings, _ := h.languageMappingSrvc.GetByUser(user.ID) mappings, _ := h.languageMappingSrvc.GetByUser(user.ID)
aliases, _ := h.aliasSrvc.GetByUser(user.ID)
aliasMap := make(map[string][]*models.Alias)
for _, a := range aliases {
k := fmt.Sprintf("%s_%d", a.Key, a.Type)
if _, ok := aliasMap[k]; !ok {
aliasMap[k] = []*models.Alias{a}
} else {
aliasMap[k] = append(aliasMap[k], a)
}
}
combinedAliases := make([]*view.SettingsVMCombinedAlias, 0)
for _, l := range aliasMap {
ca := &view.SettingsVMCombinedAlias{
Key: l[0].Key,
Type: l[0].Type,
Values: make([]string, len(l)),
}
for i, a := range l {
ca.Values[i] = a.Value
}
combinedAliases = append(combinedAliases, ca)
}
return &view.SettingsViewModel{ return &view.SettingsViewModel{
User: user, User: user,
LanguageMappings: mappings, LanguageMappings: mappings,
Aliases: combinedAliases,
Success: r.URL.Query().Get("success"), Success: r.URL.Query().Get("success"),
Error: r.URL.Query().Get("error"), Error: r.URL.Query().Get("error"),
} }

View File

@ -1,11 +1,12 @@
package services package services
import ( import (
"errors"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/repositories"
"sync"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
"github.com/muety/wakapi/repositories"
"log"
"sync"
) )
type AliasService struct { type AliasService struct {
@ -22,7 +23,14 @@ func NewAliasService(aliasRepo repositories.IAliasRepository) *AliasService {
var userAliases sync.Map var userAliases sync.Map
func (srv *AliasService) LoadUserAliases(userId string) error { func (srv *AliasService) IsInitialized(userId string) bool {
if _, ok := userAliases.Load(userId); ok {
return true
}
return false
}
func (srv *AliasService) InitializeUser(userId string) error {
aliases, err := srv.repository.GetByUser(userId) aliases, err := srv.repository.GetByUser(userId)
if err == nil { if err == nil {
userAliases.Store(userId, aliases) userAliases.Store(userId, aliases)
@ -30,9 +38,25 @@ func (srv *AliasService) LoadUserAliases(userId string) error {
return err return err
} }
func (srv *AliasService) GetByUser(userId string) ([]*models.Alias, error) {
aliases, err := srv.repository.GetByUser(userId)
if err != nil {
return nil, err
}
return aliases, nil
}
func (srv *AliasService) GetByUserAndKeyAndType(userId, key string, summaryType uint8) ([]*models.Alias, error) {
aliases, err := srv.repository.GetByUserAndKeyAndType(userId, key, summaryType)
if err != nil {
return nil, err
}
return aliases, nil
}
func (srv *AliasService) GetAliasOrDefault(userId string, summaryType uint8, value string) (string, error) { func (srv *AliasService) GetAliasOrDefault(userId string, summaryType uint8, value string) (string, error) {
if !srv.IsInitialized(userId) { if !srv.IsInitialized(userId) {
if err := srv.LoadUserAliases(userId); err != nil { if err := srv.InitializeUser(userId); err != nil {
return "", err return "", err
} }
} }
@ -46,9 +70,46 @@ func (srv *AliasService) GetAliasOrDefault(userId string, summaryType uint8, val
return value, nil return value, nil
} }
func (srv *AliasService) IsInitialized(userId string) bool { func (srv *AliasService) Create(alias *models.Alias) (*models.Alias, error) {
if _, ok := userAliases.Load(userId); ok { result, err := srv.repository.Insert(alias)
return true if err != nil {
return nil, err
}
go srv.reinitUser(alias.UserID)
return result, nil
}
func (srv *AliasService) Delete(alias *models.Alias) error {
if alias.UserID == "" {
return errors.New("no user id specified")
}
err := srv.repository.Delete(alias.ID)
go srv.reinitUser(alias.UserID)
return err
}
func (srv *AliasService) DeleteMulti(aliases []*models.Alias) error {
ids := make([]uint, len(aliases))
affectedUsers := make(map[string]bool)
for i, a := range aliases {
if a.UserID == "" {
return errors.New("no user id specified")
}
affectedUsers[a.UserID] = true
ids[i] = a.ID
}
err := srv.repository.DeleteBatch(ids)
for k := range affectedUsers {
go srv.reinitUser(k)
}
return err
}
func (srv *AliasService) reinitUser(userId string) {
if err := srv.InitializeUser(userId); err != nil {
log.Printf("error initializing user aliases %v\n", err)
} }
return false
} }

View File

@ -1,6 +1,7 @@
package services package services
import ( import (
"errors"
"github.com/muety/wakapi/config" "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
"github.com/muety/wakapi/repositories" "github.com/muety/wakapi/repositories"
@ -18,7 +19,7 @@ func NewLanguageMappingService(languageMappingsRepo repositories.ILanguageMappin
return &LanguageMappingService{ return &LanguageMappingService{
config: config.Get(), config: config.Get(),
repository: languageMappingsRepo, repository: languageMappingsRepo,
cache: cache.New(1*time.Hour, 2*time.Hour), cache: cache.New(24*time.Hour, 24*time.Hour),
} }
} }
@ -63,6 +64,9 @@ func (srv *LanguageMappingService) Create(mapping *models.LanguageMapping) (*mod
} }
func (srv *LanguageMappingService) Delete(mapping *models.LanguageMapping) error { func (srv *LanguageMappingService) Delete(mapping *models.LanguageMapping) error {
if mapping.UserID == "" {
return errors.New("no user id specified")
}
err := srv.repository.Delete(mapping.ID) err := srv.repository.Delete(mapping.ID)
srv.cache.Delete(mapping.UserID) srv.cache.Delete(mapping.UserID)
return err return err

View File

@ -15,9 +15,14 @@ type IMiscService interface {
} }
type IAliasService interface { type IAliasService interface {
LoadUserAliases(string) error Create(*models.Alias) (*models.Alias, error)
GetAliasOrDefault(string, uint8, string) (string, error) Delete(*models.Alias) error
DeleteMulti([]*models.Alias) error
IsInitialized(string) bool IsInitialized(string) bool
InitializeUser(string) error
GetByUser(string) ([]*models.Alias, error)
GetByUserAndKeyAndType(string, string, uint8) ([]*models.Alias, error)
GetAliasOrDefault(string, uint8, string) (string, error)
} }
type IHeartbeatService interface { type IHeartbeatService interface {

View File

@ -50,7 +50,7 @@ func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f Summ
} }
// Initialize alias resolver service // Initialize alias resolver service
if err := srv.aliasService.LoadUserAliases(user.ID); err != nil { if err := srv.aliasService.InitializeUser(user.ID); err != nil {
return nil, err return nil, err
} }

View File

@ -249,7 +249,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
from, to = suite.TestStartTime, suite.TestStartTime.Add(1*time.Hour) from, to = suite.TestStartTime, suite.TestStartTime.Add(1*time.Hour)
suite.HeartbeatService.On("GetAllWithin", from, to, suite.TestUser).Return(filter(from, to, suite.TestHeartbeats), nil) suite.HeartbeatService.On("GetAllWithin", from, to, suite.TestUser).Return(filter(from, to, suite.TestHeartbeats), nil)
suite.AliasService.On("LoadUserAliases", TestUserId).Return(nil) suite.AliasService.On("InitializeUser", TestUserId).Return(nil)
suite.AliasService.On("GetAliasOrDefault", TestUserId, models.SummaryProject, TestProject1).Return(TestProject2, nil) suite.AliasService.On("GetAliasOrDefault", TestUserId, models.SummaryProject, TestProject1).Return(TestProject2, nil)
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil) suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil)

View File

@ -18,6 +18,10 @@ func FormatDateHuman(date time.Time) string {
return date.Format("Mon, 02 Jan 2006 15:04") return date.Format("Mon, 02 Jan 2006 15:04")
} }
func Add(i, j int) int {
return i + j
}
func ParseUserAgent(ua string) (string, string, error) { func ParseUserAgent(ua string) (string, string, error) {
re := regexp.MustCompile(`(?iU)^wakatime\/[\d+.]+\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`) re := regexp.MustCompile(`(?iU)^wakatime\/[\d+.]+\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`)
groups := re.FindAllStringSubmatch(ua, -1) groups := re.FindAllStringSubmatch(ua, -1)

View File

@ -1 +1 @@
1.18.3 1.19.0

View File

@ -5,6 +5,18 @@
<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-800 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
<style>
.inline-bullet-list li a {
text-decoration: underline;
}
.inline-bullet-list li:after {
content: "•";
}
.inline-bullet-list li:last-child:after {
content: "";
}
</style>
{{ template "header.tpl.html" . }} {{ template "header.tpl.html" . }}
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
@ -19,8 +31,31 @@
<main class="mt-4 flex-grow flex justify-center w-full"> <main class="mt-4 flex-grow flex justify-center w-full">
<div class="flex flex-col flex-grow max-w-xl mt-8"> <div class="flex flex-col flex-grow max-w-xl mt-8">
<div class="text-gray-500 text-xs mb-8">
<ul class="flex justify-center flex-wrap space-x-1 inline-bullet-list">
<li class="hover:text-gray-400 mb-1">
<a href="settings#password">Change Password</a>
</li>
<li class="hover:text-gray-400 mb-1">
<a href="settings#apikey">Reset API Key</a>
</li>
<li class="hover:text-gray-400 mb-1">
<a href="settings#aliases">Aliases</a>
</li>
<li class="hover:text-gray-400 mb-1">
<a href="settings#languages">Languages & File Extensions</a>
</li>
<li class="hover:text-gray-400 mb-1">
<a href="settings#badges">Badges</a>
</li>
<li class="hover:text-gray-400 mb-1">
<a href="settings#danger">Danger Zone</a>
</li>
</ul>
</div>
<div class="w-full my-8 pb-8 border-b border-gray-700"> <div class="w-full my-8 pb-8 border-b border-gray-700">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"> <div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="password">
Change Password Change Password
</div> </div>
@ -52,13 +87,15 @@
</div> </div>
<div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700"> <div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"> <div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="apikey">
Reset API Key Reset API Key
</div> </div>
<form class="mt-6" action="settings/reset" method="post"> <form class="mt-6" action="settings/reset" method="post">
<div class="text-gray-300 text-sm mb-4"> <div class="text-gray-300 text-sm mb-4">
<strong>⚠️ Caution:</strong> Resetting your API key requires you to update your <span class="font-mono">.wakatime.cfg</span> files on all of your computers to make the WakaTime client send heartbeats again. <strong>⚠️ Caution:</strong> Resetting your API key requires you to update your <span
class="font-mono">.wakatime.cfg</span> files on all of your computers to make the WakaTime
client send heartbeats again.
</div> </div>
<div class="flex justify-between float-right"> <div class="flex justify-between float-right">
@ -69,112 +106,197 @@
</form> </form>
</div> </div>
<div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700"> <div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700" id="aliases">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"> <div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block">
Language Mappings Aliases
</div> </div>
<div class="text-gray-300 text-sm mb-4 mt-6"> <div class="text-gray-300 text-sm mb-4 mt-6">
You can specify custom mapping from file extensions to programming languages (e.g. a <span class="text-xs bg-gray-900 rounded py-1 px-2 font-mono">.jsx</span> file could be mapped to <span class="text-xs bg-gray-900 rounded py-1 px-2 font-mono">React</span>.) You can specify aliases for any type of entity. For instance, you can define a rule, that both <span
class="inline-block mb-1 text-gray-500 italic">myapp-frontend</span> and <span
class="inline-block mb-1 text-gray-500 italic">myapp-backend</span> are combined under a
project called <span class="inline-block mb-1 text-gray-500 italic">myapp</span>.
</div> </div>
{{ if .LanguageMappings }} {{ if .Aliases }}
{{ range $i, $mapping := .LanguageMappings }} <h3 class="text-md font-semibold text-white">Rules</h3>
<div class="text-white border-1 w-full border-green-700 inline-block my-1 py-1 text-align"> {{ range $i, $alias := .Aliases }}
<label class="inline-block text-sm mb-1 text-gray-500" >When filename ends in:</label> <div class="flex items-center">
{{ $mapping.Extension }} <div class="text-gray-500 border-1 w-full border-green-700 inline-block my-1 py-1 text-align text-sm"
<label class="inline-block text-sm mb-1 text-gray-500" >Change the language to:</label> style="line-height: 1.8">
{{ $mapping.Language }} &#9656;&nbsp; All <span class="underline">{{ $alias.Type | typeName }}s</span> named
{{ range $j, $value := $alias.Values }}
<form class="float-right" action="settings/language_mappings/delete" method="post"> <span class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono">{{- $value -}}</span>
<input type="hidden" id="mapping_id" name="mapping_id" required value="{{ $mapping.ID }}"> {{ if lt $j (add (len $alias.Values) -2) }}
<button type="submit" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm"> <span class="-ml-1">{{- ", " | capitalize -}}</span>
X {{ else if lt $j (add (len $alias.Values) -1) }}
<span>{{- "or" -}}</span>
{{ end }}
{{ end }}
are mapped to <span class="underline">{{ $alias.Type | typeName }}</span> <span
class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono">{{ $alias.Key }}</span>.
</div>
<form class="float-right" action="settings/aliases/delete" method="post">
<input type="hidden" id="delete_alias_key" name="key" required value="{{ $alias.Key }}">
<input type="hidden" id="delete_alias_type" name="type" required value="{{ $alias.Type }}">
<button type="submit" class="py-1 px-3 rounded border border-red-500 hover:border-red-600 text-gray-400 text-sm">
</button> </button>
</form> </form>
</div> </div>
{{end}} {{end}}
{{else}} <div class="mb-8"></div>
<div class="text-white border-1 w-full border-green-700 inline-block my-1 py-1">
No rules.
</div>
{{end}} {{end}}
<form action="settings/language_mappings" method="post"> <h3 class="text-md font-semibold text-white">Add Rule</h3>
<div class="inline-block justify-around mt-4 w-full"> <form action="settings/aliases" method="post">
<label class="inline-block text-sm mb-1 text-gray-500" for="extension">When filename ends in:</label> <div class="flex items-center mt-2 w-full text-gray-500 text-sm">
<input class="shadow appearance-nonshadow 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" <span class="mr-2">Map</span>
type="text" id="extension" <select name="type" id="select-type"
name="extension" placeholder=".py" minlength="1" required> class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3 cursor-pointer">
</div> {{ range $i, $t := entityTypes }}
<div class="inline-block justify-around mt-4 w-full"> <option value="{{ $t }}">{{ $t | typeName | capitalize }}</option>
<label class="inline-block text-sm mb-1 text-gray-500" for="language">Change the language to:</label> {{ end }}
<input class="shadow appearance-nonshadow 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" </select>
type="text" id="language" <span class="mx-2">named</span>
name="language" placeholder="Python" minlength="1" required> <input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3"
</div> type="text" id="alias-value" style="width: 130px;"
<div class="flex justify-between float-right"> name="value" placeholder="myapp-frontend" minlength="1" required>
<button type="submit" class="py-1 px-3 my-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm"> <span class="mx-2">to</span>
Add <input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3"
</button> type="text" id="alias-key" style="width: 100px"
name="key" placeholder="myapp" minlength="1" required>
<div class="flex-grow flex justify-end">
<button type="submit"
class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">
Add
</button>
</div>
</div> </div>
</form> </form>
</div> </div>
<div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700"> <div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"> <div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="languages">
Languages & File Extensions
</div>
<div class="text-gray-300 text-sm mb-4 mt-6">
You can specify custom mapping from file extensions to programming languages, for instance a <span
class="inline-block mb-1 text-gray-500 italic">.jsx</span> file could be mapped to the <span
class="inline-block mb-1 text-gray-500 italic">React</span> language.
</div>
{{ if .LanguageMappings }}
<h3 class="text-md font-semibold text-white">Rules</h3>
{{ range $i, $mapping := .LanguageMappings }}
<div class="flex items-center">
<div class="text-gray-500 border-1 w-full border-green-700 inline-block my-1 py-1 text-align text-sm">
&#9656;&nbsp; When filename ends in <span
class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono mr-1">{{ $mapping.Extension }}</span>
then change the <span class="underline">language</span> to <span
class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono mr-1">{{ $mapping.Language }}</span>
</div>
<form class="float-right" action="settings/language_mappings/delete" method="post">
<input type="hidden" id="mapping_id" name="mapping_id" required value="{{ $mapping.ID }}">
<button type="submit" class="py-1 px-3 rounded border border-red-500 hover:border-red-600 text-gray-400 text-sm">
</button>
</form>
</div>
{{end}}
<div class="mb-8"></div>
{{end}}
<h3 class="text-md font-semibold text-white">Add Rule</h3>
<form action="settings/language_mappings" method="post">
<div class="flex items-center w-full text-gray-500 text-sm">
<span class="mr-2">When filename ends in</span>
<input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3"
type="text" id="extension" style="width: 70px"
name="extension" placeholder=".py" minlength="1" required>
<span class="mx-2">change language to</span>
<input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3"
type="text" id="language" style="width: 100px"
name="language" placeholder="Python" minlength="1" required>
<div class="flex-grow flex justify-end">
<button type="submit"
class="py-1 px-3 my-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">
Add
</button>
</div>
</div>
</form>
</div>
<div class="w-full mt-4 mb-8 pb-8 border-b border-gray-700">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="badges">
Badges Badges
</div> </div>
<form class="mt-6" action="settings/badges" method="post"> <form class="mt-6" action="settings/badges" method="post">
<div class="text-gray-300 text-sm mb-4"> <div class="text-gray-300 text-sm mb-4">
{{ if .User.BadgesEnabled }} {{ if .User.BadgesEnabled }}
<p>Badges are currently enabled. You can disable the feature by deactivating the respective API endpoint.</p> <p>Badges are currently enabled. You can disable the feature by deactivating the respective API
endpoint.</p>
<div class="flex justify-around mt-4"> <div class="flex justify-around mt-4">
<span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">GET /api/compat/shields/v1</span> <span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">GET /api/compat/shields/v1</span>
<button type="submit" class="py-1 px-2 rounded bg-orange-700 hover:bg-orange-800 text-white text-xs" title="Disable support for badges to secure endpoint"> <button type="submit"
Status: public class="py-1 px-2 rounded bg-orange-700 hover:bg-orange-800 text-white text-xs"
</button> title="Disable support for badges to secure endpoint">
</div> Status: public
</button>
</div>
<h3 class="font-semibold mb-2 mt-8">Examples</h3> <h3 class="font-semibold mb-2 mt-8">Examples</h3>
<div class="flex flex-col mb-4"> <div class="flex flex-col mb-4">
<div class="flex justify-between my-2"> <div class="flex justify-between my-2">
<div> <div>
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today" alt="Shields.io badge"/> <img class="with-url-src"
</div> src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today"
<span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto" style="max-width: 300px;"> alt="Shields.io badge"/>
</div>
<span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto"
style="max-width: 300px;">
https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today
</span> </span>
</div>
<div class="flex justify-between my-2">
<div>
<img class="with-url-src"
src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d"
alt="Shields.io badge"/>
</div> </div>
<div class="flex justify-between my-2"> <span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto"
<div> style="max-width: 300px;">
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d" alt="Shields.io badge"/>
</div>
<span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto" style="max-width: 300px;">
https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d
</span> </span>
</div>
</div> </div>
</div>
<p>You can also add <span class="text-xs bg-gray-900 rounded py-1 px-2 font-mono">/project:your-cool-project</span> to the URL to filter by project.</p> <p>You can also add <span class="text-xs bg-gray-900 rounded py-1 px-2 font-mono">/project:your-cool-project</span>
to the URL to filter by project.</p>
{{ else }} {{ else }}
<p>You have the ability to create badges from your coding statistics using <a href="https://shields.io" target="_blank" class="border-b border-green-800" rel="noopener noreferrer">Shields.io</a>. To do so, you need to grant public, unauthorized access to the respective endpoint.</p> <p>You have the ability to create badges from your coding statistics using <a
<div class="flex justify-around mt-4"> href="https://shields.io" target="_blank" class="border-b border-green-800"
<span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">GET /api/compat/shields/v1</span> rel="noopener noreferrer">Shields.io</a>. To do so, you need to grant public, unauthorized
<button type="submit" class="py-1 px-2 rounded bg-green-700 hover:bg-green-800 text-white text-xs" title="Make endpoint public to enable badges"> access to the respective endpoint.</p>
Status: protected <div class="flex justify-around mt-4">
</button> <span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">GET /api/compat/shields/v1</span>
</div> <button type="submit"
class="py-1 px-2 rounded bg-green-700 hover:bg-green-800 text-white text-xs"
title="Make endpoint public to enable badges">
Status: protected
</button>
</div>
{{ end }} {{ end }}
</div> </div>
</form> </form>
</div> </div>
<div class="w-full mt-4 mb-8 pb-8"> <div class="w-full mt-4 mb-8 pb-8">
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"> <div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="danger">
⚠️ Danger Zone ⚠️ Danger Zone
</div> </div>
<div class="mt-10 text-gray-300 text-sm"> <div class="mt-10 text-gray-300 text-sm">
@ -182,19 +304,26 @@
Regenerate summaries Regenerate summaries
</h3> </h3>
<p> <p>
Wakapi improves its efficiency and speed by automatically aggregating individual heartbeats to summaries on a per-day basis. Wakapi improves its efficiency and speed by automatically aggregating individual heartbeats to
That is, historic summaries, i.e. such from past days, are generated once and only fetched from the database in a static fashion afterwards, unless you pass <span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">&recompute=true</span> with your request. summaries on a per-day basis.
That is, historic summaries, i.e. such from past days, are generated once and only fetched from the
database in a static fashion afterwards, unless you pass <span
class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">&recompute=true</span>
with your request.
</p> </p>
<p class="mt-2"> <p class="mt-2">
If, for some reason, these aggregated summaries are faulty or preconditions have change (e.g. you modified language mappings retrospectively), you may want to re-generate them from raw heartbeats. If, for some reason, these aggregated summaries are faulty or preconditions have change (e.g. you
modified language mappings retrospectively), you may want to re-generate them from raw heartbeats.
</p> </p>
<p class="mt-2"> <p class="mt-2">
<strong>Note:</strong> Only run this action if you know what you are doing. Data might be lost is case heartbeats were deleted after the respective summaries had been generated. <strong>Note:</strong> Only run this action if you know what you are doing. Data might be lost is
case heartbeats were deleted after the respective summaries had been generated.
</p> </p>
</div> </div>
<div class="mt-10 flex justify-center"> <div class="mt-10 flex justify-center">
<form action="settings/regenerate" method="post" id="form-regenerate-summaries"> <form action="settings/regenerate" method="post" id="form-regenerate-summaries">
<button type="button" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm" id="btn-regenerate-summaries"> <button type="button" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm"
id="btn-regenerate-summaries">
Clear & Regenerate Clear & Regenerate
</button> </button>
</form> </form>
@ -214,7 +343,7 @@
e.classList.remove('hidden') e.classList.remove('hidden')
}) })
const btnRegenerate = document.querySelector("#btn-regenerate-summaries") const btnRegenerate = document.querySelector('#btn-regenerate-summaries')
const formRegenerate = document.querySelector('#form-regenerate-summaries') const formRegenerate = document.querySelector('#form-regenerate-summaries')
btnRegenerate.addEventListener('click', () => { btnRegenerate.addEventListener('click', () => {
if (confirm('Are you sure?')) { if (confirm('Are you sure?')) {