//go:build ignore // +build ignore package admin import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" "stream.api/internal/database/model" "stream.api/pkg/response" ) type AdminPlanPayload struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Features []string `json:"features,omitempty"` Price float64 `json:"price"` Cycle string `json:"cycle"` StorageLimit int64 `json:"storage_limit"` UploadLimit int32 `json:"upload_limit"` DurationLimit int32 `json:"duration_limit"` QualityLimit string `json:"quality_limit"` IsActive bool `json:"is_active"` UserCount int64 `json:"user_count"` PaymentCount int64 `json:"payment_count"` SubscriptionCount int64 `json:"subscription_count"` } type SavePlanRequest struct { Name string `json:"name" binding:"required"` Description string `json:"description"` Features []string `json:"features"` Price float64 `json:"price" binding:"required"` Cycle string `json:"cycle" binding:"required"` StorageLimit int64 `json:"storage_limit" binding:"required"` UploadLimit int32 `json:"upload_limit" binding:"required"` IsActive *bool `json:"is_active"` } func buildAdminPlanPayload(plan model.Plan, userCount, paymentCount, subscriptionCount int64) AdminPlanPayload { return AdminPlanPayload{ ID: plan.ID, Name: plan.Name, Description: adminStringValue(plan.Description), Features: adminStringSliceValue(plan.Features), Price: plan.Price, Cycle: plan.Cycle, StorageLimit: plan.StorageLimit, UploadLimit: plan.UploadLimit, DurationLimit: plan.DurationLimit, QualityLimit: plan.QualityLimit, IsActive: adminBoolValue(plan.IsActive, true), UserCount: userCount, PaymentCount: paymentCount, SubscriptionCount: subscriptionCount, } } func (h *Handler) loadPlanUsageCounts(ctx *gin.Context, planID string) (int64, int64, int64, error) { var userCount int64 if err := h.db.WithContext(ctx.Request.Context()).Model(&model.User{}).Where("plan_id = ?", planID).Count(&userCount).Error; err != nil { return 0, 0, 0, err } var paymentCount int64 if err := h.db.WithContext(ctx.Request.Context()).Model(&model.Payment{}).Where("plan_id = ?", planID).Count(&paymentCount).Error; err != nil { return 0, 0, 0, err } var subscriptionCount int64 if err := h.db.WithContext(ctx.Request.Context()).Model(&model.PlanSubscription{}).Where("plan_id = ?", planID).Count(&subscriptionCount).Error; err != nil { return 0, 0, 0, err } return userCount, paymentCount, subscriptionCount, nil } func validatePlanRequest(req *SavePlanRequest) string { if strings.TrimSpace(req.Name) == "" { return "Name is required" } if strings.TrimSpace(req.Cycle) == "" { return "Cycle is required" } if req.Price < 0 { return "Price must be greater than or equal to 0" } if req.StorageLimit <= 0 { return "Storage limit must be greater than 0" } if req.UploadLimit <= 0 { return "Upload limit must be greater than 0" } return "" } // @Summary List Plans // @Description Get all plans with usage counts (admin only) // @Tags admin // @Produce json // @Success 200 {object} response.Response // @Router /admin/plans [get] // @Security BearerAuth func (h *Handler) ListPlans(c *gin.Context) { ctx := c.Request.Context() var plans []model.Plan if err := h.db.WithContext(ctx).Order("price ASC").Find(&plans).Error; err != nil { h.logger.Error("Failed to list plans", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to list plans") return } result := make([]AdminPlanPayload, 0, len(plans)) for _, plan := range plans { userCount, paymentCount, subscriptionCount, err := h.loadPlanUsageCounts(c, plan.ID) if err != nil { h.logger.Error("Failed to load plan usage", "error", err, "plan_id", plan.ID) response.Error(c, http.StatusInternalServerError, "Failed to list plans") return } result = append(result, buildAdminPlanPayload(plan, userCount, paymentCount, subscriptionCount)) } response.Success(c, gin.H{"plans": result}) } // @Summary Create Plan // @Description Create a plan (admin only) // @Tags admin // @Accept json // @Produce json // @Param request body SavePlanRequest true "Plan payload" // @Success 201 {object} response.Response // @Router /admin/plans [post] // @Security BearerAuth func (h *Handler) CreatePlan(c *gin.Context) { var req SavePlanRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, err.Error()) return } if msg := validatePlanRequest(&req); msg != "" { response.Error(c, http.StatusBadRequest, msg) return } plan := &model.Plan{ ID: uuid.New().String(), Name: strings.TrimSpace(req.Name), Description: adminStringPtr(req.Description), Features: adminStringSlice(req.Features), Price: req.Price, Cycle: strings.TrimSpace(req.Cycle), StorageLimit: req.StorageLimit, UploadLimit: req.UploadLimit, DurationLimit: 0, QualityLimit: "", IsActive: func() *bool { value := true if req.IsActive != nil { value = *req.IsActive } return &value }(), } if err := h.db.WithContext(c.Request.Context()).Create(plan).Error; err != nil { h.logger.Error("Failed to create plan", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to create plan") return } response.Created(c, gin.H{"plan": buildAdminPlanPayload(*plan, 0, 0, 0)}) } // @Summary Update Plan // @Description Update a plan (admin only) // @Tags admin // @Accept json // @Produce json // @Param id path string true "Plan ID" // @Param request body SavePlanRequest true "Plan payload" // @Success 200 {object} response.Response // @Router /admin/plans/{id} [put] // @Security BearerAuth func (h *Handler) UpdatePlan(c *gin.Context) { id := strings.TrimSpace(c.Param("id")) if id == "" { response.Error(c, http.StatusNotFound, "Plan not found") return } var req SavePlanRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, err.Error()) return } if msg := validatePlanRequest(&req); msg != "" { response.Error(c, http.StatusBadRequest, msg) return } ctx := c.Request.Context() var plan model.Plan if err := h.db.WithContext(ctx).Where("id = ?", id).First(&plan).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { response.Error(c, http.StatusNotFound, "Plan not found") return } response.Error(c, http.StatusInternalServerError, "Failed to update plan") return } plan.Name = strings.TrimSpace(req.Name) plan.Description = adminStringPtr(req.Description) plan.Features = adminStringSlice(req.Features) plan.Price = req.Price plan.Cycle = strings.TrimSpace(req.Cycle) plan.StorageLimit = req.StorageLimit plan.UploadLimit = req.UploadLimit if req.IsActive != nil { plan.IsActive = req.IsActive } if err := h.db.WithContext(ctx).Save(&plan).Error; err != nil { h.logger.Error("Failed to update plan", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to update plan") return } userCount, paymentCount, subscriptionCount, err := h.loadPlanUsageCounts(c, plan.ID) if err != nil { h.logger.Error("Failed to load plan usage", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to update plan") return } response.Success(c, gin.H{"plan": buildAdminPlanPayload(plan, userCount, paymentCount, subscriptionCount)}) } // @Summary Delete Plan // @Description Delete a plan, or deactivate it if already used (admin only) // @Tags admin // @Produce json // @Param id path string true "Plan ID" // @Success 200 {object} response.Response // @Router /admin/plans/{id} [delete] // @Security BearerAuth func (h *Handler) DeletePlan(c *gin.Context) { id := strings.TrimSpace(c.Param("id")) if id == "" { response.Error(c, http.StatusNotFound, "Plan not found") return } ctx := c.Request.Context() var plan model.Plan if err := h.db.WithContext(ctx).Where("id = ?", id).First(&plan).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { response.Error(c, http.StatusNotFound, "Plan not found") return } response.Error(c, http.StatusInternalServerError, "Failed to delete plan") return } var paymentCount int64 if err := h.db.WithContext(ctx).Model(&model.Payment{}).Where("plan_id = ?", id).Count(&paymentCount).Error; err != nil { response.Error(c, http.StatusInternalServerError, "Failed to delete plan") return } var subscriptionCount int64 if err := h.db.WithContext(ctx).Model(&model.PlanSubscription{}).Where("plan_id = ?", id).Count(&subscriptionCount).Error; err != nil { response.Error(c, http.StatusInternalServerError, "Failed to delete plan") return } if paymentCount > 0 || subscriptionCount > 0 { inactive := false if err := h.db.WithContext(ctx).Model(&model.Plan{}).Where("id = ?", id).Update("is_active", inactive).Error; err != nil { h.logger.Error("Failed to deactivate plan", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to deactivate plan") return } response.Success(c, gin.H{"message": "Plan deactivated", "mode": "deactivated"}) return } if err := h.db.WithContext(ctx).Where("id = ?", id).Delete(&model.Plan{}).Error; err != nil { h.logger.Error("Failed to delete plan", "error", err) response.Error(c, http.StatusInternalServerError, "Failed to delete plan") return } response.Success(c, gin.H{"message": "Plan deleted", "mode": "deleted"}) }