feat(channels): gate available channels behind feature switch (backend)
Add a DB-backed soft switch "available_channels_enabled" controlling the user-facing /channels/available endpoint and sidebar entry. Default to false (opt-in) — the feature stays invisible until an admin enables it under Admin Settings > Features. - domain_constants: SettingKeyAvailableChannelsEnabled - settings_view: AllSettings/PublicSettings + AvailableChannelsEnabled - setting_service: public+all read/write, seed default "false", GetAvailableChannelsRuntime helper (fail-closed on read error) - admin setting_handler: UpdateSettingsRequest *bool + update branch + audit diff entry - public setting_handler: expose via GET /api/v1/settings - available_channel_handler: featureEnabled() guard — returns empty list after auth when disabled (401 precedes the feature check to preserve existing behavior)
This commit is contained in:
@@ -254,6 +254,11 @@ const (
|
||||
// pre-filled when creating a new channel monitor from the admin UI. Range: [15, 3600].
|
||||
SettingKeyChannelMonitorDefaultIntervalSeconds = "channel_monitor_default_interval_seconds"
|
||||
|
||||
// SettingKeyAvailableChannelsEnabled is a DB-backed soft switch for the "Available Channels"
|
||||
// user-facing aggregate view. When false: user endpoint returns an empty list and the
|
||||
// sidebar entry is hidden. Defaults to false (opt-in feature).
|
||||
SettingKeyAvailableChannelsEnabled = "available_channels_enabled"
|
||||
|
||||
// =========================
|
||||
// Overload Cooldown (529)
|
||||
// =========================
|
||||
|
||||
@@ -452,6 +452,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
SettingKeyAccountQuotaNotifyEnabled,
|
||||
SettingKeyChannelMonitorEnabled,
|
||||
SettingKeyChannelMonitorDefaultIntervalSeconds,
|
||||
SettingKeyAvailableChannelsEnabled,
|
||||
}
|
||||
|
||||
settings, err := s.settingRepo.GetMultiple(ctx, keys)
|
||||
@@ -537,6 +538,8 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
|
||||
ChannelMonitorEnabled: !isFalseSettingValue(settings[SettingKeyChannelMonitorEnabled]),
|
||||
ChannelMonitorDefaultIntervalSeconds: parseChannelMonitorInterval(settings[SettingKeyChannelMonitorDefaultIntervalSeconds]),
|
||||
|
||||
AvailableChannelsEnabled: settings[SettingKeyAvailableChannelsEnabled] == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -595,6 +598,25 @@ func (s *SettingService) GetChannelMonitorRuntime(ctx context.Context) ChannelMo
|
||||
}
|
||||
}
|
||||
|
||||
// AvailableChannelsRuntime is the lightweight view of the available-channels feature
|
||||
// switch consumed by the user-facing handler.
|
||||
type AvailableChannelsRuntime struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// GetAvailableChannelsRuntime reads the available-channels feature switch directly
|
||||
// from the settings store. Fail-closed: on error returns Enabled=false, matching
|
||||
// the opt-in default (unknown ↔ disabled).
|
||||
func (s *SettingService) GetAvailableChannelsRuntime(ctx context.Context) AvailableChannelsRuntime {
|
||||
vals, err := s.settingRepo.GetMultiple(ctx, []string{SettingKeyAvailableChannelsEnabled})
|
||||
if err != nil {
|
||||
return AvailableChannelsRuntime{Enabled: false}
|
||||
}
|
||||
return AvailableChannelsRuntime{
|
||||
Enabled: vals[SettingKeyAvailableChannelsEnabled] == "true",
|
||||
}
|
||||
}
|
||||
|
||||
// SetOnUpdateCallback sets a callback function to be called when settings are updated
|
||||
// This is used for cache invalidation (e.g., HTML cache in frontend server)
|
||||
func (s *SettingService) SetOnUpdateCallback(callback func()) {
|
||||
@@ -1151,6 +1173,9 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
updates[SettingKeyChannelMonitorDefaultIntervalSeconds] = strconv.Itoa(v)
|
||||
}
|
||||
|
||||
// Available channels feature switch
|
||||
updates[SettingKeyAvailableChannelsEnabled] = strconv.FormatBool(settings.AvailableChannelsEnabled)
|
||||
|
||||
// Claude Code version check
|
||||
updates[SettingKeyMinClaudeCodeVersion] = settings.MinClaudeCodeVersion
|
||||
updates[SettingKeyMaxClaudeCodeVersion] = settings.MaxClaudeCodeVersion
|
||||
@@ -1700,6 +1725,9 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
||||
SettingKeyChannelMonitorEnabled: "true",
|
||||
SettingKeyChannelMonitorDefaultIntervalSeconds: "60",
|
||||
|
||||
// Available channels feature (default disabled; opt-in)
|
||||
SettingKeyAvailableChannelsEnabled: "false",
|
||||
|
||||
// Claude Code version check (default: empty = disabled)
|
||||
SettingKeyMinClaudeCodeVersion: "",
|
||||
SettingKeyMaxClaudeCodeVersion: "",
|
||||
@@ -2008,6 +2036,9 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
settings[SettingKeyChannelMonitorDefaultIntervalSeconds],
|
||||
)
|
||||
|
||||
// Available channels feature (default: disabled; strict true)
|
||||
result.AvailableChannelsEnabled = settings[SettingKeyAvailableChannelsEnabled] == "true"
|
||||
|
||||
// Claude Code version check
|
||||
result.MinClaudeCodeVersion = settings[SettingKeyMinClaudeCodeVersion]
|
||||
result.MaxClaudeCodeVersion = settings[SettingKeyMaxClaudeCodeVersion]
|
||||
|
||||
@@ -129,6 +129,9 @@ type SystemSettings struct {
|
||||
ChannelMonitorEnabled bool `json:"channel_monitor_enabled"`
|
||||
ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"`
|
||||
|
||||
// Available Channels feature (user-facing aggregate view)
|
||||
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
|
||||
|
||||
// Claude Code version check
|
||||
MinClaudeCodeVersion string
|
||||
MaxClaudeCodeVersion string
|
||||
@@ -217,6 +220,9 @@ type PublicSettings struct {
|
||||
// Channel Monitor feature
|
||||
ChannelMonitorEnabled bool `json:"channel_monitor_enabled"`
|
||||
ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"`
|
||||
|
||||
// Available Channels feature (user-facing aggregate view)
|
||||
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
|
||||
}
|
||||
|
||||
type WeChatConnectOAuthConfig struct {
|
||||
|
||||
Reference in New Issue
Block a user