Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/screenshots/user-id-display.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 27 additions & 2 deletions backend/internal/app/settings_composite_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ func newCompositeSettingsHTTPFixture(repo settingscore.Repository, cfg *config.C
}

// TestSettingsFieldOwnership enumerates static participants and the flattened
// update request type. It checks unique ownership, all 287 request fields are
// update request type. It checks unique ownership, all request fields are
// represented, and no participant declares an extra field outside that request.
func TestSettingsFieldOwnership(t *testing.T) {
defaults := scheduler.DefaultAdminSettingsDefaults()
Expand Down Expand Up @@ -477,7 +477,7 @@ func TestSettingsFieldOwnership(t *testing.T) {
delete(owners, name)
count++
}
require.Equal(t, 288, count, "HTTP 字段变化需要同步字段登记")
require.Equal(t, 289, count, "HTTP 字段变化需要同步字段登记")
require.Empty(t, owners, "参与者不应声明不存在的输入字段")
}

Expand Down Expand Up @@ -2271,3 +2271,28 @@ func TestSettingsCoverAtomicSave(t *testing.T) {
require.Empty(t, repo.values[site.SettingKeySiteShareImage])
require.Empty(t, site.ReadShareImageMetadata(repo.values[site.SettingKeySiteShareImageMeta]).Hash)
}

