diff --git a/backend/repository/recipient.go b/backend/repository/recipient.go index bafc2d8..a7fbb5b 100644 --- a/backend/repository/recipient.go +++ b/backend/repository/recipient.go @@ -434,45 +434,6 @@ func (r *Recipient) GetOrphaned( return result, nil } -// DeleteAllOrphaned deletes all recipients that are not in any group -func (r *Recipient) DeleteAllOrphaned( - ctx context.Context, - companyID *uuid.UUID, -) (int64, error) { - // build optimized LEFT JOIN delete query for orphaned recipients - var companyFilter string - var args []interface{} - - if companyID != nil { - companyFilter = fmt.Sprintf("AND r.company_id = ?") - args = append(args, companyID) - } else { - companyFilter = fmt.Sprintf("AND r.company_id IS NULL") - } - - // use raw SQL for optimized LEFT JOIN delete - query := fmt.Sprintf(` - DELETE FROM %s - WHERE id IN ( - SELECT r.id FROM %s r - LEFT JOIN %s rgr ON r.id = rgr.recipient_id - WHERE rgr.recipient_id IS NULL %s - )`, - database.RECIPIENT_TABLE, - database.RECIPIENT_TABLE, - database.RECIPIENT_GROUP_RECIPIENT_TABLE, - companyFilter, - ) - - result := r.DB.Exec(query, args...) - - if result.Error != nil { - return 0, result.Error - } - - return result.RowsAffected, nil -} - // GetByID gets a recipient by id func (r *Recipient) GetByID( ctx context.Context, diff --git a/backend/service/recipient.go b/backend/service/recipient.go index 0db3b6e..3d83729 100644 --- a/backend/service/recipient.go +++ b/backend/service/recipient.go @@ -437,15 +437,30 @@ func (r *Recipient) DeleteAllOrphaned( r.AuditLogNotAuthorized(ae) return 0, errs.ErrAuthorizationFailed } - // delete orphaned recipients - count, err := r.RecipientRepository.DeleteAllOrphaned( + + // get all orphaned recipients + orphanedRecipients, err := r.RecipientRepository.GetOrphaned( ctx, companyID, + &repository.RecipientOption{}, // no pagination, get all ) if err != nil { - r.Logger.Errorw("failed to delete orphaned recipients - failed to delete orphaned recipients", "error", err) + r.Logger.Errorw("failed to get orphaned recipients", "error", err) return 0, errs.Wrap(err) } + + // delete each orphaned recipient using core deletion logic + var count int64 + for _, recipient := range orphanedRecipients.Rows { + recipientID := recipient.ID.MustGet() + err = r.deleteRecipientByID(ctx, &recipientID) + if err != nil { + r.Logger.Errorw("failed to delete orphaned recipient", "error", err, "recipientID", recipientID.String()) + return count, errs.Wrap(err) + } + count++ + } + ae.Details["count"] = count r.AuditLogAuthorized(ae) return count, nil @@ -686,26 +701,13 @@ func getStringFromOptional(field nullable.Nullable[vo.OptionalString127]) string return "" } -// Delete deletes a recipient -func (r *Recipient) DeleteByID( +// deleteRecipientByID is the core deletion logic without permission checks or audit logging +func (r *Recipient) deleteRecipientByID( ctx context.Context, - session *model.Session, id *uuid.UUID, ) error { - ae := NewAuditEvent("Recipient.DeleteByID", session) - ae.Details["id"] = id.String() - // check permissions - isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) - if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { - r.LogAuthError(err) - return err - } - if !isAuthorized { - r.AuditLogNotAuthorized(ae) - return errs.ErrAuthorizationFailed - } // remove recipient from all groups - err = r.RecipientGroupRepository.RemoveRecipientByIDFromAllGroups(ctx, id) + err := r.RecipientGroupRepository.RemoveRecipientByIDFromAllGroups(ctx, id) if err != nil { r.Logger.Errorw("failed to delete recipient - failed to remove recipient from all groups", "error", err, @@ -758,7 +760,34 @@ func (r *Recipient) DeleteByID( r.Logger.Errorw("failed to delete recipient - failed to delete recipient", "error", err) return err } - r.AuditLogAuthorized(ae) return nil } + +// DeleteByID deletes a recipient +func (r *Recipient) DeleteByID( + ctx context.Context, + session *model.Session, + id *uuid.UUID, +) error { + ae := NewAuditEvent("Recipient.DeleteByID", session) + ae.Details["id"] = id.String() + // check permissions + isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) + if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { + r.LogAuthError(err) + return err + } + if !isAuthorized { + r.AuditLogNotAuthorized(ae) + return errs.ErrAuthorizationFailed + } + + err = r.deleteRecipientByID(ctx, id) + if err != nil { + return err + } + + r.AuditLogAuthorized(ae) + return nil +}