fix(account): seed the email account display name from the local part
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 1m3s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 1m3s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
An email account was provisioned with no display name (unlike Telegram/VK, which seed one), so an email login showed an empty name. Seed it from the email's local part (before '@', trimmed and capped to the column width) on first contact; the user can rename it later. Only new accounts are seeded — an existing account's name is never overwritten.
This commit is contained in:
@@ -122,21 +122,35 @@ func (s *Store) ProvisionByIdentity(ctx context.Context, kind, externalID string
|
|||||||
|
|
||||||
// ProvisionEmail returns the account owning the email identity externalID, creating
|
// ProvisionEmail returns the account owning the email identity externalID, creating
|
||||||
// it on first contact with browserTZ — the client's detected "±HH:MM" UTC offset —
|
// it on first contact with browserTZ — the client's detected "±HH:MM" UTC offset —
|
||||||
// seeded into its time zone and language seeded from the client's UI language. Like
|
// seeded into its time zone, language seeded from the client's UI language, and its
|
||||||
|
// display name seeded from the email's local part (so it is not left nameless). Like
|
||||||
// ProvisionByIdentity it is race-safe and leaves an existing account untouched, so a
|
// ProvisionByIdentity it is race-safe and leaves an existing account untouched, so a
|
||||||
// returning user's saved zone and language are never overwritten. The email account is
|
// returning user's saved zone, language and name are never overwritten. The email account is
|
||||||
// created here (the code-request step), not at the later login, so this is where its
|
// created here (the code-request step), not at the later login, so this is where its
|
||||||
// zone and language are seeded. It is created flagged is_guest with an unconfirmed
|
// zone and language are seeded. It is created flagged is_guest with an unconfirmed
|
||||||
// email identity: an abandoned, never-confirmed login is then reaped like any guest,
|
// email identity: an abandoned, never-confirmed login is then reaped like any guest,
|
||||||
// freeing the reserved address, and confirming the code clears the guest flag.
|
// freeing the reserved address, and confirming the code clears the guest flag.
|
||||||
func (s *Store) ProvisionEmail(ctx context.Context, externalID, browserTZ, language string) (Account, error) {
|
func (s *Store) ProvisionEmail(ctx context.Context, externalID, browserTZ, language string) (Account, error) {
|
||||||
return s.provision(ctx, KindEmail, externalID, provisionSeed{
|
return s.provision(ctx, KindEmail, externalID, provisionSeed{
|
||||||
|
displayName: emailDisplayName(externalID),
|
||||||
timeZone: seedZone(browserTZ),
|
timeZone: seedZone(browserTZ),
|
||||||
preferredLanguage: supportedLanguage(language),
|
preferredLanguage: supportedLanguage(language),
|
||||||
isGuest: true,
|
isGuest: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emailDisplayName derives a display name from an email address — the local part
|
||||||
|
// before '@', trimmed and capped to the column width — so a new email account is not
|
||||||
|
// left nameless. It is only the first-contact seed; the user can rename it later.
|
||||||
|
func emailDisplayName(email string) string {
|
||||||
|
local, _, _ := strings.Cut(email, "@")
|
||||||
|
local = strings.TrimSpace(local)
|
||||||
|
if r := []rune(local); len(r) > maxDisplayName {
|
||||||
|
local = strings.TrimRight(string(r[:maxDisplayName]), " ")
|
||||||
|
}
|
||||||
|
return local
|
||||||
|
}
|
||||||
|
|
||||||
// supportedLanguage returns code normalised to a supported UI language ("en" or
|
// supportedLanguage returns code normalised to a supported UI language ("en" or
|
||||||
// "ru"), or "" when it maps to neither, so a new account keeps the 'en' default. It
|
// "ru"), or "" when it maps to neither, so a new account keeps the 'en' default. It
|
||||||
// accepts region-tagged codes ("ru-RU").
|
// accepts region-tagged codes ("ru-RU").
|
||||||
|
|||||||
@@ -373,3 +373,24 @@ func TestConfirmByTokenLinkMerge(t *testing.T) {
|
|||||||
t.Fatalf("merge result = %+v, want NeedsMerge with owner=%s", res, owner)
|
t.Fatalf("merge result = %+v, want NeedsMerge with owner=%s", res, owner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEmailAccountSeedsDisplayName seeds a new email account's display name from the
|
||||||
|
// email's local part, so an email login is not left nameless.
|
||||||
|
func TestEmailAccountSeedsDisplayName(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
store := account.NewStore(testDB)
|
||||||
|
svc := account.NewEmailService(store, &capturingMailer{}, "https://erudit-game.ru")
|
||||||
|
local := "kaya-" + uuid.NewString()[:8]
|
||||||
|
|
||||||
|
id, err := svc.RequestLoginCode(ctx, local+"@example.com", "", "en")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("request login: %v", err)
|
||||||
|
}
|
||||||
|
acc, err := store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get: %v", err)
|
||||||
|
}
|
||||||
|
if acc.DisplayName != local {
|
||||||
|
t.Errorf("display name = %q, want the email local part %q", acc.DisplayName, local)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user