mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: GET /heartbeat endpoint (resolves #241)
This commit is contained in:
parent
7159df30c2
commit
e7f3432113
9
main.go
9
main.go
@ -2,9 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/lpar/gzipped/v2"
|
|
||||||
"github.com/muety/wakapi/models"
|
|
||||||
"github.com/muety/wakapi/routes/relay"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -13,6 +10,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lpar/gzipped/v2"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/routes/relay"
|
||||||
|
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
@ -187,6 +188,7 @@ func main() {
|
|||||||
wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(userService, summaryService)
|
wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(userService, summaryService)
|
||||||
wakatimeV1UsersHandler := wtV1Routes.NewUsersHandler(userService, heartbeatService)
|
wakatimeV1UsersHandler := wtV1Routes.NewUsersHandler(userService, heartbeatService)
|
||||||
wakatimeV1ProjectsHandler := wtV1Routes.NewProjectsHandler(userService, heartbeatService)
|
wakatimeV1ProjectsHandler := wtV1Routes.NewProjectsHandler(userService, heartbeatService)
|
||||||
|
wakatimeV1HeartbeatsHandler := wtV1Routes.NewHeartbeatHandler(userService, heartbeatService)
|
||||||
shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService)
|
shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService)
|
||||||
|
|
||||||
// MVC Handlers
|
// MVC Handlers
|
||||||
@ -241,6 +243,7 @@ func main() {
|
|||||||
wakatimeV1StatsHandler.RegisterRoutes(apiRouter)
|
wakatimeV1StatsHandler.RegisterRoutes(apiRouter)
|
||||||
wakatimeV1UsersHandler.RegisterRoutes(apiRouter)
|
wakatimeV1UsersHandler.RegisterRoutes(apiRouter)
|
||||||
wakatimeV1ProjectsHandler.RegisterRoutes(apiRouter)
|
wakatimeV1ProjectsHandler.RegisterRoutes(apiRouter)
|
||||||
|
wakatimeV1HeartbeatsHandler.RegisterRoutes(apiRouter)
|
||||||
shieldV1BadgeHandler.RegisterRoutes(apiRouter)
|
shieldV1BadgeHandler.RegisterRoutes(apiRouter)
|
||||||
|
|
||||||
// Static Routes
|
// Static Routes
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,11 +21,34 @@ type HeartbeatEntry struct {
|
|||||||
IsWrite bool `json:"is_write"`
|
IsWrite bool `json:"is_write"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
Time models.CustomTime `json:"time"`
|
Time float64 `json:"time"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
UserId string `json:"user_id"`
|
UserId string `json:"user_id"`
|
||||||
MachineNameId string `json:"machine_name_id"`
|
MachineNameId string `json:"machine_name_id"`
|
||||||
UserAgentId string `json:"user_agent_id"`
|
UserAgentId string `json:"user_agent_id"`
|
||||||
CreatedAt models.CustomTime `json:"created_at"`
|
CreatedAt models.CustomTime `json:"created_at"`
|
||||||
ModifiedAt models.CustomTime `json:"created_at"`
|
ModifiedAt models.CustomTime `json:"created_at",omitempty`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToHeartbeatEntry(entries []*models.Heartbeat) []HeartbeatEntry {
|
||||||
|
out := make([]HeartbeatEntry, len(entries))
|
||||||
|
for i := 0; i < len(entries); i++ {
|
||||||
|
entry := entries[i]
|
||||||
|
out[i] = HeartbeatEntry{
|
||||||
|
Id: strconv.FormatUint(entry.ID, 10),
|
||||||
|
Branch: entry.Branch,
|
||||||
|
Category: entry.Category,
|
||||||
|
Entity: entry.Entity,
|
||||||
|
IsWrite: entry.IsWrite,
|
||||||
|
Language: entry.Language,
|
||||||
|
Project: entry.Project,
|
||||||
|
Time: float64(entry.Time.T().Unix()),
|
||||||
|
Type: entry.Type,
|
||||||
|
UserId: entry.UserID,
|
||||||
|
MachineNameId: entry.Machine,
|
||||||
|
UserAgentId: entry.UserAgent,
|
||||||
|
CreatedAt: entry.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,7 @@ func constructSuccessResponse(n int) *heartbeatResponseVm {
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /v1/users/{user}/heartbeats [post]
|
// @Router /v1/users/{user}/heartbeats [post]
|
||||||
@ -155,6 +156,7 @@ func (h *HeartbeatApiHandler) postAlias1() {}
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /compat/wakatime/v1/users/{user}/heartbeats [post]
|
// @Router /compat/wakatime/v1/users/{user}/heartbeats [post]
|
||||||
@ -165,6 +167,7 @@ func (h *HeartbeatApiHandler) postAlias2() {}
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
// @Param heartbeat body models.Heartbeat true "A single heartbeat"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /users/{user}/heartbeats [post]
|
// @Router /users/{user}/heartbeats [post]
|
||||||
@ -185,6 +188,7 @@ func (h *HeartbeatApiHandler) postAlias4() {}
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /v1/users/{user}/heartbeats.bulk [post]
|
// @Router /v1/users/{user}/heartbeats.bulk [post]
|
||||||
@ -195,6 +199,7 @@ func (h *HeartbeatApiHandler) postAlias5() {}
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /compat/wakatime/v1/users/{user}/heartbeats.bulk [post]
|
// @Router /compat/wakatime/v1/users/{user}/heartbeats.bulk [post]
|
||||||
@ -205,6 +210,7 @@ func (h *HeartbeatApiHandler) postAlias6() {}
|
|||||||
// @Tags heartbeat
|
// @Tags heartbeat
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
// @Param heartbeat body []models.Heartbeat true "Multiple heartbeats"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 201
|
// @Success 201
|
||||||
// @Router /users/{user}/heartbeats.bulk [post]
|
// @Router /users/{user}/heartbeats.bulk [post]
|
||||||
|
85
routes/compat/wakatime/v1/heartbeat.go
Normal file
85
routes/compat/wakatime/v1/heartbeat.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
conf "github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/middlewares"
|
||||||
|
wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
|
"github.com/muety/wakapi/services"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeartbeatsResult struct {
|
||||||
|
Data []wakatime.HeartbeatEntry `json:"data"`
|
||||||
|
End string `json:"end"`
|
||||||
|
Start string `json:"start"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeartbeatHandler struct {
|
||||||
|
userSrvc services.IUserService
|
||||||
|
heartbeatSrvc services.IHeartbeatService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHeartbeatHandler(userService services.IUserService, heartbeatService services.IHeartbeatService) *HeartbeatHandler {
|
||||||
|
return &HeartbeatHandler{
|
||||||
|
userSrvc: userService,
|
||||||
|
heartbeatSrvc: heartbeatService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeartbeatHandler) RegisterRoutes(router *mux.Router) {
|
||||||
|
r := router.PathPrefix("").Subrouter()
|
||||||
|
r.Use(
|
||||||
|
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
|
||||||
|
)
|
||||||
|
r.Path("/compat/wakatime/v1/users/{user}/heartbeats").Methods(http.MethodGet).HandlerFunc(h.Get)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Get heartbeats of user for specified date
|
||||||
|
// @ID get-heartbeats
|
||||||
|
// @Tags heartbeat
|
||||||
|
// @Param date query string true "Date"
|
||||||
|
// @Param user path string true "Username (or current)"
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {object} v1.HeartbeatEntry
|
||||||
|
// @Failure 400 {string} string "bad date"
|
||||||
|
// @Router /compat/wakatime/v1/users/{user}/heartbeats [get]
|
||||||
|
func (h *HeartbeatHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current")
|
||||||
|
if err != nil {
|
||||||
|
return // response was already sent by util function
|
||||||
|
}
|
||||||
|
|
||||||
|
params := r.URL.Query()
|
||||||
|
dateParam := params.Get("date")
|
||||||
|
date, err := time.Parse(conf.SimpleDateFormat, dateParam)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("bad date"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone := user.TZ()
|
||||||
|
rangeFrom, rangeTo := utils.StartOfDay(date.In(timezone)), utils.EndOfDay(date.In(timezone))
|
||||||
|
|
||||||
|
heartbeats, err := h.heartbeatSrvc.GetAllWithin(rangeFrom, rangeTo, user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(conf.ErrInternalServerError))
|
||||||
|
conf.Log().Request(r).Error("failed to retrieve heartbeats - %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := HeartbeatsResult{
|
||||||
|
Data: wakatime.ToHeartbeatEntry(heartbeats),
|
||||||
|
Start: rangeFrom.UTC().Format(time.RFC3339),
|
||||||
|
End: rangeTo.UTC().Format(time.RFC3339),
|
||||||
|
Timezone: timezone.String(),
|
||||||
|
}
|
||||||
|
utils.RespondJSON(w, r, http.StatusOK, res)
|
||||||
|
}
|
@ -6,6 +6,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
@ -13,8 +16,6 @@ import (
|
|||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const OriginWakatime = "wakatime"
|
const OriginWakatime = "wakatime"
|
||||||
@ -284,7 +285,7 @@ func mapHeartbeat(
|
|||||||
OperatingSystem: ua.Os,
|
OperatingSystem: ua.Os,
|
||||||
Machine: ma.Value,
|
Machine: ma.Value,
|
||||||
UserAgent: ua.Value,
|
UserAgent: ua.Value,
|
||||||
Time: entry.Time,
|
Time: models.CustomTime(time.Unix(0, int64(entry.Time*1e9))),
|
||||||
Origin: OriginWakatime,
|
Origin: OriginWakatime,
|
||||||
OriginId: entry.Id,
|
OriginId: entry.Id,
|
||||||
}).Hashed()
|
}).Hashed()
|
||||||
|
@ -160,6 +160,48 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/compat/wakatime/v1/users/{user}/heartbeats": {
|
"/compat/wakatime/v1/users/{user}/heartbeats": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"heartbeat"
|
||||||
|
],
|
||||||
|
"summary": "Get heartbeats of user for specified date",
|
||||||
|
"operationId": "get-heartbeats",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Date",
|
||||||
|
"name": "date",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1.HeartbeatEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "bad date",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
@ -183,6 +225,13 @@ var doc = `{
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -219,6 +268,13 @@ var doc = `{
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -863,6 +919,13 @@ var doc = `{
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -899,6 +962,13 @@ var doc = `{
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -967,6 +1037,13 @@ var doc = `{
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1003,6 +1080,13 @@ var doc = `{
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1094,6 +1178,13 @@ var doc = `{
|
|||||||
"models.Summary": {
|
"models.Summary": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"description": "branches are not persisted, but calculated at runtime in case a project filter is applied",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.SummaryItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
"editors": {
|
"editors": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@ -1222,6 +1313,50 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1.HeartbeatEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"branch": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_write": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"machine_name_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_agent_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1.Project": {
|
"v1.Project": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1250,6 +1385,12 @@ var doc = `{
|
|||||||
"v1.StatsData": {
|
"v1.StatsData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1.SummariesEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
"daily_average": {
|
"daily_average": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@ -1325,6 +1466,12 @@ var doc = `{
|
|||||||
"v1.SummariesData": {
|
"v1.SummariesData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1.SummariesEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -145,6 +145,48 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/compat/wakatime/v1/users/{user}/heartbeats": {
|
"/compat/wakatime/v1/users/{user}/heartbeats": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"heartbeat"
|
||||||
|
],
|
||||||
|
"summary": "Get heartbeats of user for specified date",
|
||||||
|
"operationId": "get-heartbeats",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Date",
|
||||||
|
"name": "date",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1.HeartbeatEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "bad date",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
@ -168,6 +210,13 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -204,6 +253,13 @@
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -848,6 +904,13 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -884,6 +947,13 @@
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -952,6 +1022,13 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -988,6 +1065,13 @@
|
|||||||
"$ref": "#/definitions/models.Heartbeat"
|
"$ref": "#/definitions/models.Heartbeat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (or current)",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1079,6 +1163,13 @@
|
|||||||
"models.Summary": {
|
"models.Summary": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"description": "branches are not persisted, but calculated at runtime in case a project filter is applied",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.SummaryItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
"editors": {
|
"editors": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@ -1207,6 +1298,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1.HeartbeatEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"branch": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_write": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"machine_name_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_agent_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1.Project": {
|
"v1.Project": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1235,6 +1370,12 @@
|
|||||||
"v1.StatsData": {
|
"v1.StatsData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1.SummariesEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
"daily_average": {
|
"daily_average": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@ -1310,6 +1451,12 @@
|
|||||||
"v1.SummariesData": {
|
"v1.SummariesData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1.SummariesEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -54,6 +54,12 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
models.Summary:
|
models.Summary:
|
||||||
properties:
|
properties:
|
||||||
|
branches:
|
||||||
|
description: branches are not persisted, but calculated at runtime in case
|
||||||
|
a project filter is applied
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.SummaryItem'
|
||||||
|
type: array
|
||||||
editors:
|
editors:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.SummaryItem'
|
$ref: '#/definitions/models.SummaryItem'
|
||||||
@ -142,6 +148,35 @@ definitions:
|
|||||||
schemaVersion:
|
schemaVersion:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
v1.HeartbeatEntry:
|
||||||
|
properties:
|
||||||
|
branch:
|
||||||
|
type: string
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
entity:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
is_write:
|
||||||
|
type: boolean
|
||||||
|
language:
|
||||||
|
type: string
|
||||||
|
machine_name_id:
|
||||||
|
type: string
|
||||||
|
project:
|
||||||
|
type: string
|
||||||
|
time:
|
||||||
|
type: number
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
user_agent_id:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
v1.Project:
|
v1.Project:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
@ -160,6 +195,10 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
v1.StatsData:
|
v1.StatsData:
|
||||||
properties:
|
properties:
|
||||||
|
branches:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/v1.SummariesEntry'
|
||||||
|
type: array
|
||||||
daily_average:
|
daily_average:
|
||||||
type: number
|
type: number
|
||||||
days_including_holidays:
|
days_including_holidays:
|
||||||
@ -209,6 +248,10 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
v1.SummariesData:
|
v1.SummariesData:
|
||||||
properties:
|
properties:
|
||||||
|
branches:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/v1.SummariesEntry'
|
||||||
|
type: array
|
||||||
categories:
|
categories:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/v1.SummariesEntry'
|
$ref: '#/definitions/v1.SummariesEntry'
|
||||||
@ -441,6 +484,33 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- wakatime
|
- wakatime
|
||||||
/compat/wakatime/v1/users/{user}/heartbeats:
|
/compat/wakatime/v1/users/{user}/heartbeats:
|
||||||
|
get:
|
||||||
|
operationId: get-heartbeats
|
||||||
|
parameters:
|
||||||
|
- description: Date
|
||||||
|
in: query
|
||||||
|
name: date
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/v1.HeartbeatEntry'
|
||||||
|
"400":
|
||||||
|
description: bad date
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Get heartbeats of user for specified date
|
||||||
|
tags:
|
||||||
|
- heartbeat
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@ -452,6 +522,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
@ -474,6 +549,11 @@ paths:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
type: array
|
type: array
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
@ -900,6 +980,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
@ -922,6 +1007,11 @@ paths:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
type: array
|
type: array
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
@ -965,6 +1055,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
@ -987,6 +1082,11 @@ paths:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.Heartbeat'
|
$ref: '#/definitions/models.Heartbeat'
|
||||||
type: array
|
type: array
|
||||||
|
- description: Username (or current)
|
||||||
|
in: path
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: ""
|
description: ""
|
||||||
|
@ -965,6 +965,121 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create heartbeats (get heartbeats test)",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"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)",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{WRITEUSER_TOKEN}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"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}]",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{BASE_URL}}/api/heartbeat",
|
||||||
|
"host": [
|
||||||
|
"{{BASE_URL}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"heartbeat"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get heartbeats",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Status code is 200\", function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"pm.test(\"Response body is correct\", function () {",
|
||||||
|
" var jsonData = pm.response.json();",
|
||||||
|
" pm.expect(jsonData.timezone).to.eql(pm.collectionVariables.get('TZ'));",
|
||||||
|
" var date = new Date(\"2022-01-01T00:00:00+0100\")",
|
||||||
|
" pm.expect(new Date(jsonData.start)).to.eql(date);",
|
||||||
|
" pm.expect(new Date(jsonData.end)).to.eql(new Date(date.getTime() + 3600 * 1000 * 24));",
|
||||||
|
" pm.expect(jsonData.data.length).to.eql(2);",
|
||||||
|
"});"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableCookies": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{WRITEUSER_TOKEN}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/heartbeats?date=2022-01-01",
|
||||||
|
"host": [
|
||||||
|
"{{BASE_URL}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"compat",
|
||||||
|
"wakatime",
|
||||||
|
"v1",
|
||||||
|
"users",
|
||||||
|
"current",
|
||||||
|
"heartbeats"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "date",
|
||||||
|
"value": "2022-01-01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1982,7 +2097,7 @@
|
|||||||
"",
|
"",
|
||||||
"pm.test(\"Correct content\", function () {",
|
"pm.test(\"Correct content\", function () {",
|
||||||
" const jsonData = pm.response.json();",
|
" const jsonData = pm.response.json();",
|
||||||
" pm.expect(jsonData.data.text).to.eql('0 hrs 2 mins');",
|
" pm.expect(jsonData.data.text).to.eql('0 hrs 8 mins');",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
|
Loading…
Reference in New Issue
Block a user