added messages templates

This commit is contained in:
onysd 2026-09-01 14:40:06 +03:00
parent e8dc967e6a
commit 7cd1f64d0d
29 changed files with 1266 additions and 84 deletions

View file

@ -29,6 +29,26 @@ type Info struct {
// icon has been uploaded. Kept alongside Name/Description so Store can
// find the icon file without a directory listing.
IconExt string `json:"icon_ext,omitempty"`
// WelcomeMessagePhoneTemplate/WelcomeMessageEmailTemplate are raw
// admin-panel overrides for the login-notification message sent from
// the official system account (777000) on every completed phone/email
// sign-in -- see domain.ResolveWelcomeMessageTemplate. Empty means "not
// configured": the resolver falls through to the TELESRV_WELCOME_MESSAGE_*
// env var, then the compiled-in default. Deliberately stored raw (not
// pre-resolved), so a deployment that never touches the panel keeps
// tracking whatever the fallback currently is, including future changes
// to the compiled-in default.
WelcomeMessagePhoneTemplate string `json:"welcome_message_phone_template,omitempty"`
WelcomeMessageEmailTemplate string `json:"welcome_message_email_template,omitempty"`
// LoginCodeMessageTemplate is the raw admin-panel override for the
// 777000 login-code delivery message (see
// domain.ResolveLoginCodeMessageTemplate). Unlike the welcome-message
// templates above there is only one -- the message never varies by
// delivery channel. Empty means "not configured": the resolver falls
// through to the TELESRV_LOGIN_CODE_MESSAGE_TEMPLATE env var, then the
// compiled-in default. Stored raw, same "not pre-resolved" contract as
// the welcome-message overrides.
LoginCodeMessageTemplate string `json:"login_code_message_template,omitempty"`
}
// Store reads/writes Info and the icon file under a directory (typically
@ -80,6 +100,42 @@ func (s *Store) SetText(name, description string) error {
return s.save(info)
}
// SetWelcomeMessageTemplates updates the login-notification template
// overrides, preserving whatever name/description/icon is already
// configured. An empty string in either argument clears that method's
// override (falls back to the env var / compiled-in default -- see Info's
// field comments), following the same "empty means unset" convention as the
// rest of Info.
func (s *Store) SetWelcomeMessageTemplates(phone, email string) error {
info, err := s.Get()
if err != nil {
return err
}
info.WelcomeMessagePhoneTemplate = strings.TrimSpace(phone)
info.WelcomeMessageEmailTemplate = strings.TrimSpace(email)
return s.save(info)
}
// SetLoginCodeMessageTemplate updates the login-code delivery message's
// admin-panel override, preserving whatever else is already configured. An
// empty string clears the override (falls back to the env var / compiled-in
// default -- same "empty means unset" convention as the rest of Info).
// Unlike SetWelcomeMessageTemplates there is no per-method split: every
// login code, regardless of delivery channel, uses the same template.
//
// Callers must validate template with domain.ValidateLoginCodeMessageTemplate
// before calling this -- this method does not itself reject a template
// missing the {{code}} placeholder, since internal/identity does not depend
// on internal/domain (see the package doc comment).
func (s *Store) SetLoginCodeMessageTemplate(template string) error {
info, err := s.Get()
if err != nil {
return err
}
info.LoginCodeMessageTemplate = strings.TrimSpace(template)
return s.save(info)
}
// SetIcon replaces the icon file (removing any previous one under a
// different extension) and records its extension in identity.json.
// ext must include the leading dot (e.g. ".png").

View file

@ -93,3 +93,107 @@ func TestStorePreservesIconAcrossTextEdits(t *testing.T) {
t.Fatalf("icon lost after unrelated SetText: ext=%q ok=%v", ext, ok)
}
}
func TestStoreWelcomeMessageTemplatesRoundTrip(t *testing.T) {
s := NewStore(t.TempDir())
info, err := s.Get()
if err != nil {
t.Fatal(err)
}
if info.WelcomeMessagePhoneTemplate != "" || info.WelcomeMessageEmailTemplate != "" {
t.Fatalf("expected empty overrides before any write, got %+v", info)
}
if err := s.SetWelcomeMessageTemplates(" Custom phone template ", "Custom email template"); err != nil {
t.Fatal(err)
}
info, err = s.Get()
if err != nil {
t.Fatal(err)
}
if info.WelcomeMessagePhoneTemplate != "Custom phone template" || info.WelcomeMessageEmailTemplate != "Custom email template" {
t.Fatalf("got %+v", info)
}
// Clearing one override (empty string) must not disturb the other.
if err := s.SetWelcomeMessageTemplates("", "Custom email template"); err != nil {
t.Fatal(err)
}
info, err = s.Get()
if err != nil {
t.Fatal(err)
}
if info.WelcomeMessagePhoneTemplate != "" || info.WelcomeMessageEmailTemplate != "Custom email template" {
t.Fatalf("got %+v after clearing phone override", info)
}
}
func TestStoreLoginCodeMessageTemplateRoundTrip(t *testing.T) {
s := NewStore(t.TempDir())
info, err := s.Get()
if err != nil {
t.Fatal(err)
}
if info.LoginCodeMessageTemplate != "" {
t.Fatalf("expected empty override before any write, got %+v", info)
}
if err := s.SetLoginCodeMessageTemplate(" Custom code template {{code}} "); err != nil {
t.Fatal(err)
}
info, err = s.Get()
if err != nil {
t.Fatal(err)
}
if info.LoginCodeMessageTemplate != "Custom code template {{code}}" {
t.Fatalf("got %+v", info)
}
// Clearing (empty string) resets to "unset".
if err := s.SetLoginCodeMessageTemplate(""); err != nil {
t.Fatal(err)
}
info, err = s.Get()
if err != nil {
t.Fatal(err)
}
if info.LoginCodeMessageTemplate != "" {
t.Fatalf("expected override cleared, got %+v", info)
}
}
func TestStoreLoginCodeMessageTemplatePreservedAcrossTextEdits(t *testing.T) {
s := NewStore(t.TempDir())
if err := s.SetLoginCodeMessageTemplate("code tpl {{code}}"); err != nil {
t.Fatal(err)
}
if err := s.SetText("New Name", "New description"); err != nil {
t.Fatal(err)
}
info, err := s.Get()
if err != nil {
t.Fatal(err)
}
if info.LoginCodeMessageTemplate != "code tpl {{code}}" {
t.Fatalf("login code message template lost after unrelated SetText: %+v", info)
}
}
func TestStoreWelcomeMessageTemplatesPreservedAcrossTextEdits(t *testing.T) {
s := NewStore(t.TempDir())
if err := s.SetWelcomeMessageTemplates("phone tpl", "email tpl"); err != nil {
t.Fatal(err)
}
if err := s.SetText("New Name", "New description"); err != nil {
t.Fatal(err)
}
info, err := s.Get()
if err != nil {
t.Fatal(err)
}
if info.WelcomeMessagePhoneTemplate != "phone tpl" || info.WelcomeMessageEmailTemplate != "email tpl" {
t.Fatalf("welcome message templates lost after unrelated SetText: %+v", info)
}
}