package account import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "scrabble/backend/internal/postgres/jet/backend/table" ) // Reasons recorded on a retained_identities row: what detached the credential from its // account. The row is written just before the live identities row is removed, preserving // the legal dossier (which email/vk/tg was linked, and when) even as the identity frees // for reuse. See docs/ARCHITECTURE.md ยง9.1. const ( retainUnlink = "unlink" retainChange = "change" retainDelete = "delete" ) // retainIdentityTx appends a retention-journal row for one identity being detached, inside // tx. linkedAt is the identity's original creation time; detached_at defaults to now(). It // must run in the same transaction as the identity removal, so the dossier and the live // state can never diverge. func retainIdentityTx(ctx context.Context, tx *sql.Tx, accountID uuid.UUID, kind, externalID string, confirmed bool, linkedAt time.Time, reason string) error { id, err := uuid.NewV7() if err != nil { return fmt.Errorf("account: new retained id: %w", err) } ins := table.RetainedIdentities.INSERT( table.RetainedIdentities.RetainedID, table.RetainedIdentities.AccountID, table.RetainedIdentities.Kind, table.RetainedIdentities.ExternalID, table.RetainedIdentities.Confirmed, table.RetainedIdentities.LinkedAt, table.RetainedIdentities.Reason, ).VALUES(id, accountID, kind, externalID, confirmed, linkedAt, reason) if _, err := ins.ExecContext(ctx, tx); err != nil { return fmt.Errorf("account: retain identity (%s, %s): %w", kind, externalID, err) } return nil }