mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: wakatime data import (resolve #87)
This commit is contained in:
@ -11,6 +11,7 @@ import (
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/models/view"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/services/imports"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -21,15 +22,25 @@ type SettingsHandler struct {
|
||||
config *conf.Config
|
||||
userSrvc services.IUserService
|
||||
summarySrvc services.ISummaryService
|
||||
heartbeatSrvc services.IHeartbeatService
|
||||
aliasSrvc services.IAliasService
|
||||
aggregationSrvc services.IAggregationService
|
||||
languageMappingSrvc services.ILanguageMappingService
|
||||
keyValueSrvc services.IKeyValueService
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
var credentialsDecoder = schema.NewDecoder()
|
||||
|
||||
func NewSettingsHandler(userService services.IUserService, summaryService services.ISummaryService, aliasService services.IAliasService, aggregationService services.IAggregationService, languageMappingService services.ILanguageMappingService) *SettingsHandler {
|
||||
func NewSettingsHandler(
|
||||
userService services.IUserService,
|
||||
heartbeatService services.IHeartbeatService,
|
||||
summaryService services.ISummaryService,
|
||||
aliasService services.IAliasService,
|
||||
aggregationService services.IAggregationService,
|
||||
languageMappingService services.ILanguageMappingService,
|
||||
keyValueService services.IKeyValueService,
|
||||
) *SettingsHandler {
|
||||
return &SettingsHandler{
|
||||
config: conf.Get(),
|
||||
summarySrvc: summaryService,
|
||||
@ -37,6 +48,8 @@ func NewSettingsHandler(userService services.IUserService, summaryService servic
|
||||
aggregationSrvc: aggregationService,
|
||||
languageMappingSrvc: languageMappingService,
|
||||
userSrvc: userService,
|
||||
heartbeatSrvc: heartbeatService,
|
||||
keyValueSrvc: keyValueService,
|
||||
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
@ -88,10 +101,12 @@ func (h *SettingsHandler) PostIndex(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if errorMsg != "" {
|
||||
w.WriteHeader(status)
|
||||
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError(errorMsg))
|
||||
return
|
||||
}
|
||||
if successMsg != "" {
|
||||
w.WriteHeader(status)
|
||||
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess(successMsg))
|
||||
return
|
||||
}
|
||||
@ -116,6 +131,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
|
||||
return h.actionToggleBadges
|
||||
case "toggle_wakatime":
|
||||
return h.actionSetWakatimeApiKey
|
||||
case "import_wakatime":
|
||||
return h.actionImportWaktime
|
||||
case "regenerate_summaries":
|
||||
return h.actionRegenerateSummaries
|
||||
case "delete_account":
|
||||
@ -315,6 +332,66 @@ func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http
|
||||
return http.StatusOK, "Wakatime API Key updated successfully", ""
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
if user.WakatimeApiKey == "" {
|
||||
return http.StatusForbidden, "", "not connected to wakatime"
|
||||
}
|
||||
|
||||
kvKey := fmt.Sprintf("%s_%s", conf.KeyLastImportImport, user.ID)
|
||||
|
||||
if !h.config.IsDev() {
|
||||
lastImportKv := h.keyValueSrvc.MustGetString(kvKey)
|
||||
lastImport, _ := time.Parse(time.RFC822, lastImportKv.Value)
|
||||
if time.Now().Sub(lastImport) < time.Duration(h.config.App.ImportBackoffMin)*time.Minute {
|
||||
return http.StatusTooManyRequests,
|
||||
"",
|
||||
fmt.Sprintf("Too many data imports. You are only allowed to request an import every %d minutes.", h.config.App.ImportBackoffMin)
|
||||
}
|
||||
}
|
||||
|
||||
go func(user *models.User) {
|
||||
importer := imports.NewWakatimeHeartbeatImporter(user.WakatimeApiKey)
|
||||
|
||||
countBefore, err := h.heartbeatSrvc.CountByUser(user)
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
|
||||
count := 0
|
||||
batch := make([]*models.Heartbeat, 0)
|
||||
|
||||
for hb := range importer.Import(user) {
|
||||
count++
|
||||
batch = append(batch, hb)
|
||||
|
||||
if len(batch) == h.config.App.ImportBatchSize {
|
||||
if err := h.heartbeatSrvc.InsertBatch(batch); err != nil {
|
||||
logbuch.Warn("failed to insert imported heartbeat, already existing? – %v", err)
|
||||
}
|
||||
|
||||
batch = make([]*models.Heartbeat, 0)
|
||||
}
|
||||
}
|
||||
|
||||
countAfter, _ := h.heartbeatSrvc.CountByUser(user)
|
||||
logbuch.Info("downloaded %d heartbeats for user '%s' (%d actually imported)", count, user.ID, countAfter-countBefore)
|
||||
|
||||
h.regenerateSummaries(user)
|
||||
}(user)
|
||||
|
||||
h.keyValueSrvc.PutString(&models.KeyStringValue{
|
||||
Key: kvKey,
|
||||
Value: time.Now().Format(time.RFC822),
|
||||
})
|
||||
|
||||
return http.StatusAccepted, "Import started. This may take a few minutes.", ""
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
@ -322,16 +399,8 @@ func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *ht
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
|
||||
logbuch.Info("clearing summaries for user '%s'", user.ID)
|
||||
if err := h.summarySrvc.DeleteByUser(user.ID); err != nil {
|
||||
logbuch.Error("failed to clear summaries: %v", err)
|
||||
return http.StatusInternalServerError, "", "failed to delete old summaries"
|
||||
}
|
||||
|
||||
if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
|
||||
logbuch.Error("failed to regenerate summaries: %v", err)
|
||||
return http.StatusInternalServerError, "", "failed to generate aggregations"
|
||||
|
||||
if err := h.regenerateSummaries(user); err != nil {
|
||||
return http.StatusInternalServerError, "", "failed to regenerate summaries"
|
||||
}
|
||||
|
||||
return http.StatusOK, "summaries are being regenerated – this may take a few seconds", ""
|
||||
@ -368,7 +437,7 @@ func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool {
|
||||
|
||||
request, err := http.NewRequest(
|
||||
http.MethodGet,
|
||||
conf.WakatimeApiUrl+conf.WakatimeApiUserEndpoint,
|
||||
conf.WakatimeApiUrl+conf.WakatimeApiUserUrl,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
@ -385,6 +454,21 @@ func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) regenerateSummaries(user *models.User) error {
|
||||
logbuch.Info("clearing summaries for user '%s'", user.ID)
|
||||
if err := h.summarySrvc.DeleteByUser(user.ID); err != nil {
|
||||
logbuch.Error("failed to clear summaries: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
|
||||
logbuch.Error("failed to regenerate summaries: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel {
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
mappings, _ := h.languageMappingSrvc.GetByUser(user.ID)
|
||||
|
Reference in New Issue
Block a user