2020-09-12 00:24:51 +03:00
|
|
|
package v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"github.com/gorilla/mux"
|
2020-11-08 12:12:49 +03:00
|
|
|
conf "github.com/muety/wakapi/config"
|
2021-02-06 22:09:08 +03:00
|
|
|
"github.com/muety/wakapi/middlewares"
|
2020-09-12 00:24:51 +03:00
|
|
|
"github.com/muety/wakapi/models"
|
2020-09-12 17:09:23 +03:00
|
|
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
2020-09-12 00:24:51 +03:00
|
|
|
"github.com/muety/wakapi/services"
|
|
|
|
"github.com/muety/wakapi/utils"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2020-09-12 17:09:23 +03:00
|
|
|
type SummariesHandler struct {
|
2020-11-08 12:12:49 +03:00
|
|
|
config *conf.Config
|
2021-02-06 22:09:08 +03:00
|
|
|
userSrvc services.IUserService
|
2020-11-08 12:12:49 +03:00
|
|
|
summarySrvc services.ISummaryService
|
2020-09-12 00:24:51 +03:00
|
|
|
}
|
|
|
|
|
2021-02-06 22:09:08 +03:00
|
|
|
func NewSummariesHandler(userService services.IUserService, summaryService services.ISummaryService) *SummariesHandler {
|
2020-09-12 17:09:23 +03:00
|
|
|
return &SummariesHandler{
|
2021-02-06 22:09:08 +03:00
|
|
|
userSrvc: userService,
|
2020-09-12 00:24:51 +03:00
|
|
|
summarySrvc: summaryService,
|
2020-11-08 12:12:49 +03:00
|
|
|
config: conf.Get(),
|
2020-09-12 00:24:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 23:28:02 +03:00
|
|
|
func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
|
2021-02-07 00:40:54 +03:00
|
|
|
r := router.PathPrefix("/compat/wakatime/v1/users/{user}/summaries").Subrouter()
|
2021-02-06 22:09:08 +03:00
|
|
|
r.Use(
|
|
|
|
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
|
|
|
|
)
|
|
|
|
r.Methods(http.MethodGet).HandlerFunc(h.Get)
|
2021-01-30 12:34:52 +03:00
|
|
|
}
|
|
|
|
|
2021-01-31 18:23:47 +03:00
|
|
|
// TODO: Support parameters: project, branches, timeout, writes_only, timezone
|
|
|
|
// See https://wakatime.com/developers#summaries.
|
|
|
|
// Timezone can be specified via an offset suffix (e.g. +02:00) in date strings.
|
|
|
|
// Requires https://github.com/muety/wakapi/issues/108.
|
2020-09-12 00:24:51 +03:00
|
|
|
|
2021-02-07 13:54:07 +03:00
|
|
|
// @Summary Retrieve WakaTime-compatible summaries
|
|
|
|
// @Description Mimics https://wakatime.com/developers#summaries.
|
|
|
|
// @ID get-wakatime-summaries
|
|
|
|
// @Tags wakatime
|
|
|
|
// @Produce json
|
|
|
|
// @Param user path string true "User ID to fetch data for (or 'current')"
|
|
|
|
// @Param range query string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
|
|
|
|
// @Param start query string false "Start date (e.g. '2021-02-07')"
|
|
|
|
// @Param end query string false "End date (e.g. '2021-02-08')"
|
|
|
|
// @Security ApiKeyAuth
|
|
|
|
// @Success 200 {object} v1.SummariesViewModel
|
|
|
|
// @Router /compat/wakatime/v1/users/{user}/summaries [get]
|
2021-02-03 23:28:02 +03:00
|
|
|
func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
2020-09-12 00:24:51 +03:00
|
|
|
vars := mux.Vars(r)
|
|
|
|
requestedUser := vars["user"]
|
2021-03-26 15:10:10 +03:00
|
|
|
authorizedUser := middlewares.GetPrincipal(r)
|
2020-09-12 00:24:51 +03:00
|
|
|
|
|
|
|
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
summaries, err, status := h.loadUserSummaries(r)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(status)
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-31 18:23:47 +03:00
|
|
|
filters := &models.Filters{}
|
|
|
|
if projectQuery := r.URL.Query().Get("project"); projectQuery != "" {
|
|
|
|
filters.Project = projectQuery
|
|
|
|
}
|
|
|
|
|
|
|
|
vm := v1.NewSummariesFrom(summaries, filters)
|
2021-04-26 22:26:47 +03:00
|
|
|
utils.RespondJSON(w, r, http.StatusOK, vm)
|
2020-09-12 00:24:51 +03:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:09:23 +03:00
|
|
|
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
2021-03-26 15:10:10 +03:00
|
|
|
user := middlewares.GetPrincipal(r)
|
2020-09-12 00:24:51 +03:00
|
|
|
params := r.URL.Query()
|
2021-01-31 18:23:47 +03:00
|
|
|
rangeParam, startParam, endParam := params.Get("range"), params.Get("start"), params.Get("end")
|
2020-09-12 00:24:51 +03:00
|
|
|
|
|
|
|
var start, end time.Time
|
2021-01-31 18:23:47 +03:00
|
|
|
if rangeParam != "" {
|
2021-01-31 20:41:48 +03:00
|
|
|
// range param takes precedence
|
2021-04-25 15:15:18 +03:00
|
|
|
if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(rangeParam, user.TZ()); err == nil {
|
2021-01-31 18:23:47 +03:00
|
|
|
start, end = parsedFrom, parsedTo
|
|
|
|
} else {
|
|
|
|
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
|
|
|
|
}
|
2021-04-25 15:15:18 +03:00
|
|
|
} else if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(startParam, user.TZ()); err == nil && startParam == endParam {
|
2021-01-31 20:41:48 +03:00
|
|
|
// also accept start param to be a range param
|
|
|
|
start, end = parsedFrom, parsedTo
|
2020-09-12 00:24:51 +03:00
|
|
|
} else {
|
2021-01-31 20:41:48 +03:00
|
|
|
// eventually, consider start and end params a date
|
2020-09-12 00:24:51 +03:00
|
|
|
var err error
|
|
|
|
|
2021-04-28 23:19:44 +03:00
|
|
|
start, err = utils.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), user.TZ())
|
2020-09-12 00:24:51 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest
|
|
|
|
}
|
|
|
|
|
2021-04-28 23:19:44 +03:00
|
|
|
end, err = utils.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), user.TZ())
|
2020-09-12 00:24:51 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-29 22:08:47 +03:00
|
|
|
// wakatime iterprets end date as "inclusive", wakapi usually as "exclusive"
|
|
|
|
// i.e. for wakatime, an interval 2021-04-29 - 2021-04-29 is actually 2021-04-29 - 2021-04-30,
|
|
|
|
// while for wakapi it would be empty
|
|
|
|
// see https://github.com/muety/wakapi/issues/192
|
|
|
|
end = utils.EndOfDay(end).Add(-1 * time.Second)
|
|
|
|
|
2020-09-12 00:24:51 +03:00
|
|
|
overallParams := &models.SummaryParams{
|
|
|
|
From: start,
|
|
|
|
To: end,
|
|
|
|
User: user,
|
|
|
|
Recompute: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
intervals := utils.SplitRangeByDays(overallParams.From, overallParams.To)
|
|
|
|
summaries := make([]*models.Summary, len(intervals))
|
|
|
|
|
|
|
|
for i, interval := range intervals {
|
2021-03-25 01:31:04 +03:00
|
|
|
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, false)
|
2020-09-12 00:24:51 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err, http.StatusInternalServerError
|
|
|
|
}
|
2021-04-29 22:08:47 +03:00
|
|
|
// wakatime returns requested instead of actual summary range
|
|
|
|
summary.FromTime = models.CustomTime(interval[0])
|
|
|
|
summary.ToTime = models.CustomTime(interval[1].Add(-1 * time.Second))
|
2020-09-12 00:24:51 +03:00
|
|
|
summaries[i] = summary
|
|
|
|
}
|
|
|
|
|
|
|
|
return summaries, nil, http.StatusOK
|
|
|
|
}
|