fix: isolate deletion notifications into per-user transactions

On Postgres, a failed operation puts the transaction in an error state
where subsequent operations fail. The previous loop with continue would
keep trying to use a broken transaction. Each user now gets its own
transaction so a single notification failure doesn't affect others.
This commit is contained in:
kolaente 2026-02-24 13:13:58 +01:00
parent 312648d7d6
commit eea59c33c7
3 changed files with 30 additions and 21 deletions

View File

@ -17,6 +17,7 @@
package user
import (
"fmt"
"time"
"code.vikunja.io/api/pkg/cron"
@ -50,6 +51,9 @@ func notifyUsersScheduledForDeletion() {
return
}
// Close the read-only session before processing each user in its own transaction.
s.Close()
log.Debugf("Found %d users scheduled for deletion to notify", len(users))
for _, user := range users {
@ -67,13 +71,22 @@ func notifyUsersScheduledForDeletion() {
log.Debugf("Notifying user %d of the deletion of their account...", user.ID)
err = notifications.Notify(user, &AccountDeletionNotification{
if err := notifyUserOfDeletion(user, number); err != nil {
log.Errorf("Could not process deletion notification for user %d: %s", user.ID, err)
}
}
}
func notifyUserOfDeletion(user *User, number int) error {
s := db.NewSession()
defer s.Close()
err := notifications.Notify(user, &AccountDeletionNotification{
User: user,
NotificationNumber: number,
}, s)
if err != nil {
log.Errorf("Could not notify user %d of their deletion: %s", user.ID, err)
continue
return fmt.Errorf("could not notify user: %w", err)
}
user.DeletionLastReminderSent = time.Now()
@ -81,13 +94,10 @@ func notifyUsersScheduledForDeletion() {
Cols("deletion_last_reminder_sent").
Update(user)
if err != nil {
log.Errorf("Could update user %d last deletion reminder sent date: %s", user.ID, err)
}
return fmt.Errorf("could not update last deletion reminder sent date: %w", err)
}
if err := s.Commit(); err != nil {
log.Errorf("Could not commit user deletion notifications: %s", err)
}
return s.Commit()
}
// RequestDeletion creates a user deletion confirm token and sends a notification to the user

View File

@ -93,7 +93,6 @@ func ResetPassword(s *xorm.Session, reset *PasswordReset) (userID int64, err err
return
}
// PasswordTokenRequest defines the request format for password reset resqest
type PasswordTokenRequest struct {
Email string `json:"email" valid:"email,length(0|250)" maxLength:"250"`