Add IsFree subscription toggle to bypass all tier limitations
- Add IsFree boolean field to UserSubscription model - When IsFree is true, user sees limitations_enabled=false regardless of global setting - CheckLimit() bypasses all limit checks for IsFree users - Add admin endpoint GET /api/admin/subscriptions/user/:user_id - Add IsFree toggle to admin user detail page under Subscription card - Add database migration 004_subscription_is_free - Add integration tests for IsFree functionality - Add task kanban categorization documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -236,6 +236,7 @@ type SubscriptionFilters struct {
|
||||
type UpdateSubscriptionRequest struct {
|
||||
Tier *string `json:"tier" binding:"omitempty,oneof=free premium pro"`
|
||||
AutoRenew *bool `json:"auto_renew"`
|
||||
IsFree *bool `json:"is_free"`
|
||||
Platform *string `json:"platform" binding:"omitempty,max=20"`
|
||||
SubscribedAt *string `json:"subscribed_at"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
|
||||
@@ -258,6 +258,7 @@ type SubscriptionResponse struct {
|
||||
Tier string `json:"tier"`
|
||||
Platform string `json:"platform"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
IsFree bool `json:"is_free"`
|
||||
SubscribedAt *string `json:"subscribed_at,omitempty"`
|
||||
ExpiresAt *string `json:"expires_at,omitempty"`
|
||||
CancelledAt *string `json:"cancelled_at,omitempty"`
|
||||
|
||||
@@ -143,6 +143,9 @@ func (h *AdminSubscriptionHandler) Update(c *gin.Context) {
|
||||
if req.AutoRenew != nil {
|
||||
subscription.AutoRenew = *req.AutoRenew
|
||||
}
|
||||
if req.IsFree != nil {
|
||||
subscription.IsFree = *req.IsFree
|
||||
}
|
||||
|
||||
if err := h.db.Save(&subscription).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update subscription"})
|
||||
@@ -153,6 +156,44 @@ func (h *AdminSubscriptionHandler) Update(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
}
|
||||
|
||||
// GetByUser handles GET /api/admin/subscriptions/user/:user_id
|
||||
func (h *AdminSubscriptionHandler) GetByUser(c *gin.Context) {
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var subscription models.UserSubscription
|
||||
err = h.db.
|
||||
Preload("User").
|
||||
Where("user_id = ?", userID).
|
||||
First(&subscription).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// Create a default subscription for the user
|
||||
subscription = models.UserSubscription{
|
||||
UserID: uint(userID),
|
||||
Tier: models.TierFree,
|
||||
AutoRenew: true,
|
||||
IsFree: false,
|
||||
}
|
||||
if err := h.db.Create(&subscription).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create subscription"})
|
||||
return
|
||||
}
|
||||
// Reload with user
|
||||
h.db.Preload("User").First(&subscription, subscription.ID)
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch subscription"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toSubscriptionResponse(&subscription))
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/admin/subscriptions/stats
|
||||
func (h *AdminSubscriptionHandler) GetStats(c *gin.Context) {
|
||||
var total, free, premium, pro int64
|
||||
@@ -177,6 +218,7 @@ func (h *AdminSubscriptionHandler) toSubscriptionResponse(sub *models.UserSubscr
|
||||
Tier: string(sub.Tier),
|
||||
Platform: sub.Platform,
|
||||
AutoRenew: sub.AutoRenew,
|
||||
IsFree: sub.IsFree,
|
||||
CreatedAt: sub.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ func SetupRoutes(router *gin.Engine, db *gorm.DB, cfg *config.Config, deps *Depe
|
||||
{
|
||||
subscriptions.GET("", subscriptionHandler.List)
|
||||
subscriptions.GET("/stats", subscriptionHandler.GetStats)
|
||||
subscriptions.GET("/user/:user_id", subscriptionHandler.GetByUser)
|
||||
subscriptions.GET("/:id", subscriptionHandler.Get)
|
||||
subscriptions.PUT("/:id", subscriptionHandler.Update)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user