diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 440e2f74..1c1bd475 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -5,6 +5,7 @@ import "net/http" const ( DefaultOrgName = "My Organization" PrivateCaptcha = "Private Captcha" + PrivateCaptchaTeam = "Private Captcha Team" StageDev = "dev" StageStaging = "staging" StageTest = "test" diff --git a/pkg/common/utils.go b/pkg/common/utils.go index 73d0a853..81afd8ac 100644 --- a/pkg/common/utils.go +++ b/pkg/common/utils.go @@ -9,6 +9,7 @@ import ( "net/url" "strings" "time" + "unicode" "github.com/jpillora/backoff" ) @@ -98,6 +99,52 @@ func ParseBoolean(value string) bool { } } +func containsAlphabetic(s string) bool { + for _, r := range s { + if unicode.IsLetter(r) { + return true + } + } + return false +} + +func onlyAlphabetic(s string) bool { + for _, r := range s { + if !unicode.IsLetter(r) { + return false + } + } + return true +} + +func isLowerCase(s string) bool { + for _, r := range s { + if !unicode.IsLower(r) { + return false + } + } + + return true +} + +func GuessFirstName(username string) string { + parts := strings.Fields(username) + + for _, p := range parts { + if containsAlphabetic(p) { + if onlyAlphabetic(p) && isLowerCase(p) { + runes := []rune(p) + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) + } + + return p + } + } + + return username +} + func ChunkedCleanup(ctx context.Context, minInterval, maxInterval time.Duration, defaultChunkSize int, deleter func(context.Context, time.Time, int) int) { b := &backoff.Backoff{ Min: minInterval, diff --git a/pkg/common/utils_test.go b/pkg/common/utils_test.go index 8817bf16..07f1cbfd 100644 --- a/pkg/common/utils_test.go +++ b/pkg/common/utils_test.go @@ -123,3 +123,29 @@ func TestSubDomain(t *testing.T) { }) } } + +func TestGuessFirstName(t *testing.T) { + tests := []struct { + username string + expected string + }{ + {"john doe", "John"}, + {"123 alice 456", "Alice"}, + {"bob123 charlie", "bob123"}, + {"david", "David"}, + {"123 456 789", "123 456 789"}, + {"", ""}, + {" ", " "}, + {"!@# john_doe", "john_doe"}, + {"___123___", "___123___"}, + {" emily rose ", "Emily"}, + {"123 456abc", "456abc"}, + } + + for _, tt := range tests { + actual := GuessFirstName(tt.username) + if actual != tt.expected { + t.Errorf("GuessFirstName(%q) = %q; want %q", tt.username, actual, tt.expected) + } + } +} diff --git a/pkg/maintenance/email.go b/pkg/maintenance/email.go index cd11d87f..be9aae32 100644 --- a/pkg/maintenance/email.go +++ b/pkg/maintenance/email.go @@ -254,7 +254,7 @@ func (j *UserEmailNotificationsJob) processNotificationsChunk(ctx context.Contex Subject: un.Subject, EmailTo: n.Email, EmailFrom: emailFrom, - NameFrom: common.PrivateCaptcha, + NameFrom: common.PrivateCaptchaTeam, ReplyTo: replyToEmail, HTMLBody: htmlBodyTpl.String(), TextBody: textBodyTpl.String(), diff --git a/pkg/portal/email.go b/pkg/portal/email.go index dc15b6b1..21ba7c7f 100644 --- a/pkg/portal/email.go +++ b/pkg/portal/email.go @@ -93,7 +93,7 @@ func (pm *PortalMailer) SendTwoFactor(ctx context.Context, email string, code in Subject: fmt.Sprintf("[%s] Your verification code is %v", common.PrivateCaptcha, data.Code), EmailTo: email, EmailFrom: pm.EmailFrom.Value(), - NameFrom: common.PrivateCaptcha, + NameFrom: common.PrivateCaptchaTeam, } clog := slog.With("email", email, "code", data.Code) @@ -151,7 +151,7 @@ func (pm *PortalMailer) SendWelcome(ctx context.Context, email, name string) err Subject: "Welcome to Private Captcha", EmailTo: email, EmailFrom: pm.EmailFrom.Value(), - NameFrom: common.PrivateCaptcha, + NameFrom: common.PrivateCaptchaTeam, ReplyTo: pm.ReplyToEmail.Value(), } diff --git a/pkg/portal/jobs.go b/pkg/portal/jobs.go index 2af935f1..567e0646 100644 --- a/pkg/portal/jobs.go +++ b/pkg/portal/jobs.go @@ -36,5 +36,5 @@ func (j *onboardUserJob) InitialPause() time.Duration { } func (j *onboardUserJob) RunOnce(ctx context.Context) error { - return j.mailer.SendWelcome(ctx, j.user.Email, j.user.Name) + return j.mailer.SendWelcome(ctx, j.user.Email, common.GuessFirstName(j.user.Name)) }