feat: change email, export my data, delete account (with data anonymization)

This commit is contained in:
Florian Sylvain committed 2026-10-30 20:19:07 +01:00
1 parent bb191c389a
commit 10a66cd979
123 files changed
+10789 -76

No files matched your search

@@ -242,6 +242,80 @@ func (u *UserRepository) UpdateDisplayName(userId uint32, displayName string) (d
return mappers.UserToDomain(localUser), nil
}
func (u *UserRepository) IsEmailInUse(email string, exceptUserId uint32) (bool, error) {
return isEmailInUse(u.db, email, exceptUserId)
}
func isEmailInUse(db *gorm.DB, email string, exceptUserId uint32) (bool, error) {
var holders int64
err := db.Model(&entity.User{}).Where("LOWER(email) = LOWER(?) AND id <> ?", email, exceptUserId).Count(&holders).Error
return holders > 0, err
}
func (u *UserRepository) SetPendingEmailChange(userId uint32, pendingEmail string, code string, expiration time.Time) (domainUser.User, error) {
return u.updatePendingEmailChange(userId, map[string]any{
"pending_email": mappers.DomainToNullableString(pendingEmail),
"email_change_code": mappers.DomainToNullableString(code),
"email_change_expiration": expiration,
})
}
func (u *UserRepository) ClearPendingEmailChange(userId uint32) (domainUser.User, error) {
return u.updatePendingEmailChange(userId, clearedEmailChange())
}
func clearedEmailChange() map[string]any {
return map[string]any{
"pending_email": nil,
"email_change_code": nil,
"email_change_expiration": time.Time{},
}
}
func (u *UserRepository) updatePendingEmailChange(userId uint32, columns map[string]any) (domainUser.User, error) {
result := u.db.Model(&entity.User{}).Where("id = ?", userId).Updates(columns)
if result.Error != nil {
return domainUser.User{}, result.Error
}
return u.Get(userId)
}
func (u *UserRepository) ConfirmEmailChange(userId uint32, pendingEmail string, code string, now time.Time) (domainUser.User, error) {
taken, err := isEmailInUse(u.db, pendingEmail, userId)
if err != nil {
return domainUser.User{}, err
}
columns := clearedEmailChange()
if !taken {
columns["email"] = pendingEmail
}
result := pendingChangeOf(u.db, userId, pendingEmail, code).Where("email_change_expiration > ?", now).Updates(columns)
if result.Error != nil && isUniqueViolation(result.Error) {
return domainUser.User{}, u.refuseTakenAddress(userId, pendingEmail, code)
}
if result.Error != nil {
return domainUser.User{}, result.Error
}
if result.RowsAffected == 0 {
return domainUser.User{}, domainUser.ErrInvalidEmailChangeCode
}
if taken {
return domainUser.User{}, domainUser.ErrEmailUnavailable
}
return u.Get(userId)
}
func (u *UserRepository) refuseTakenAddress(userId uint32, pendingEmail string, code string) error {
if err := pendingChangeOf(u.db, userId, pendingEmail, code).Updates(clearedEmailChange()).Error; err != nil {
return err
}
return domainUser.ErrEmailUnavailable
}
func pendingChangeOf(db *gorm.DB, userId uint32, pendingEmail string, code string) *gorm.DB {
return db.Model(&entity.User{}).Where("id = ? AND pending_email = ? AND email_change_code = ?", userId, pendingEmail, code)
}
func (u *UserRepository) UpdateRole(id uint32, from uint32, to uint32, assignedBy uint32) (domainUser.User, error) {
err := u.db.Transaction(func(tx *gorm.DB) error {
query := tx.Model(&entity.User{}).Where("id = ? AND EXISTS (SELECT 1 FROM roles WHERE id = ?)", id, to)