setting_handler.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package handler
  2. import (
  3. "html"
  4. "net/http"
  5. "strings"
  6. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // SettingHandler 公开设置处理器(无需认证)
  13. type SettingHandler struct {
  14. settingService *service.SettingService
  15. notificationEmailService *service.NotificationEmailService
  16. version string
  17. }
  18. // NewSettingHandler 创建公开设置处理器
  19. func NewSettingHandler(settingService *service.SettingService, version string) *SettingHandler {
  20. return &SettingHandler{
  21. settingService: settingService,
  22. version: version,
  23. }
  24. }
  25. // SetNotificationEmailService attaches the public notification email service without
  26. // changing the constructor signature used by existing tests.
  27. func (h *SettingHandler) SetNotificationEmailService(notificationEmailService *service.NotificationEmailService) {
  28. h.notificationEmailService = notificationEmailService
  29. }
  30. // GetPublicSettings 获取公开设置
  31. // GET /api/v1/settings/public
  32. func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
  33. settings, err := h.settingService.GetPublicSettings(c.Request.Context())
  34. if err != nil {
  35. response.ErrorFrom(c, err)
  36. return
  37. }
  38. response.Success(c, dto.PublicSettings{
  39. RegistrationEnabled: settings.RegistrationEnabled,
  40. EmailVerifyEnabled: settings.EmailVerifyEnabled,
  41. ForceEmailOnThirdPartySignup: settings.ForceEmailOnThirdPartySignup,
  42. RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
  43. RegistrationEmailDomainQuotaEnabled: settings.RegistrationEmailDomainQuotaEnabled,
  44. PromoCodeEnabled: settings.PromoCodeEnabled,
  45. PasswordResetEnabled: settings.PasswordResetEnabled,
  46. InvitationCodeEnabled: settings.InvitationCodeEnabled,
  47. TotpEnabled: settings.TotpEnabled,
  48. PasskeyEnabled: settings.PasskeyEnabled,
  49. LoginAgreementEnabled: settings.LoginAgreementEnabled,
  50. LoginAgreementMode: settings.LoginAgreementMode,
  51. LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
  52. LoginAgreementRevision: settings.LoginAgreementRevision,
  53. LoginAgreementDocuments: publicLoginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
  54. TurnstileEnabled: settings.TurnstileEnabled,
  55. TurnstileSiteKey: settings.TurnstileSiteKey,
  56. TencentCaptchaEnabled: settings.TencentCaptchaEnabled,
  57. TencentCaptchaAppID: settings.TencentCaptchaAppID,
  58. TencentCaptchaRegion: settings.TencentCaptchaRegion,
  59. AliyunCaptchaEnabled: settings.AliyunCaptchaEnabled,
  60. AliyunCaptchaSceneID: settings.AliyunCaptchaSceneID,
  61. AliyunCaptchaPrefix: settings.AliyunCaptchaPrefix,
  62. AliyunCaptchaRegion: settings.AliyunCaptchaRegion,
  63. SiteName: settings.SiteName,
  64. SiteLogo: settings.SiteLogo,
  65. SiteSubtitle: settings.SiteSubtitle,
  66. APIBaseURL: settings.APIBaseURL,
  67. ContactInfo: settings.ContactInfo,
  68. DocURL: settings.DocURL,
  69. HomeContent: settings.HomeContent,
  70. CompactHomeEnabled: settings.CompactHomeEnabled,
  71. HideCcsImportButton: settings.HideCcsImportButton,
  72. PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
  73. PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
  74. TableDefaultPageSize: settings.TableDefaultPageSize,
  75. TablePageSizeOptions: settings.TablePageSizeOptions,
  76. CustomMenuItems: dto.ParseUserVisibleMenuItems(settings.CustomMenuItems),
  77. CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
  78. DingTalkOAuthEnabled: settings.DingTalkOAuthEnabled,
  79. LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled,
  80. WeChatOAuthEnabled: settings.WeChatOAuthEnabled,
  81. WeChatOAuthOpenEnabled: settings.WeChatOAuthOpenEnabled,
  82. WeChatOAuthMPEnabled: settings.WeChatOAuthMPEnabled,
  83. WeChatOAuthMobileEnabled: settings.WeChatOAuthMobileEnabled,
  84. OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
  85. OIDCOAuthProviderName: settings.OIDCOAuthProviderName,
  86. GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
  87. GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
  88. BackendModeEnabled: settings.BackendModeEnabled,
  89. PaymentEnabled: settings.PaymentEnabled,
  90. Version: h.version,
  91. ServerTimezone: timezone.Name(),
  92. ServerUTCOffset: timezone.UTCOffset(),
  93. BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
  94. AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
  95. BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
  96. BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
  97. ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
  98. ChannelMonitorMode: settings.ChannelMonitorMode,
  99. ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
  100. ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput,
  101. ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota,
  102. ChannelMonitorHideUserRanking: settings.ChannelMonitorHideUserRanking,
  103. AvailableChannelsEnabled: settings.AvailableChannelsEnabled,
  104. ModelPlazaEnabled: settings.ModelPlazaEnabled,
  105. ModelPlazaRequireAuth: settings.ModelPlazaRequireAuth,
  106. PluginManagementEnabled: settings.PluginManagementEnabled,
  107. AffiliateEnabled: settings.AffiliateEnabled,
  108. RiskControlEnabled: settings.RiskControlEnabled,
  109. AllowUserViewErrorRequests: settings.AllowUserViewErrorRequests,
  110. })
  111. }
  112. // UnsubscribeNotificationEmail handles optional notification email opt-outs.
  113. // GET /api/v1/settings/email-unsubscribe?token=...
  114. func (h *SettingHandler) UnsubscribeNotificationEmail(c *gin.Context) {
  115. if h.notificationEmailService == nil {
  116. response.InternalError(c, "notification email service is not configured")
  117. return
  118. }
  119. token := strings.TrimSpace(c.Query("token"))
  120. if token == "" {
  121. response.BadRequest(c, "token is required")
  122. return
  123. }
  124. result, err := h.notificationEmailService.Unsubscribe(c.Request.Context(), token)
  125. if err != nil {
  126. response.BadRequest(c, err.Error())
  127. return
  128. }
  129. body := "<!doctype html><html><head><meta charset=\"utf-8\"><title>Unsubscribed</title></head><body style=\"font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;padding:32px;\"><h1>Unsubscribed</h1><p>You have unsubscribed <strong>" + html.EscapeString(result.Email) + "</strong> from <strong>" + html.EscapeString(result.Event) + "</strong> emails.</p></body></html>"
  130. c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(body))
  131. }
  132. func publicLoginAgreementDocumentsToDTO(items []service.LoginAgreementDocument) []dto.LoginAgreementDocument {
  133. result := make([]dto.LoginAgreementDocument, 0, len(items))
  134. for _, item := range items {
  135. result = append(result, dto.LoginAgreementDocument{
  136. ID: item.ID,
  137. Title: item.Title,
  138. ContentMD: item.ContentMD,
  139. })
  140. }
  141. return result
  142. }