package handler import ( "html" "net/http" "strings" "github.com/Wei-Shaw/sub2api/internal/handler/dto" "github.com/Wei-Shaw/sub2api/internal/pkg/response" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" ) // SettingHandler 公开设置处理器(无需认证) type SettingHandler struct { settingService *service.SettingService notificationEmailService *service.NotificationEmailService version string } // NewSettingHandler 创建公开设置处理器 func NewSettingHandler(settingService *service.SettingService, version string) *SettingHandler { return &SettingHandler{ settingService: settingService, version: version, } } // SetNotificationEmailService attaches the public notification email service without // changing the constructor signature used by existing tests. func (h *SettingHandler) SetNotificationEmailService(notificationEmailService *service.NotificationEmailService) { h.notificationEmailService = notificationEmailService } // GetPublicSettings 获取公开设置 // GET /api/v1/settings/public func (h *SettingHandler) GetPublicSettings(c *gin.Context) { settings, err := h.settingService.GetPublicSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.PublicSettings{ RegistrationEnabled: settings.RegistrationEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled, ForceEmailOnThirdPartySignup: settings.ForceEmailOnThirdPartySignup, RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist, PromoCodeEnabled: settings.PromoCodeEnabled, PasswordResetEnabled: settings.PasswordResetEnabled, InvitationCodeEnabled: settings.InvitationCodeEnabled, TotpEnabled: settings.TotpEnabled, LoginAgreementEnabled: settings.LoginAgreementEnabled, LoginAgreementMode: settings.LoginAgreementMode, LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt, LoginAgreementRevision: settings.LoginAgreementRevision, LoginAgreementDocuments: publicLoginAgreementDocumentsToDTO(settings.LoginAgreementDocuments), TurnstileEnabled: settings.TurnstileEnabled, TurnstileSiteKey: settings.TurnstileSiteKey, SiteName: settings.SiteName, SiteLogo: settings.SiteLogo, SiteSubtitle: settings.SiteSubtitle, APIBaseURL: settings.APIBaseURL, ContactInfo: settings.ContactInfo, DocURL: settings.DocURL, HomeContent: settings.HomeContent, HideCcsImportButton: settings.HideCcsImportButton, PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled, PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL, TableDefaultPageSize: settings.TableDefaultPageSize, TablePageSizeOptions: settings.TablePageSizeOptions, CustomMenuItems: dto.ParseUserVisibleMenuItems(settings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints), DingTalkOAuthEnabled: settings.DingTalkOAuthEnabled, LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled, WeChatOAuthEnabled: settings.WeChatOAuthEnabled, WeChatOAuthOpenEnabled: settings.WeChatOAuthOpenEnabled, WeChatOAuthMPEnabled: settings.WeChatOAuthMPEnabled, WeChatOAuthMobileEnabled: settings.WeChatOAuthMobileEnabled, OIDCOAuthEnabled: settings.OIDCOAuthEnabled, OIDCOAuthProviderName: settings.OIDCOAuthProviderName, GitHubOAuthEnabled: settings.GitHubOAuthEnabled, GoogleOAuthEnabled: settings.GoogleOAuthEnabled, BackendModeEnabled: settings.BackendModeEnabled, PaymentEnabled: settings.PaymentEnabled, Version: h.version, BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled, AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled, BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold, BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL, ChannelMonitorEnabled: settings.ChannelMonitorEnabled, ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds, AvailableChannelsEnabled: settings.AvailableChannelsEnabled, AffiliateEnabled: settings.AffiliateEnabled, RiskControlEnabled: settings.RiskControlEnabled, }) } // UnsubscribeNotificationEmail handles optional notification email opt-outs. // GET /api/v1/settings/email-unsubscribe?token=... func (h *SettingHandler) UnsubscribeNotificationEmail(c *gin.Context) { if h.notificationEmailService == nil { response.InternalError(c, "notification email service is not configured") return } token := strings.TrimSpace(c.Query("token")) if token == "" { response.BadRequest(c, "token is required") return } result, err := h.notificationEmailService.Unsubscribe(c.Request.Context(), token) if err != nil { response.BadRequest(c, err.Error()) return } body := "
You have unsubscribed " + html.EscapeString(result.Email) + " from " + html.EscapeString(result.Event) + " emails.
" c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(body)) } func publicLoginAgreementDocumentsToDTO(items []service.LoginAgreementDocument) []dto.LoginAgreementDocument { result := make([]dto.LoginAgreementDocument, 0, len(items)) for _, item := range items { result = append(result, dto.LoginAgreementDocument{ ID: item.ID, Title: item.Title, ContentMD: item.ContentMD, }) } return result }