// TestSettingsShowUserIDDefaultsAndPartialUpdates exercises the display switch through
// the combined settings handler. It protects the disabled default, explicit updates,
// and retention when an unrelated save omits the field.
func TestSettingsShowUserIDDefaultsAndPartialUpdates(t *testing.T) {
h, repo := newStepUpSwitchTestHandler(t, map[string]string{})
response := doUpdateSettings(t, h, map[string]any{}, nil)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
require.Contains(t, response.Body.String(), `"show_user_id":false`)

response = doUpdateSettings(t, h, map[string]any{"show_user_id": true}, nil)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
require.Equal(t, "true", repo.values[site.SettingKeyShowUserID])
require.Contains(t, response.Body.String(), `"show_user_id":true`)

response = doUpdateSettings(t, h, map[string]any{"contact_info": "support@example.test"}, nil)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
require.Equal(t, "true", repo.values[site.SettingKeyShowUserID])
require.Contains(t, response.Body.String(), `"show_user_id":true`)

response = doUpdateSettings(t, h, map[string]any{"show_user_id": false}, nil)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
require.Equal(t, "false", repo.values[site.SettingKeyShowUserID])
require.Contains(t, response.Body.String(), `"show_user_id":false`)
}
3 changes: 3 additions & 0 deletions backend/internal/settings/composite/site_projection.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (s *Snapshot) SiteAdminSettings() site.AdminSettings {
FooterText: s.FooterText,
FrontendURL: s.FrontendURL,
HideCcsImportButton: s.HideCcsImportButton,
ShowUserID: s.ShowUserID,
HomeContent: s.HomeContent,
HomeFeaturedModels: s.HomeFeaturedModels,
LoginAgreementDocuments: s.LoginAgreementDocuments,
Expand Down Expand Up @@ -58,6 +59,7 @@ func (s *Snapshot) ApplySiteAdminSettings(value site.AdminSettings) {
s.FooterText = value.FooterText
s.FrontendURL = value.FrontendURL
s.HideCcsImportButton = value.HideCcsImportButton
s.ShowUserID = value.ShowUserID
s.HomeContent = value.HomeContent
s.HomeFeaturedModels = value.HomeFeaturedModels
s.LoginAgreementDocuments = value.LoginAgreementDocuments
Expand Down Expand Up @@ -90,6 +92,7 @@ func (s *Snapshot) ApplySiteAdminReadSettings(value *site.AdminReadSettings) {
s.FooterText = value.FooterText
s.FrontendURL = value.FrontendURL
s.HideCcsImportButton = value.HideCcsImportButton
s.ShowUserID = value.ShowUserID
s.HomeContent = value.HomeContent
s.HomeFeaturedModels = value.HomeFeaturedModels
s.LoginAgreementDocuments = value.LoginAgreementDocuments
Expand Down
1 change: 1 addition & 0 deletions backend/internal/settings/composite/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ type Snapshot struct {
DocURL string // source documentation destination text
HomeContent string // source custom homepage content
HideCcsImportButton bool // CCS import control visibility switch
ShowUserID bool // User ID display switch.
PurchaseSubscriptionEnabled bool // subscription purchase entry switch
PurchaseSubscriptionURL string // subscription purchase destination address
TableDefaultPageSize int // default table row count
Expand Down
3 changes: 3 additions & 0 deletions backend/internal/settings/httpapi/composite_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ func DiffSettings(before *composite.Snapshot, after *composite.Snapshot, beforeA
if before.HideCcsImportButton != after.HideCcsImportButton {
changed = append(changed, "hide_ccs_import_button")
}
if before.ShowUserID != after.ShowUserID {
changed = append(changed, "show_user_id")
}
if before.DefaultConcurrency != after.DefaultConcurrency {
changed = append(changed, "default_concurrency")
}
Expand Down
1 change: 1 addition & 0 deletions backend/internal/settings/httpapi/composite_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func buildSystemSettingsResponse(
DocURL: settings.DocURL,
HomeContent: settings.HomeContent,
HideCcsImportButton: settings.HideCcsImportButton,
ShowUserID: settings.ShowUserID,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
TableDefaultPageSize: settings.TableDefaultPageSize,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/settings/httpapi/composite_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@ func (h *Handler) UpdateSettings(c *gin.Context) {
DocURL: req.DocURL,
HomeContent: req.HomeContent,
HideCcsImportButton: req.HideCcsImportButton,
ShowUserID: boolValueOrDefault(req.ShowUserID, previousSettings.ShowUserID),
PurchaseSubscriptionEnabled: purchaseEnabled,
PurchaseSubscriptionURL: purchaseURL,
TableDefaultPageSize: req.TableDefaultPageSize,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/settings/httpapi/dto/settings_views.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ type SystemSettings struct {
DocURL string `json:"doc_url"` // source documentation destination text
HomeContent string `json:"home_content"` // source custom homepage content
HideCcsImportButton bool `json:"hide_ccs_import_button"` // CCS import control visibility switch
ShowUserID bool `json:"show_user_id"` // User ID display switch.
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` // subscription purchase entry switch
PurchaseSubscriptionURL string `json:"purchase_subscription_url"` // subscription purchase destination address
TableDefaultPageSize int `json:"table_default_page_size"` // default table row count
Expand Down
3 changes: 3 additions & 0 deletions backend/internal/settings/httpapi/dto/update_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ type UpdateSettingsRequest struct {
DocURL string `json:"doc_url"` // source documentation destination text
HomeContent string `json:"home_content"` // source custom homepage content
HideCcsImportButton bool `json:"hide_ccs_import_button"` // CCS import control visibility switch
// ShowUserID updates account identifier visibility when supplied; nil leaves
// the stored switch unchanged during unrelated administrator settings updates.
ShowUserID *bool `json:"show_user_id"` // User ID display switch.
// PurchaseSubscriptionEnabled and PurchaseSubscriptionURL use the prior
// Snapshot when nil. Enabled purchase requires a nonempty absolute HTTP(S) URL,
// while supplied URLs are validated even when the entry is disabled.
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/site/admin_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type AdminSettings struct {
FooterText string `json:"footer_text"` // additional footer text
FrontendURL string `json:"frontend_url"` // deployment frontend address
HideCcsImportButton bool `json:"hide_ccs_import_button"` // hide import action
ShowUserID bool `json:"show_user_id"` // User ID display switch.
HomeContent string `json:"home_content"` // homepage HTML or link
HomeFeaturedModels string `json:"home_featured_models"` // featured model JSON
// LoginAgreementDocuments supplies the agreement list; marshaling normalizes
Expand Down Expand Up @@ -103,6 +104,7 @@ func PrepareAdminSettings(settings *AdminSettings) (map[string]string, error) {
updates[SettingKeyDocURL] = settings.DocURL
updates[SettingKeyHomeContent] = settings.HomeContent
updates[SettingKeyHideCcsImportButton] = strconv.FormatBool(settings.HideCcsImportButton)
updates[SettingKeyShowUserID] = strconv.FormatBool(settings.ShowUserID)
updates[SettingKeyPurchaseSubscriptionEnabled] = strconv.FormatBool(settings.PurchaseSubscriptionEnabled)
updates[SettingKeyPurchaseSubscriptionURL] = strings.TrimSpace(settings.PurchaseSubscriptionURL)
tableDefaultPageSize, tablePageSizeOptions := NormalizeTablePreferences(
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/site/admin_settings_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type AdminReadSettings struct {
FooterText string // additional footer text
FrontendURL string // deployment frontend address
HideCcsImportButton bool // hide import action
ShowUserID bool // User ID display switch.
HomeContent string // homepage HTML or link
HomeFeaturedModels string // featured model JSON
// LoginAgreementDocuments carries normalized stored documents; missing,
Expand Down Expand Up @@ -87,6 +88,7 @@ func ReadAdminSettings(settings map[string]string) *AdminReadSettings {
result.DocURL = settings[SettingKeyDocURL]
result.HomeContent = settings[SettingKeyHomeContent]
result.HideCcsImportButton = settings[SettingKeyHideCcsImportButton] == "true"
result.ShowUserID = settings[SettingKeyShowUserID] == "true"
result.PurchaseSubscriptionEnabled = settings[SettingKeyPurchaseSubscriptionEnabled] == "true"
result.PurchaseSubscriptionURL = strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL])
result.CustomMenuItems = settings[SettingKeyCustomMenuItems]
Expand Down
1 change: 1 addition & 0 deletions backend/internal/site/httpapi/dto/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type (
DocURL string `json:"doc_url"` // resolved documentation link
HomeContent string `json:"home_content"` // resolved homepage content
HideCcsImportButton bool `json:"hide_ccs_import_button"` // hide import action
ShowUserID bool `json:"show_user_id"` // User ID display switch.
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` // subscription purchase switch
PurchaseSubscriptionURL string `json:"purchase_subscription_url"` // resolved purchase destination
TableDefaultPageSize int `json:"table_default_page_size"` // default rows per page
Expand Down
1 change: 1 addition & 0 deletions backend/internal/site/httpapi/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func (h *PublicHandler) GetPublicSettings(c *gin.Context) {
DocURL: settings.DocURL,
HomeContent: settings.HomeContent,
HideCcsImportButton: settings.HideCcsImportButton,
ShowUserID: settings.ShowUserID,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
TableDefaultPageSize: settings.TableDefaultPageSize,
Expand Down
47 changes: 42 additions & 5 deletions backend/internal/site/httpapi/public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ import (
"github.com/TokenFlux/TokenRouter/internal/site"
)

// calendarPublicSource supplies empty settings so the test exercises the injected calendar.
type calendarPublicSource struct{}
// public_test.go checks the public settings API against the HTML injection path.
// Controlled display values and injected calendars verify that both transports
// expose the same feature decisions and timezone metadata before browsers render
// account controls.

// calendarPublicSource supplies a display switch alongside injected calendar settings.
type calendarPublicSource struct {
showUserID string // Stored display-switch value.
}

// TestPublicCalendarMatchesAPIAndInjection supplies calendars with distinct zone names and display labels.
// It verifies that API and HTML injection return the configured label and the
Expand Down Expand Up @@ -58,15 +65,45 @@ func TestPublicCalendarMatchesAPIAndInjection(t *testing.T) {
}
}

// LoadSitePublicInputs supplies empty display, auth, and usage inputs for calendar tests.
// LoadSitePublicInputs supplies display-switch values with empty auth and usage inputs.
// Each call allocates a fresh values map, keeping fixture data independent of
// the response service's language and navigation transformations.
func (calendarPublicSource) LoadSitePublicInputs(context.Context) (site.PublicInputs, error) {
return site.PublicInputs{Values: map[string]string{}}, nil
func (source calendarPublicSource) LoadSitePublicInputs(context.Context) (site.PublicInputs, error) {
return site.PublicInputs{Values: map[string]string{site.SettingKeyShowUserID: source.showUserID}}, nil
}

// PublicVersion returns the fixed source build label used by injection fixtures.
func (calendarPublicSource) PublicVersion() string { return "calendar-contract" }

// LoadSiteShareImage leaves this text-only fixture without an uploaded cover.
func (calendarPublicSource) LoadSiteShareImage(context.Context) (string, error) { return "", nil }

// TestPublicUserIDSwitchMatchesAPIAndInjection supplies missing, disabled, enabled,
// and invalid stored values. Both public transports must agree so initial HTML
// rendering and subsequent API refreshes expose the same account display policy.
func TestPublicUserIDSwitchMatchesAPIAndInjection(t *testing.T) {
for _, value := range []string{"", "false", "true", "invalid"} {
t.Run(value, func(t *testing.T) {
service := site.NewPublicService(calendarPublicSource{showUserID: value}, timezone.NewCalendar(time.UTC), "UTC")
handler := NewPublicHandler(service, "test")
response := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(response)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/settings/public", nil)
handler.GetPublicSettings(ctx)
require.Equal(t, http.StatusOK, response.Code)
var envelope struct {
Data map[string]any `json:"data"` // Public settings response.
}
require.NoError(t, json.Unmarshal(response.Body.Bytes(), &envelope))
require.Equal(t, value == "true", envelope.Data["show_user_id"])

injection, err := service.GetPublicSettingsForInjection(context.Background())
require.NoError(t, err)
encoded, err := json.Marshal(injection)
require.NoError(t, err)
var fields map[string]any
require.NoError(t, json.Unmarshal(encoded, &fields))
require.Equal(t, value == "true", fields["show_user_id"])
})
}
}
5 changes: 5 additions & 0 deletions backend/internal/site/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
SettingKeyFooterText = "footer_text"
SettingKeyForceEmailOnThirdPartySignup = "force_email_on_third_party_signup"
SettingKeyHideCcsImportButton = "hide_ccs_import_button"
SettingKeyShowUserID = "show_user_id"
SettingKeyHomeContent = "home_content"
SettingKeyHomeFeaturedModels = "home_featured_models"
SettingKeyInvitationCodeEnabled = "invitation_code_enabled"
Expand Down Expand Up @@ -306,6 +307,7 @@ func (s *PublicService) GetPublicSettings(ctx context.Context) (*PublicSettings,
DocURL: settings[SettingKeyDocURL],
HomeContent: settings[SettingKeyHomeContent],
HideCcsImportButton: settings[SettingKeyHideCcsImportButton] == "true",
ShowUserID: settings[SettingKeyShowUserID] == "true",
PurchaseSubscriptionEnabled: settings[SettingKeyPurchaseSubscriptionEnabled] == "true",
PurchaseSubscriptionURL: strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL]),
TableDefaultPageSize: tableDefaultPageSize,
Expand Down Expand Up @@ -632,6 +634,7 @@ func (s *PublicService) GetPublicSettingsForInjection(ctx context.Context) (any,
DocURL string `json:"doc_url,omitempty"` // resolved documentation link
HomeContent string `json:"home_content,omitempty"` // resolved homepage content
HideCcsImportButton bool `json:"hide_ccs_import_button"` // hide import action
ShowUserID bool `json:"show_user_id"` // User ID display switch.
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` // subscription purchase switch
PurchaseSubscriptionURL string `json:"purchase_subscription_url,omitempty"` // resolved purchase destination
TableDefaultPageSize int `json:"table_default_page_size"` // default rows per page
Expand Down Expand Up @@ -726,6 +729,7 @@ func (s *PublicService) GetPublicSettingsForInjection(ctx context.Context) (any,
DocURL: settings.DocURL,
HomeContent: settings.HomeContent,
HideCcsImportButton: settings.HideCcsImportButton,
ShowUserID: settings.ShowUserID,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
TableDefaultPageSize: settings.TableDefaultPageSize,
Expand Down Expand Up @@ -1030,6 +1034,7 @@ func PublicValueKeys() []string {
SettingKeyFooterText,
SettingKeyForceEmailOnThirdPartySignup,
SettingKeyHideCcsImportButton,
SettingKeyShowUserID,
SettingKeyHomeContent,
SettingKeyHomeFeaturedModels,
SettingKeyInvitationCodeEnabled,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/site/public_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func PublicInputKeys() []string {
"doc_url",
"home_content",
"hide_ccs_import_button",
"show_user_id",
"purchase_subscription_enabled",
"purchase_subscription_url",
"table_default_page_size",
Expand Down
3 changes: 3 additions & 0 deletions backend/internal/site/public_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type (
DocURL string // resolved documentation link
HomeContent string // resolved homepage content
HideCcsImportButton bool // hide import action
// ShowUserID is enabled when the stored switch equals true. Missing
// settings keep account identifiers hidden in the account menu and profile.
ShowUserID bool // User ID display switch.

PurchaseSubscriptionEnabled bool // subscription purchase switch
PurchaseSubscriptionURL string // resolved purchase destination
Expand Down
1 change: 1 addition & 0 deletions backend/internal/site/settings_participant.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func SettingsParticipant() settings.Participant {
SettingKeyFooterText,
SettingKeyFrontendURL,
SettingKeyHideCcsImportButton,
SettingKeyShowUserID,
SettingKeyHomeContent,
SettingKeyHomeFeaturedModels,
SettingKeyLoginAgreementDocuments,
Expand Down
Loading