diff --git a/backend/internal/account/email.go b/backend/internal/account/email.go index d65015d..102bd73 100644 --- a/backend/internal/account/email.go +++ b/backend/internal/account/email.go @@ -206,6 +206,11 @@ func (s *EmailService) ConfirmCode(ctx context.Context, accountID uuid.UUID, ema if err := s.store.confirmEmailIdentity(ctx, conf.id, accountID, addr, s.now()); err != nil { return Account{}, err } + // Binding the first confirmed email promotes a guest to a durable account, matching the + // link and deeplink flows (defence-in-depth: no confirmed-email path leaves is_guest set). + if err := s.store.ClearGuest(ctx, accountID); err != nil { + return Account{}, err + } return s.store.GetByID(ctx, accountID) } diff --git a/backend/internal/account/userlist.go b/backend/internal/account/userlist.go index 9103c9f..c9b7acd 100644 --- a/backend/internal/account/userlist.go +++ b/backend/internal/account/userlist.go @@ -26,12 +26,14 @@ type UserListItem struct { } // UserFilter narrows the admin user list: Robots selects robot accounts (otherwise the -// non-robot "people"); NameMask and ExternalIDMask are glob masks ('*' = any run, '?' = -// one char) matched case-insensitively against the display name / any identity's external -// id; EmailExact is a strict (exact) match against an account's email identity. An empty -// value means no filter on that field. +// non-robot "people"); Deleted selects tombstoned accounts (every other scope hides them); +// NameMask and ExternalIDMask are glob masks ('*' = any run, '?' = one char) matched +// case-insensitively against the display name / any identity's external id; EmailExact is a +// strict (exact) match against an account's email identity. An empty value means no filter +// on that field. type UserFilter struct { Robots bool + Deleted bool NameMask string ExternalIDMask string EmailExact string @@ -57,6 +59,12 @@ func (s *Store) IsRobot(ctx context.Context, accountID uuid.UUID) (bool, error) func userListWhere(f UserFilter) (string, []any) { args := []any{f.Robots} where := robotExists + ` = $1` + // The Deleted scope shows tombstoned accounts; every other scope hides them. + if f.Deleted { + where += ` AND a.deleted_at IS NOT NULL` + } else { + where += ` AND a.deleted_at IS NULL` + } if name := LikePattern(f.NameMask); name != "" { args = append(args, name) where += fmt.Sprintf(` AND a.display_name ILIKE $%d ESCAPE '\'`, len(args)) diff --git a/backend/internal/adminconsole/templates/pages/user_detail.gohtml b/backend/internal/adminconsole/templates/pages/user_detail.gohtml index f6efb5d..2d300d1 100644 --- a/backend/internal/adminconsole/templates/pages/user_detail.gohtml +++ b/backend/internal/adminconsole/templates/pages/user_detail.gohtml @@ -119,11 +119,11 @@ {{end}} -{{if not .Deleted}}{{if not .Guest}} +{{if not .Deleted}}
-{{end}}{{end}} +{{end}}