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

feat: option to publicly share stats data (resolve #36)

This commit is contained in:
Ferdinand Mütsch
2021-02-06 22:32:03 +01:00
parent d1dc73b5e6
commit fca12f522f
12 changed files with 605 additions and 414 deletions

View File

@ -10,6 +10,7 @@ import (
"net/http"
"regexp"
"strings"
"time"
)
const (
@ -46,13 +47,6 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
return
}
requestedUserId := mux.Vars(r)["user"]
user, err := h.userSrvc.GetUserById(requestedUserId)
if err != nil || !user.BadgesEnabled {
w.WriteHeader(http.StatusUnauthorized)
return
}
var filterEntity, filterKey string
if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
filterEntity, filterKey = groups[1], groups[2]
@ -65,6 +59,21 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
}
}
requestedUserId := mux.Vars(r)["user"]
user, err := h.userSrvc.GetUserById(requestedUserId)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
_, rangeFrom, rangeTo := utils.ResolveInterval(interval)
minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(user.ShareDataMaxDays)))
if rangeFrom.Before(minStart) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("requested time range too broad"))
return
}
var filters *models.Filters
switch filterEntity {
case "project":

View File

@ -1,7 +1,6 @@
package v1
import (
"errors"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/middlewares"
@ -30,7 +29,7 @@ func NewStatsHandler(userService services.IUserService, summaryService services.
func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/wakatime/v1/users/{user}/stats/{range}").Subrouter()
r.Use(
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
middlewares.NewAuthenticateMiddleware(h.userSrvc).WithOptionalFor([]string{"/"}).Handler,
)
r.Methods(http.MethodGet).HandlerFunc(h.Get)
}
@ -38,42 +37,69 @@ func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
// TODO: support filtering (requires https://github.com/muety/wakapi/issues/108)
func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
requestedUser := vars["user"]
requestedRange := vars["range"]
var vars = mux.Vars(r)
var authorizedUser, requestedUser *models.User
user := r.Context().Value(models.UserKey).(*models.User)
if u := r.Context().Value(models.UserKey); u != nil {
authorizedUser = u.(*models.User)
}
if requestedUser != user.ID && requestedUser != "current" {
w.WriteHeader(http.StatusForbidden)
if authorizedUser != nil && vars["user"] == "current" {
vars["user"] = authorizedUser.ID
}
requestedUser, err := h.userSrvc.GetUserById(vars["user"])
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("user not found"))
return
}
summary, err, status := h.loadUserSummary(user, requestedRange)
err, rangeFrom, rangeTo := utils.ResolveIntervalRaw(vars["range"])
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("invalid range"))
return
}
minStart := utils.StartOfDay(rangeTo.Add(-24 * time.Hour * time.Duration(requestedUser.ShareDataMaxDays)))
if (authorizedUser == nil || requestedUser.ID != authorizedUser.ID) &&
(requestedUser.ShareDataMaxDays == 0 || rangeFrom.Before(minStart)) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("requested time range too broad"))
return
}
summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo)
if err != nil {
w.WriteHeader(status)
w.Write([]byte(err.Error()))
return
}
filters := &models.Filters{}
if projectQuery := r.URL.Query().Get("project"); projectQuery != "" {
filters.Project = projectQuery
stats := v1.NewStatsFrom(summary, &models.Filters{})
// post filter stats according to user's given sharing permissions
if !requestedUser.ShareEditors {
stats.Data.Editors = nil
}
if !requestedUser.ShareLanguages {
stats.Data.Languages = nil
}
if !requestedUser.ShareProjects {
stats.Data.Projects = nil
}
if !requestedUser.ShareOSs {
stats.Data.OperatingSystems = nil
}
if !requestedUser.ShareMachines {
stats.Data.Machines = nil
}
vm := v1.NewStatsFrom(summary, filters)
utils.RespondJSON(w, http.StatusOK, vm)
utils.RespondJSON(w, http.StatusOK, stats)
}
func (h *StatsHandler) loadUserSummary(user *models.User, rangeKey string) (*models.Summary, error, int) {
var start, end time.Time
if err, parsedFrom, parsedTo := utils.ResolveIntervalRaw(rangeKey); err == nil {
start, end = parsedFrom, parsedTo
} else {
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
}
func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time) (*models.Summary, error, int) {
overallParams := &models.SummaryParams{
From: start,
To: end,

View File

@ -2,6 +2,7 @@ package routes
import (
"encoding/base64"
"errors"
"fmt"
"github.com/emvi/logbuch"
"github.com/gorilla/mux"
@ -127,8 +128,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
return h.actionDeleteLanguageMapping
case "add_mapping":
return h.actionAddLanguageMapping
case "toggle_badges":
return h.actionToggleBadges
case "update_sharing":
return h.actionUpdateSharing
case "toggle_wakatime":
return h.actionSetWakatimeApiKey
case "import_wakatime":
@ -202,6 +203,38 @@ func (h *SettingsHandler) actionResetApiKey(w http.ResponseWriter, r *http.Reque
return http.StatusOK, msg, ""
}
func (h *SettingsHandler) actionUpdateSharing(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
var err error
user := r.Context().Value(models.UserKey).(*models.User)
defer h.userSrvc.FlushCache()
user.ShareProjects, err = strconv.ParseBool(r.PostFormValue("share_projects"))
user.ShareLanguages, err = strconv.ParseBool(r.PostFormValue("share_languages"))
user.ShareEditors, err = strconv.ParseBool(r.PostFormValue("share_editors"))
user.ShareOSs, err = strconv.ParseBool(r.PostFormValue("share_oss"))
user.ShareMachines, err = strconv.ParseBool(r.PostFormValue("share_machines"))
if v, e := strconv.Atoi(r.PostFormValue("max_days")); e == nil && v >= 0 {
user.ShareDataMaxDays = uint(v)
} else {
err = errors.New("")
}
if err != nil {
return http.StatusBadRequest, "", "invalid input"
}
if _, err := h.userSrvc.Update(user); err != nil {
return http.StatusInternalServerError, "", "internal sever error"
}
return http.StatusOK, "settings updated", ""
}
func (h *SettingsHandler) actionDeleteAlias(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
@ -299,19 +332,6 @@ func (h *SettingsHandler) actionAddLanguageMapping(w http.ResponseWriter, r *htt
return http.StatusOK, "mapping added successfully", ""
}
func (h *SettingsHandler) actionToggleBadges(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
if _, err := h.userSrvc.ToggleBadges(user); err != nil {
return http.StatusInternalServerError, "", "internal server error"
}
return http.StatusOK, "", ""
}
func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()