grok_media_eligibility_handler.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package admin
  2. import (
  3. "strconv"
  4. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  5. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/gin-gonic/gin"
  8. )
  9. const (
  10. grokMediaEligibilityModeAuto = "auto"
  11. grokMediaEligibilityModeEnabled = "enabled"
  12. grokMediaEligibilityModeDisabled = "disabled"
  13. )
  14. type grokMediaEligibilityResponse struct {
  15. AccountID int64 `json:"account_id"`
  16. Mode string `json:"mode"`
  17. Eligible bool `json:"eligible"`
  18. Reason string `json:"reason"`
  19. }
  20. type updateGrokMediaEligibilityRequest struct {
  21. Mode string `json:"mode" binding:"required"`
  22. }
  23. // GetGrokMediaEligibility returns the current manual override and evaluated
  24. // media-routing decision for a Grok OAuth account.
  25. // GET /api/v1/admin/accounts/:id/grok-media-eligibility
  26. func (h *AccountHandler) GetGrokMediaEligibility(c *gin.Context) {
  27. account, err := h.getGrokOAuthAccount(c)
  28. if err != nil {
  29. response.ErrorFrom(c, err)
  30. return
  31. }
  32. response.Success(c, buildGrokMediaEligibilityResponse(account))
  33. }
  34. // UpdateGrokMediaEligibility updates only the manual media eligibility
  35. // override. It deliberately uses UpdateAccountExtra so unrelated account
  36. // extra fields are preserved.
  37. // PUT /api/v1/admin/accounts/:id/grok-media-eligibility
  38. func (h *AccountHandler) UpdateGrokMediaEligibility(c *gin.Context) {
  39. account, err := h.getGrokOAuthAccount(c)
  40. if err != nil {
  41. response.ErrorFrom(c, err)
  42. return
  43. }
  44. var req updateGrokMediaEligibilityRequest
  45. if err := c.ShouldBindJSON(&req); err != nil {
  46. response.BadRequest(c, "Invalid request: "+err.Error())
  47. return
  48. }
  49. var value any
  50. switch req.Mode {
  51. case grokMediaEligibilityModeAuto:
  52. value = nil
  53. case grokMediaEligibilityModeEnabled:
  54. value = true
  55. case grokMediaEligibilityModeDisabled:
  56. value = false
  57. default:
  58. response.ErrorFrom(c, infraerrors.BadRequest(
  59. "GROK_MEDIA_ELIGIBILITY_INVALID_MODE",
  60. "mode must be one of auto, enabled, disabled",
  61. ))
  62. return
  63. }
  64. updates := map[string]any{service.GrokMediaEligibleExtraKey: value}
  65. if err := service.ValidateGrokMediaEligibilityExtra(account.Platform, updates); err != nil {
  66. response.ErrorFrom(c, err)
  67. return
  68. }
  69. if err := h.adminService.UpdateAccountExtra(c.Request.Context(), account.ID, updates); err != nil {
  70. response.ErrorFrom(c, err)
  71. return
  72. }
  73. updated, err := h.adminService.GetAccount(c.Request.Context(), account.ID)
  74. if err != nil {
  75. response.ErrorFrom(c, err)
  76. return
  77. }
  78. if err := ensureGrokOAuthAccount(updated); err != nil {
  79. response.ErrorFrom(c, err)
  80. return
  81. }
  82. response.Success(c, buildGrokMediaEligibilityResponse(updated))
  83. }
  84. func (h *AccountHandler) getGrokOAuthAccount(c *gin.Context) (*service.Account, error) {
  85. accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
  86. if err != nil {
  87. return nil, infraerrors.BadRequest("INVALID_ACCOUNT_ID", "Invalid account ID")
  88. }
  89. account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if err := ensureGrokOAuthAccount(account); err != nil {
  94. return nil, err
  95. }
  96. return account, nil
  97. }
  98. func ensureGrokOAuthAccount(account *service.Account) error {
  99. if account == nil {
  100. return infraerrors.NotFound("ACCOUNT_NOT_FOUND", "account not found")
  101. }
  102. if account.Platform != service.PlatformGrok || account.Type != service.AccountTypeOAuth {
  103. return infraerrors.BadRequest(
  104. "GROK_MEDIA_ELIGIBILITY_UNSUPPORTED_ACCOUNT",
  105. "media eligibility is only supported for Grok OAuth accounts",
  106. )
  107. }
  108. return nil
  109. }
  110. func buildGrokMediaEligibilityResponse(account *service.Account) grokMediaEligibilityResponse {
  111. eligible, reason := account.GrokMediaGenerationEligibility()
  112. return grokMediaEligibilityResponse{
  113. AccountID: account.ID,
  114. Mode: grokMediaEligibilityModeFromExtra(account.Extra),
  115. Eligible: eligible,
  116. Reason: reason,
  117. }
  118. }
  119. func grokMediaEligibilityModeFromExtra(extra map[string]any) string {
  120. if extra != nil {
  121. if enabled, ok := extra[service.GrokMediaEligibleExtraKey].(bool); ok {
  122. if enabled {
  123. return grokMediaEligibilityModeEnabled
  124. }
  125. return grokMediaEligibilityModeDisabled
  126. }
  127. }
  128. return grokMediaEligibilityModeAuto
  129. }