From a3eb4719de519ca7f170a68b2d461c3483eb3835 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 3 Jul 2026 09:20:59 +0200 Subject: [PATCH] refactor(account): generalize RemoveEmailIdentity to RemoveIdentity(kind) Generalize the email-erase store op to any identity kind (with the same last-identity guard and, for email, the pending-confirmation cleanup) so the profile Unlink control can reuse it for Telegram/VK. RemoveEmailIdentity stays as a thin wrapper for the admin console. --- backend/internal/account/link.go | 42 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/backend/internal/account/link.go b/backend/internal/account/link.go index 08f3b32..9fa1b31 100644 --- a/backend/internal/account/link.go +++ b/backend/internal/account/link.go @@ -21,25 +21,25 @@ var ErrIdentityTaken = errors.New("account: identity already linked to another a // none, making it unreachable after logout. The admin email-erase refuses it. var ErrLastIdentity = errors.New("account: cannot remove the last identity") -// RemoveEmailIdentity deletes the account's email identity and any pending confirmations -// for it, freeing the address for reuse. It refuses when the email is the account's only -// identity (ErrLastIdentity) — that would leave the account unreachable — and returns -// ErrNotFound when the account has no email identity. It backs the admin console's -// "erase email" action. -func (s *Store) RemoveEmailIdentity(ctx context.Context, accountID uuid.UUID) error { +// RemoveIdentity deletes the account's identity of the given kind (and, for an email, +// any pending confirmations for it), freeing it for reuse. It refuses when that is the +// account's only identity (ErrLastIdentity) — which would leave the account +// unreachable — and returns ErrNotFound when the account has no identity of that kind. +// It backs the profile Unlink control and the admin "erase email" action. +func (s *Store) RemoveIdentity(ctx context.Context, accountID uuid.UUID, kind string) error { ids, err := s.Identities(ctx, accountID) if err != nil { return err } - hasEmail, others := false, 0 + hasKind, others := false, 0 for _, id := range ids { - if id.Kind == KindEmail { - hasEmail = true + if id.Kind == kind { + hasKind = true } else { others++ } } - if !hasEmail { + if !hasKind { return ErrNotFound } if others == 0 { @@ -48,21 +48,29 @@ func (s *Store) RemoveEmailIdentity(ctx context.Context, accountID uuid.UUID) er return withTx(ctx, s.db, func(tx *sql.Tx) error { delID := table.Identities.DELETE().WHERE( table.Identities.AccountID.EQ(postgres.UUID(accountID)). - AND(table.Identities.Kind.EQ(postgres.String(KindEmail))), + AND(table.Identities.Kind.EQ(postgres.String(kind))), ) if _, err := delID.ExecContext(ctx, tx); err != nil { - return fmt.Errorf("account: delete email identity %s: %w", accountID, err) + return fmt.Errorf("account: delete %s identity %s: %w", kind, accountID, err) } - delConf := table.EmailConfirmations.DELETE().WHERE( - table.EmailConfirmations.AccountID.EQ(postgres.UUID(accountID)), - ) - if _, err := delConf.ExecContext(ctx, tx); err != nil { - return fmt.Errorf("account: delete email confirmations %s: %w", accountID, err) + if kind == KindEmail { + delConf := table.EmailConfirmations.DELETE().WHERE( + table.EmailConfirmations.AccountID.EQ(postgres.UUID(accountID)), + ) + if _, err := delConf.ExecContext(ctx, tx); err != nil { + return fmt.Errorf("account: delete email confirmations %s: %w", accountID, err) + } } return nil }) } +// RemoveEmailIdentity erases the account's email identity. It backs the admin console's +// "erase email" action; the user-facing profile never unlinks email (it is changed). +func (s *Store) RemoveEmailIdentity(ctx context.Context, accountID uuid.UUID) error { + return s.RemoveIdentity(ctx, accountID, KindEmail) +} + // RequestLinkCode issues and mails a confirm-code for email to accountID, // replacing any prior pending code. Unlike RequestCode it never refuses up front // (taken or already-confirmed): possession of the address is the authorization for