mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
18 Commits
2.2.3-test
...
2.3.7
Author | SHA1 | Date | |
---|---|---|---|
d5a85639b1 | |||
b6a8185957 | |||
c5da5e4622 | |||
a0f69a371f | |||
2f0cb112dd | |||
2173954b84 | |||
991e64b961 | |||
affff0c386 | |||
099cdaddbc | |||
409405117e | |||
af89ecc9c1 | |||
be354fa790 | |||
a1c4c5da6b | |||
33509beaf7 | |||
ab6ccbdfbe | |||
77e6cd9faa | |||
34bc38cecf | |||
69d3e0494b |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: 'Unit- & API tests'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@ -31,7 +32,7 @@ jobs:
|
||||
./testing/run_api_tests.sh
|
||||
|
||||
mapi:
|
||||
name: 'Mayhem for API'
|
||||
name: 'Automated pen-tests with Mayhem for API'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
@ -74,6 +75,8 @@ jobs:
|
||||
sarif_file: mapi.sarif
|
||||
|
||||
build:
|
||||
name: 'Build (Win, Linux, Mac)'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
@ -94,4 +97,4 @@ jobs:
|
||||
run: go get
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
run: go build -v .
|
||||
|
1
.github/workflows/docker.yml
vendored
1
.github/workflows/docker.yml
vendored
@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
docker-publish:
|
||||
name: 'Build and publish Docker image'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -7,7 +7,8 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
name: 'Build, package and release to GitHub'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
21
README.md
21
README.md
@ -100,19 +100,22 @@ If you want to run Wakapi on **Kubernetes**, there is [wakapi-helm-chart](https:
|
||||
#### Compile & run
|
||||
|
||||
```bash
|
||||
# Build the executable
|
||||
$ go build -o wakapi
|
||||
# Build and install
|
||||
# Alternatively: go build -o wakapi
|
||||
$ go install github.com/muety/wakapi@latest
|
||||
|
||||
# Adapt config to your needs
|
||||
$ cp config.default.yml config.yml
|
||||
$ vi config.yml
|
||||
# Get default config and customize
|
||||
$ curl -o wakapi.yml https://raw.githubusercontent.com/muety/wakapi/master/config.default.yml
|
||||
$ vi wakapi.yml
|
||||
|
||||
# Run it
|
||||
$ ./wakapi
|
||||
$ ./wakapi -config wakapi.yml
|
||||
```
|
||||
|
||||
**Note:** Check the comments in `config.yml` for best practices regarding security configuration and more.
|
||||
|
||||
💡 When running Wakapi standalone (without Docker), it is recommended to run it as a [SystemD service](etc/wakapi.service).
|
||||
|
||||
### 💻 Client setup
|
||||
|
||||
Wakapi relies on the open-source [WakaTime](https://github.com/wakatime/wakatime) client tools. In order to collect statistics for Wakapi, you need to set them up.
|
||||
@ -123,8 +126,8 @@ Wakapi relies on the open-source [WakaTime](https://github.com/wakatime/wakatime
|
||||
```ini
|
||||
[settings]
|
||||
|
||||
# Your Wakapi server URL or 'https://wakapi.dev' when using the cloud server
|
||||
api_url = http://localhost:3000/api/heartbeat
|
||||
# Your Wakapi server URL or 'https://wakapi.dev/api' when using the cloud server
|
||||
api_url = http://localhost:3000/api
|
||||
|
||||
# Your Wakapi API key (get it from the web interface after having created an account)
|
||||
api_key = 406fe41f-6d69-4183-a4cc-121e0c524c2b
|
||||
@ -145,7 +148,7 @@ You can specify configuration options either via a config file (default: `config
|
||||
| `app.inactive_days` /<br>`WAKAPI_INACTIVE_DAYS` | `7` | Number of days after which to consider a user inactive (only for metrics) |
|
||||
| `app.heartbeat_max_age /`<br>`WAKAPI_HEARTBEAT_MAX_AGE` | `4320h` | Maximum acceptable age of a heartbeat (see [`ParseDuration`](https://pkg.go.dev/time#ParseDuration)) |
|
||||
| `app.custom_languages` | - | Map from file endings to language names |
|
||||
| `app.avatar_url_template` | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) |
|
||||
| `app.avatar_url_template` /<br>`WAKAPI_AVATAR_URL_TEMPLATE` | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) |
|
||||
| `server.port` /<br> `WAKAPI_PORT` | `3000` | Port to listen on |
|
||||
| `server.listen_ipv4` /<br> `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) |
|
||||
| `server.listen_ipv6` /<br> `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) |
|
||||
|
@ -70,7 +70,7 @@ type appConfig struct {
|
||||
InactiveDays int `yaml:"inactive_days" default:"7" env:"WAKAPI_INACTIVE_DAYS"`
|
||||
HeartbeatMaxAge string `yaml:"heartbeat_max_age" default:"4320h" env:"WAKAPI_HEARTBEAT_MAX_AGE"`
|
||||
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
|
||||
AvatarURLTemplate string `yaml:"avatar_url_template" default:"api/avatar/{username_hash}.svg"`
|
||||
AvatarURLTemplate string `yaml:"avatar_url_template" default:"api/avatar/{username_hash}.svg" env:"WAKAPI_AVATAR_URL_TEMPLATE"`
|
||||
CustomLanguages map[string]string `yaml:"custom_languages"`
|
||||
Colors map[string]map[string]string `yaml:"-"`
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
53
etc/wakapi.service
Normal file
53
etc/wakapi.service
Normal file
@ -0,0 +1,53 @@
|
||||
[Unit]
|
||||
Description=Wakapi
|
||||
StartLimitIntervalSec=400
|
||||
StartLimitBurst=3
|
||||
|
||||
# Optional, in case you're running MySQL / Postgres with Systemd, too
|
||||
Requires=mysql.service
|
||||
After=mysql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
# Assuming Wakapi executable is under /opt/wakapi and config file at /etc
|
||||
# Feel free to change this
|
||||
WorkingDirectory=/opt/wakapi
|
||||
ExecStart=/opt/wakapi/wakapi -config /etc/wakapi.yml
|
||||
|
||||
# Environment variables, see README for more
|
||||
Environment=WAKAPI_DB_HOST=localhost
|
||||
Environment=WAKAPI_DB_USER=wakapi
|
||||
Environment=WAKAPI_DB_NAME=wakapi
|
||||
Environment=WAKAPI_DB_PASSWORD=secretpassword
|
||||
Environment=WAKAPI_PASSWORD_SALT=somerandomstring
|
||||
|
||||
# TODO: Use Systemd's credentials management (https://systemd.io/CREDENTIALS/) introduced in v247 (%d syntax in v250) once more established
|
||||
|
||||
# sudo groupadd wakapi
|
||||
# sudo useradd -g wakapi wakapi
|
||||
User=wakapi
|
||||
Group=wakapi
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=90
|
||||
|
||||
# Security hardening
|
||||
PrivateTmp=true
|
||||
PrivateUsers=true
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
PrivateDevices=true
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
ProtectClock=true
|
||||
RestrictSUIDSGID=true
|
||||
ProtectHostname=true
|
||||
ProtectProc=invisible
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -109,7 +109,7 @@ func (r *SummaryRepository) populateItems(summaries []*models.Summary, condition
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if _, ok := summaryMap[item.SummaryID]; ok {
|
||||
if _, ok := summaryMap[item.SummaryID]; !ok {
|
||||
continue
|
||||
}
|
||||
l := summaryMap[item.SummaryID][0].ItemsByType(item.Type)
|
||||
|
128
routes/compat/wakatime/v1/users_test.go
Normal file
128
routes/compat/wakatime/v1/users_test.go
Normal file
@ -0,0 +1,128 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/mocks"
|
||||
"github.com/muety/wakapi/models"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
adminUser = &models.User{
|
||||
ID: "AdminUser",
|
||||
ApiKey: "admin-user-api-key",
|
||||
Email: "admin@user.com",
|
||||
IsAdmin: true,
|
||||
CreatedAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 1, time.UTC)),
|
||||
LastLoggedInAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 2, time.UTC)),
|
||||
}
|
||||
|
||||
basicUser = &models.User{
|
||||
ID: "BasicUser",
|
||||
ApiKey: "basic-user-api-key",
|
||||
Email: "basic@user.com",
|
||||
IsAdmin: false,
|
||||
CreatedAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 3, time.UTC)),
|
||||
LastLoggedInAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 4, time.UTC)),
|
||||
}
|
||||
)
|
||||
|
||||
func TestUsersHandler_Get(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
apiRouter := router.PathPrefix("/api").Subrouter().StrictSlash(true)
|
||||
apiRouter.Use(middlewares.NewPrincipalMiddleware())
|
||||
|
||||
userServiceMock := new(mocks.UserServiceMock)
|
||||
userServiceMock.On("GetUserById", "AdminUser").Return(adminUser, nil)
|
||||
userServiceMock.On("GetUserByKey", "admin-user-api-key").Return(adminUser, nil)
|
||||
userServiceMock.On("GetUserById", "BasicUser").Return(basicUser, nil)
|
||||
userServiceMock.On("GetUserByKey", "basic-user-api-key").Return(basicUser, nil)
|
||||
|
||||
heartbeatServiceMock := new(mocks.HeartbeatServiceMock)
|
||||
heartbeatServiceMock.On("GetLatestByUser", adminUser).Return(&models.Heartbeat{
|
||||
CreatedAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 5, time.UTC)),
|
||||
Time: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 6, time.UTC)),
|
||||
}, nil)
|
||||
heartbeatServiceMock.On("GetLatestByUser", basicUser).Return(&models.Heartbeat{
|
||||
CreatedAt: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 5, time.UTC)),
|
||||
Time: models.CustomTime(time.Date(2022, 2, 2, 22, 22, 22, 6, time.UTC)),
|
||||
}, nil)
|
||||
|
||||
usersHandler := NewUsersHandler(userServiceMock, heartbeatServiceMock)
|
||||
usersHandler.RegisterRoutes(apiRouter)
|
||||
|
||||
t.Run("when requesting own user data", func(t *testing.T) {
|
||||
t.Run("should return own data", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/compat/wakatime/v1/users/AdminUser", nil)
|
||||
req.Header.Add(
|
||||
"Authorization",
|
||||
fmt.Sprintf("Bearer %s", base64.StdEncoding.EncodeToString([]byte(adminUser.ApiKey))),
|
||||
)
|
||||
requestRecorder := httptest.NewRecorder()
|
||||
apiRouter.ServeHTTP(requestRecorder, req)
|
||||
res := requestRecorder.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unextected error. Error: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(data), "\"username\":\"AdminUser\"") {
|
||||
t.Errorf("invalid response received. Expected json Received: %s", string(data))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when requesting another users data", func(t *testing.T) {
|
||||
t.Run("should respond with '401 unauthorized' if not an admin user", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/compat/wakatime/v1/users/AdminUser", nil)
|
||||
req.Header.Add(
|
||||
"Authorization",
|
||||
fmt.Sprintf("Bearer %s", base64.StdEncoding.EncodeToString([]byte(basicUser.ApiKey))),
|
||||
)
|
||||
requestRecorder := httptest.NewRecorder()
|
||||
apiRouter.ServeHTTP(requestRecorder, req)
|
||||
res := requestRecorder.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unextected error. Error: %s", err)
|
||||
}
|
||||
|
||||
if string(data) != "401 unauthorized" {
|
||||
t.Errorf("invalid response received. Expected: '401 unauthorized' Received: %s", string(data))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should receive user data if requesting user is an admin", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/compat/wakatime/v1/users/BasicUser", nil)
|
||||
req.Header.Add(
|
||||
"Authorization",
|
||||
fmt.Sprintf("Bearer %s", base64.StdEncoding.EncodeToString([]byte(adminUser.ApiKey))),
|
||||
)
|
||||
requestRecorder := httptest.NewRecorder()
|
||||
apiRouter.ServeHTTP(requestRecorder, req)
|
||||
res := requestRecorder.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unextected error. Error: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(data), "\"username\":\"BasicUser\"") {
|
||||
t.Errorf("invalid response received. Expected 'BasicUser' info Received: %s", string(data))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
@ -35,12 +35,12 @@ func CheckEffectiveUser(w http.ResponseWriter, r *http.Request, userService serv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if authorizedUser == nil || authorizedUser.ID != requestedUser.ID {
|
||||
if authorizedUser == nil || authorizedUser.ID != requestedUser.ID && !authorizedUser.IsAdmin {
|
||||
err := errors.New(conf.ErrUnauthorized)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authorizedUser, nil
|
||||
return requestedUser, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
@ -60,7 +61,7 @@ func (srv *DurationService) Get(from, to time.Time, user *models.User, filters *
|
||||
continue
|
||||
}
|
||||
|
||||
sameDay := d1.Time.T().Day() == latest.Time.T().Day()
|
||||
sameDay := datetime.BeginOfDay(d1.Time.T()) == datetime.BeginOfDay(latest.Time.T())
|
||||
dur := time.Duration(mathutil.Min(
|
||||
int64(d1.Time.T().Sub(latest.Time.T().Add(latest.Duration))),
|
||||
int64(HeartbeatDiffThreshold),
|
||||
|
@ -12,6 +12,7 @@ server:
|
||||
app:
|
||||
aggregation_time: '02:15'
|
||||
report_time_weekly: 'fri,18:00'
|
||||
heartbeat_max_age: 87600h # 10 years
|
||||
inactive_days: 7
|
||||
custom_languages:
|
||||
vue: Vue
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "43639725-0458-40d7-a4d4-9f55a539a7f7",
|
||||
"_postman_id": "5c0749a5-6ddf-41ea-82f1-140578788bc3",
|
||||
"name": "Wakapi API Tests",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
@ -973,10 +973,9 @@
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// 1640995199 Friday, 31 December 2021 11:59:59 PM (Jan 1st in +1, +2)",
|
||||
"// 1641074399 Saturday, 1 January 2022 9:59:59 PM (Jan 1st in +1, +2)",
|
||||
"// 1641081599 Saturday, 1 January 2022 11:59:59 PM (Jan 2nd in +1, +2)",
|
||||
""
|
||||
"pm.test(\"Status code is 201\", function () {",
|
||||
" pm.response.to.have.status(201);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
@ -997,7 +996,7 @@
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "[{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1640995199\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641074399\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641081599\n}]",
|
||||
"raw": "[{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1640995200\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641074400\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641081600\n}]",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@ -1331,8 +1330,8 @@
|
||||
"",
|
||||
"pm.test(\"Correct dates\", function () {",
|
||||
" const jsonData = pm.response.json();",
|
||||
" pm.expect(moment(jsonData.from).unix()).to.gte(moment(pm.variables.get('tsStartOfDayDate')).unix())",
|
||||
" pm.expect(moment(jsonData.to).unix()).to.gte(moment(pm.variables.get('tsEndOfDayDate')).unix())",
|
||||
" pm.expect(moment(jsonData.from).unix()).to.gte(moment(pm.variables.get('tsStartOfDayIso')).unix())",
|
||||
" pm.expect(moment(jsonData.to).unix()).to.lte(moment(pm.variables.get('tsEndOfDayIso')).unix())",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
@ -1358,7 +1357,7 @@
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?from={{tsStartOfDayDate}}&to={{tsEndOfTomorrowDate}}",
|
||||
"raw": "{{BASE_URL}}/api/summary?from={{tsStartOfDayIso}}&to={{tsEndOfDayIso}}",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
@ -1369,11 +1368,11 @@
|
||||
"query": [
|
||||
{
|
||||
"key": "from",
|
||||
"value": "{{tsStartOfDayDate}}"
|
||||
"value": "{{tsStartOfDayIso}}"
|
||||
},
|
||||
{
|
||||
"key": "to",
|
||||
"value": "{{tsEndOfTomorrowDate}}"
|
||||
"value": "{{tsEndOfDayIso}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -3371,46 +3370,60 @@
|
||||
"exec": [
|
||||
"const moment = require('moment')",
|
||||
"",
|
||||
"const now = moment()",
|
||||
"const startOfDay = moment().startOf('day')",
|
||||
"const endOfDay = moment().endOf('day')",
|
||||
"const endOfTomorrow = moment().add(1, 'd').endOf('day')",
|
||||
"// pretend we're in Berlin, as this is also the time zone configured for the user",
|
||||
"const userZone = 'Europe/Berlin'",
|
||||
"",
|
||||
"console.log(`Current timestamp is: ${now.format('x') / 1000}`)",
|
||||
"",
|
||||
"",
|
||||
"// Auth stuff",
|
||||
"const readApiKey = pm.variables.get('READUSER_API_KEY')",
|
||||
"const writeApiKey = pm.variables.get('WRITEUSER_API_KEY')",
|
||||
"",
|
||||
"if (!readApiKey || !writeApiKey) {",
|
||||
" throw new Error('no api key given')",
|
||||
"// postman doesn't have moment-timezone package included",
|
||||
"// and we can't just use utcOffset(2), because of summer / winter time",
|
||||
"// inspired by https://stackoverflow.com/a/56853085/3112139",
|
||||
"function getUtcOffset(cb) {",
|
||||
" let offset = pm.globals.get('utcOffset')",
|
||||
" if (offset) return cb(offset)",
|
||||
" pm.sendRequest(`https://worldtimeapi.org/api/timezone/${userZone}`, (err, res) => {",
|
||||
" offset = res.json().utc_offset",
|
||||
" pm.globals.set('utcOffset', offset)",
|
||||
" return cb(offset)",
|
||||
" })",
|
||||
"}",
|
||||
"",
|
||||
"pm.variables.set('READUSER_TOKEN', base64encode(readApiKey))",
|
||||
"pm.variables.set('WRITEUSER_TOKEN', base64encode(writeApiKey))",
|
||||
"getUtcOffset((utcOffset) => {",
|
||||
" const now = moment().utcOffset(utcOffset)",
|
||||
" const startOfDay = now.clone().startOf('day')",
|
||||
" const endOfDay = now.clone().endOf('day')",
|
||||
" const endOfTomorrow = now.clone().add(1, 'd').endOf('day')",
|
||||
"",
|
||||
"function base64encode(str) {",
|
||||
" return Buffer.from(str, 'utf-8').toString('base64')",
|
||||
"}",
|
||||
" // Auth stuff",
|
||||
" const readApiKey = pm.variables.get('READUSER_API_KEY')",
|
||||
" const writeApiKey = pm.variables.get('WRITEUSER_API_KEY')",
|
||||
"",
|
||||
"// Heartbeat stuff",
|
||||
"pm.variables.set('tsNow', now.format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus1Min', now.add(-1, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus2Min', now.add(-2, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsNowMinus3Min', now.add(-3, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('tsStartOfDay', startOfDay.format('x') / 1000)",
|
||||
"pm.variables.set('tsEndOfDay', endOfDay.format('x') / 1000)",
|
||||
"pm.variables.set('tsEndOfTomorrow', endOfTomorrow.format('x') / 1000)",
|
||||
"pm.variables.set('tsStartOfDayIso', startOfDay.toISOString())",
|
||||
"pm.variables.set('tsEndOfDayIso', endOfDay.toISOString())",
|
||||
"pm.variables.set('tsEndOfTomorrowIso', endOfTomorrow.toISOString())",
|
||||
"pm.variables.set('tsStartOfDayDate', startOfDay.format('YYYY-MM-DD'))",
|
||||
"pm.variables.set('tsEndOfDayDate', endOfDay.format('YYYY-MM-DD'))",
|
||||
"pm.variables.set('tsEndOfTomorrowDate', endOfTomorrow.format('YYYY-MM-DD'))",
|
||||
"pm.variables.set('ts1', now.startOf('hour').format('x') / 1000)",
|
||||
"pm.variables.set('ts2', now.startOf('hour').add(1, 'm').format('x') / 1000)",
|
||||
"pm.variables.set('ts3', now.startOf('hour').add(2, 'm').format('x') / 1000)"
|
||||
" console.log(readApiKey)",
|
||||
"",
|
||||
" if (!readApiKey || !writeApiKey) {",
|
||||
" throw new Error('no api key given')",
|
||||
" }",
|
||||
"",
|
||||
" pm.variables.set('READUSER_TOKEN', base64encode(readApiKey))",
|
||||
" pm.variables.set('WRITEUSER_TOKEN', base64encode(writeApiKey))",
|
||||
"",
|
||||
" function base64encode(str) {",
|
||||
" return Buffer.from(str, 'utf-8').toString('base64')",
|
||||
" }",
|
||||
"",
|
||||
" // Heartbeat stuff",
|
||||
" pm.variables.set('tsNow', now.clone().format('x') / 1000)",
|
||||
" pm.variables.set('tsNowMinus1Min', now.clone().add(-1, 'm').format('x') / 1000)",
|
||||
" pm.variables.set('tsNowMinus2Min', now.clone().add(-2, 'm').format('x') / 1000)",
|
||||
" pm.variables.set('tsNowMinus3Min', now.clone().add(-3, 'm').format('x') / 1000)",
|
||||
" pm.variables.set('tsStartOfDay', startOfDay.format('x') / 1000)",
|
||||
" pm.variables.set('tsEndOfDay', endOfDay.format('x') / 1000)",
|
||||
" pm.variables.set('tsEndOfTomorrow', endOfTomorrow.format('x') / 1000)",
|
||||
" pm.variables.set('tsStartOfDayIso', startOfDay.toISOString())",
|
||||
" pm.variables.set('tsEndOfDayIso', endOfDay.toISOString())",
|
||||
" pm.variables.set('tsEndOfTomorrowIso', endOfTomorrow.toISOString())",
|
||||
" pm.variables.set('ts1', now.clone().startOf('hour').format('x') / 1000)",
|
||||
" pm.variables.set('ts2', now.clone().startOf('hour').add(1, 'm').format('x') / 1000)",
|
||||
" pm.variables.set('ts3', now.clone().startOf('hour').add(2, 'm').format('x') / 1000)",
|
||||
"})"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
Reference in New Issue
Block a user