fix: purge user durations as part of data cleanup (relates to #785)

This commit is contained in:
Ferdinand Mütsch
2025-05-10 11:10:58 +02:00
parent ca3035b14b
commit 8c8ae5d7ee
7 changed files with 1096 additions and 1070 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -232,13 +232,13 @@ func main() {
// MVC Handlers
summaryHandler := routes.NewSummaryHandler(summaryService, userService, keyValueService, durationService, aliasService)
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, projectLabelService, keyValueService, mailService)
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, durationService, summaryService, aliasService, aggregationService, languageMappingService, projectLabelService, keyValueService, mailService)
subscriptionHandler := routes.NewSubscriptionHandler(userService, mailService, keyValueService)
projectsHandler := routes.NewProjectsHandler(userService, heartbeatService)
homeHandler := routes.NewHomeHandler(userService, keyValueService)
loginHandler := routes.NewLoginHandler(userService, mailService, keyValueService)
imprintHandler := routes.NewImprintHandler(keyValueService)
leaderboardHandler := condition.TernaryOperator[bool, routes.Handler](config.App.LeaderboardEnabled, routes.NewLeaderboardHandler(userService, leaderboardService), routes.NewNoopHandler())
leaderboardHandler := condition.Ternary[bool, routes.Handler](config.App.LeaderboardEnabled, routes.NewLeaderboardHandler(userService, leaderboardService), routes.NewNoopHandler())
// Other Handlers
relayHandler := relay.NewRelayHandler()

View File

@@ -46,7 +46,7 @@ func init() {
if err := tx.Migrator().CreateTable(&models.Duration{}); err != nil {
return err
}
if err := tx.Exec("insert into durations(user_id, time, duration, project, language, editor, operating_system, machine, category, branch, entity, num_heartbeats, group_hash, timeout) select user_id, time, duration, project, language, editor, operating_system, machine, category, branch, entity, num_heartbeats, group_hash, timeout from durations_old").Error; err != nil {
if err := tx.Exec("insert into durations(user_id, time, duration, project, language, editor, operating_system, machine, category, branch, entity, num_heartbeats, group_hash, timeout) select * from durations_old").Error; err != nil {
return err
}
if err := tx.Migrator().DropTable("durations_old"); err != nil {

View File

@@ -21,3 +21,8 @@ func (m *DurationServiceMock) Regenerate(u *models.User, b bool) {
func (m *DurationServiceMock) RegenerateAll() {
}
func (m *DurationServiceMock) DeleteByUser(u *models.User) error {
args := m.Called(u)
return args.Error(0)
}

View File

@@ -34,6 +34,7 @@ type SettingsHandler struct {
userSrvc services.IUserService
summarySrvc services.ISummaryService
heartbeatSrvc services.IHeartbeatService
durationSrvc services.IDurationService
aliasSrvc services.IAliasService
aggregationSrvc services.IAggregationService
languageMappingSrvc services.ILanguageMappingService
@@ -60,6 +61,7 @@ var credentialsDecoder = schema.NewDecoder()
func NewSettingsHandler(
userService services.IUserService,
heartbeatService services.IHeartbeatService,
durationService services.IDurationService,
summaryService services.ISummaryService,
aliasService services.IAliasService,
aggregationService services.IAggregationService,
@@ -77,6 +79,7 @@ func NewSettingsHandler(
projectLabelSrvc: projectLabelService,
userSrvc: userService,
heartbeatSrvc: heartbeatService,
durationSrvc: durationService,
keyValueSrvc: keyValueService,
mailSrvc: mailService,
httpClient: &http.Client{Timeout: 10 * time.Second},
@@ -750,6 +753,11 @@ func (h *SettingsHandler) actionClearData(w http.ResponseWriter, r *http.Request
conf.Log().Request(r).Error("failed to clear summaries", "error", err)
}
slog.Info("deleting durations for user", "userID", user.ID)
if err := h.durationSrvc.DeleteByUser(user); err != nil {
conf.Log().Request(r).Error("failed to clear durations", "error", err)
}
slog.Info("deleting heartbeats for user", "userID", user.ID)
if err := h.heartbeatSrvc.DeleteByUser(user); err != nil {
conf.Log().Request(r).Error("failed to clear heartbeats", "error", err)

View File

@@ -22,10 +22,10 @@ const generateDurationsInterval = 12 * time.Hour
type DurationService struct {
config *config.Config
eventBus *hub.Hub
durationRepository repositories.IDurationRepository
repository repositories.IDurationRepository
heartbeatService IHeartbeatService
userService IUserService
LanguageMappingService ILanguageMappingService
languageMappingService ILanguageMappingService
lastUserJob map[string]time.Time
queue *artifex.Dispatcher
}
@@ -36,8 +36,8 @@ func NewDurationService(durationRepository repositories.IDurationRepository, hea
eventBus: config.EventBus(),
heartbeatService: heartbeatService,
userService: userService,
LanguageMappingService: languageMappingService,
durationRepository: durationRepository,
languageMappingService: languageMappingService,
repository: durationRepository,
lastUserJob: make(map[string]time.Time),
queue: config.GetQueue(config.QueueProcessing),
}
@@ -109,7 +109,7 @@ func (srv *DurationService) Get(from, to time.Time, user *models.User, filters *
func (srv *DurationService) Regenerate(user *models.User, forceAll bool) {
var from time.Time
latest, err := srv.durationRepository.GetLatestByUser(user)
latest, err := srv.repository.GetLatestByUser(user)
if err == nil && latest != nil && !forceAll {
from = latest.TimeEnd()
}
@@ -126,13 +126,13 @@ func (srv *DurationService) Regenerate(user *models.User, forceAll bool) {
}
if forceAll {
if err := srv.durationRepository.DeleteByUser(user); err != nil {
if err := srv.repository.DeleteByUser(user); err != nil {
config.Log().Error("failed to delete old durations while generating ephemeral new ones", "user", user.ID, "error", err)
return
}
}
if err := srv.durationRepository.InsertBatch(durations); err != nil {
if err := srv.repository.InsertBatch(durations); err != nil {
config.Log().Error("failed to persist new ephemeral durations for user", "user", user.ID, "error", err)
return
}
@@ -154,12 +154,16 @@ func (srv *DurationService) RegenerateAll() {
}
}
func (srv *DurationService) DeleteByUser(user *models.User) error {
return srv.repository.DeleteByUser(user)
}
func (srv *DurationService) getCached(from, to time.Time, user *models.User, filters *models.Filters) (models.Durations, error) {
languageMappings, err := srv.LanguageMappingService.ResolveByUser(user.ID)
languageMappings, err := srv.languageMappingService.ResolveByUser(user.ID)
if err != nil {
return nil, err
}
durations, err := srv.durationRepository.GetAllWithinByFilters(from, to, user, srv.filtersToColumnMap(filters))
durations, err := srv.repository.GetAllWithinByFilters(from, to, user, srv.filtersToColumnMap(filters))
if err != nil {
return nil, err
}

View File

@@ -94,6 +94,7 @@ type IDurationService interface {
Get(time.Time, time.Time, *models.User, *models.Filters, *time.Duration, bool) (models.Durations, error)
Regenerate(*models.User, bool)
RegenerateAll()
DeleteByUser(*models.User) error
}
type ISummaryService interface {