subscription_handler.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package handler
  2. import (
  3. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  4. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  5. middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/gin-gonic/gin"
  8. )
  9. // SubscriptionSummaryItem represents a subscription item in summary
  10. type SubscriptionSummaryItem struct {
  11. ID int64 `json:"id"`
  12. GroupID int64 `json:"group_id"`
  13. GroupName string `json:"group_name"`
  14. Status string `json:"status"`
  15. DailyUsedUSD float64 `json:"daily_used_usd,omitempty"`
  16. DailyLimitUSD float64 `json:"daily_limit_usd,omitempty"`
  17. WeeklyUsedUSD float64 `json:"weekly_used_usd,omitempty"`
  18. WeeklyLimitUSD float64 `json:"weekly_limit_usd,omitempty"`
  19. MonthlyUsedUSD float64 `json:"monthly_used_usd,omitempty"`
  20. MonthlyLimitUSD float64 `json:"monthly_limit_usd,omitempty"`
  21. ExpiresAt *string `json:"expires_at,omitempty"`
  22. }
  23. // SubscriptionProgressInfo represents subscription with progress info
  24. type SubscriptionProgressInfo struct {
  25. Subscription *dto.UserSubscription `json:"subscription"`
  26. Progress *service.SubscriptionProgress `json:"progress"`
  27. }
  28. // SubscriptionHandler handles user subscription operations
  29. type SubscriptionHandler struct {
  30. subscriptionService *service.SubscriptionService
  31. }
  32. // NewSubscriptionHandler creates a new user subscription handler
  33. func NewSubscriptionHandler(subscriptionService *service.SubscriptionService) *SubscriptionHandler {
  34. return &SubscriptionHandler{
  35. subscriptionService: subscriptionService,
  36. }
  37. }
  38. // List handles listing current user's subscriptions
  39. // GET /api/v1/subscriptions
  40. func (h *SubscriptionHandler) List(c *gin.Context) {
  41. subject, ok := middleware2.GetAuthSubjectFromContext(c)
  42. if !ok {
  43. response.Unauthorized(c, "User not found in context")
  44. return
  45. }
  46. subscriptions, err := h.subscriptionService.ListUserSubscriptions(c.Request.Context(), subject.UserID)
  47. if err != nil {
  48. response.ErrorFrom(c, err)
  49. return
  50. }
  51. out := make([]dto.UserSubscription, 0, len(subscriptions))
  52. for i := range subscriptions {
  53. out = append(out, *dto.UserSubscriptionFromService(&subscriptions[i]))
  54. }
  55. response.Success(c, out)
  56. }
  57. // GetActive handles getting current user's active subscriptions
  58. // GET /api/v1/subscriptions/active
  59. func (h *SubscriptionHandler) GetActive(c *gin.Context) {
  60. subject, ok := middleware2.GetAuthSubjectFromContext(c)
  61. if !ok {
  62. response.Unauthorized(c, "User not found in context")
  63. return
  64. }
  65. subscriptions, err := h.subscriptionService.ListActiveUserSubscriptions(c.Request.Context(), subject.UserID)
  66. if err != nil {
  67. response.ErrorFrom(c, err)
  68. return
  69. }
  70. out := make([]dto.UserSubscription, 0, len(subscriptions))
  71. for i := range subscriptions {
  72. out = append(out, *dto.UserSubscriptionFromService(&subscriptions[i]))
  73. }
  74. response.Success(c, out)
  75. }
  76. // GetProgress handles getting subscription progress for current user
  77. // GET /api/v1/subscriptions/progress
  78. func (h *SubscriptionHandler) GetProgress(c *gin.Context) {
  79. subject, ok := middleware2.GetAuthSubjectFromContext(c)
  80. if !ok {
  81. response.Unauthorized(c, "User not found in context")
  82. return
  83. }
  84. // Get all active subscriptions with progress
  85. subscriptions, err := h.subscriptionService.ListActiveUserSubscriptions(c.Request.Context(), subject.UserID)
  86. if err != nil {
  87. response.ErrorFrom(c, err)
  88. return
  89. }
  90. result := make([]SubscriptionProgressInfo, 0, len(subscriptions))
  91. for i := range subscriptions {
  92. sub := &subscriptions[i]
  93. progress, err := h.subscriptionService.GetSubscriptionProgress(c.Request.Context(), sub.ID)
  94. if err != nil {
  95. // Skip subscriptions with errors
  96. continue
  97. }
  98. result = append(result, SubscriptionProgressInfo{
  99. Subscription: dto.UserSubscriptionFromService(sub),
  100. Progress: progress,
  101. })
  102. }
  103. response.Success(c, result)
  104. }
  105. // GetSummary handles getting a summary of current user's subscription status
  106. // GET /api/v1/subscriptions/summary
  107. func (h *SubscriptionHandler) GetSummary(c *gin.Context) {
  108. subject, ok := middleware2.GetAuthSubjectFromContext(c)
  109. if !ok {
  110. response.Unauthorized(c, "User not found in context")
  111. return
  112. }
  113. // Get all active subscriptions
  114. subscriptions, err := h.subscriptionService.ListActiveUserSubscriptions(c.Request.Context(), subject.UserID)
  115. if err != nil {
  116. response.ErrorFrom(c, err)
  117. return
  118. }
  119. var totalUsed float64
  120. items := make([]SubscriptionSummaryItem, 0, len(subscriptions))
  121. for _, sub := range subscriptions {
  122. item := SubscriptionSummaryItem{
  123. ID: sub.ID,
  124. GroupID: sub.GroupID,
  125. Status: sub.Status,
  126. DailyUsedUSD: sub.DailyUsageUSD,
  127. WeeklyUsedUSD: sub.WeeklyUsageUSD,
  128. MonthlyUsedUSD: sub.MonthlyUsageUSD,
  129. }
  130. // Add group info if preloaded
  131. if sub.Group != nil {
  132. item.GroupName = sub.Group.Name
  133. if sub.Group.DailyLimitUSD != nil {
  134. item.DailyLimitUSD = *sub.Group.DailyLimitUSD
  135. }
  136. if sub.Group.WeeklyLimitUSD != nil {
  137. item.WeeklyLimitUSD = *sub.Group.WeeklyLimitUSD
  138. }
  139. if sub.Group.MonthlyLimitUSD != nil {
  140. item.MonthlyLimitUSD = *sub.Group.MonthlyLimitUSD
  141. }
  142. }
  143. // Format expiration time
  144. if !sub.ExpiresAt.IsZero() {
  145. formatted := sub.ExpiresAt.Format("2006-01-02T15:04:05Z07:00")
  146. item.ExpiresAt = &formatted
  147. }
  148. // Track total usage (use monthly as the most comprehensive)
  149. totalUsed += sub.MonthlyUsageUSD
  150. items = append(items, item)
  151. }
  152. summary := struct {
  153. ActiveCount int `json:"active_count"`
  154. TotalUsedUSD float64 `json:"total_used_usd"`
  155. Subscriptions []SubscriptionSummaryItem `json:"subscriptions"`
  156. }{
  157. ActiveCount: len(subscriptions),
  158. TotalUsedUSD: totalUsed,
  159. Subscriptions: items,
  160. }
  161. response.Success(c, summary)
  162. }