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