package handler import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/johnzastrow/actalog/internal/service" "github.com/johnzastrow/actalog/pkg/logger" "github.com/johnzastrow/actalog/pkg/middleware" ) // SubscriptionHandler handles subscription-related endpoints type SubscriptionHandler struct { subscriptionService *service.SubscriptionService logger *logger.Logger } // NewSubscriptionHandler creates a new subscription handler func NewSubscriptionHandler(subscriptionService *service.SubscriptionService, l *logger.Logger) *SubscriptionHandler { return &SubscriptionHandler{ subscriptionService: subscriptionService, logger: l, } } // CreateUserSubscriptionRequest represents a request to create a user subscription // @Description Request to create a user subscription (admin only) type CreateUserSubscriptionRequest struct { UserID int64 `json:"user_id" example:"1"` SubscriptionType string `json:"subscription_type" example:"monthly"` // "free", "monthly", "annual" IsPermanentFree bool `json:"is_permanent_free" example:"false"` Notes string `json:"notes" example:"Customer referral discount"` } // CreateOrganizationSubscriptionRequest represents a request to create an organization subscription // @Description Request to create an organization subscription (admin only) type CreateOrganizationSubscriptionRequest struct { OrganizationID int64 `json:"organization_id" example:"1"` SubscriptionType string `json:"subscription_type" example:"annual"` IsPermanentFree bool `json:"is_permanent_free" example:"false"` Notes string `json:"notes" example:"Enterprise plan"` } // CancelSubscriptionRequest represents a request to cancel a subscription // @Description Request to cancel a subscription type CancelSubscriptionRequest struct { Reason string `json:"reason" example:"User requested cancellation"` } // GetMySubscriptionStatus returns the current user's subscription status // @Summary Get my subscription status // @Description Get the current user's subscription status and plan details // @Tags subscriptions // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Subscription status" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /subscriptions/my-status [get] func (h *SubscriptionHandler) GetMySubscriptionStatus(w http.ResponseWriter, r *http.Request) { // Get user ID from context userID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription status status, err := h.subscriptionService.GetUserSubscriptionStatus(userID) if err != nil { if h.logger != nil { h.logger.Error("action=get_subscription_status outcome=failure user_id=%d error=%v", userID, err) } respondError(w, http.StatusInternalServerError, "Failed to get subscription status") return } respondJSON(w, http.StatusOK, status) } // CreateUserSubscription creates a new user subscription (admin only) // @Summary Create user subscription (Admin) // @Description Create a new subscription for a user (admin only) // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param request body CreateUserSubscriptionRequest true "Subscription details" // @Success 201 {object} map[string]interface{} "Created subscription" // @Failure 400 {object} ErrorResponse "Invalid request" // @Failure 403 {object} ErrorResponse "Cannot modify own subscription" // @Failure 409 {object} ErrorResponse "Active subscription exists" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users [post] func (h *SubscriptionHandler) CreateUserSubscription(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } var req CreateUserSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } // Validate required fields if req.UserID == 0 { respondError(w, http.StatusBadRequest, "user_id is required") return } if req.SubscriptionType == "" { respondError(w, http.StatusBadRequest, "subscription_type is required") return } if h.logger != nil { h.logger.Info("action=create_user_subscription admin_id=%d user_id=%d type=%s permanent_free=%v", adminUserID, req.UserID, req.SubscriptionType, req.IsPermanentFree) } // Create subscription sub, err := h.subscriptionService.CreateUserSubscription( adminUserID, req.UserID, req.SubscriptionType, req.IsPermanentFree, req.Notes, ) if err != nil { switch err { case service.ErrCannotModifyOwnSubscription: respondError(w, http.StatusForbidden, "Admins cannot modify their own subscriptions") case service.ErrActiveSubscriptionExists: respondError(w, http.StatusConflict, "User already has an active subscription") default: if h.logger != nil { h.logger.Error("action=create_user_subscription outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to create subscription") } return } if h.logger != nil { h.logger.Info("action=create_user_subscription outcome=success admin_id=%d subscription_id=%d", adminUserID, sub.ID) } respondJSON(w, http.StatusCreated, sub) } // CreateOrganizationSubscription creates a new organization subscription (admin only) // @Summary Create organization subscription (Admin) // @Description Create a new subscription for an organization (admin only) // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param request body CreateOrganizationSubscriptionRequest true "Subscription details" // @Success 201 {object} map[string]interface{} "Created subscription" // @Failure 400 {object} ErrorResponse "Invalid request" // @Failure 409 {object} ErrorResponse "Active subscription exists" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations [post] func (h *SubscriptionHandler) CreateOrganizationSubscription(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } var req CreateOrganizationSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } // Validate required fields if req.OrganizationID == 0 { respondError(w, http.StatusBadRequest, "organization_id is required") return } if req.SubscriptionType == "" { respondError(w, http.StatusBadRequest, "subscription_type is required") return } if h.logger != nil { h.logger.Info("action=create_org_subscription admin_id=%d org_id=%d type=%s permanent_free=%v", adminUserID, req.OrganizationID, req.SubscriptionType, req.IsPermanentFree) } // Create subscription sub, err := h.subscriptionService.CreateOrganizationSubscription( adminUserID, req.OrganizationID, req.SubscriptionType, req.IsPermanentFree, req.Notes, ) if err != nil { switch err { case service.ErrActiveOrgSubscriptionExists: respondError(w, http.StatusConflict, "Organization already has an active subscription") default: if h.logger != nil { h.logger.Error("action=create_org_subscription outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to create subscription") } return } if h.logger != nil { h.logger.Info("action=create_org_subscription outcome=success admin_id=%d subscription_id=%d", adminUserID, sub.ID) } respondJSON(w, http.StatusCreated, sub) } // GetUserSubscriptions gets all subscriptions for a user (admin only) // @Summary Get user subscriptions (Admin) // @Description Get all subscriptions for a specific user (admin only) // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param user_id path int true "User ID" // @Success 200 {object} map[string]interface{} "Subscriptions list" // @Failure 400 {object} ErrorResponse "Invalid user ID" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/{user_id} [get] func (h *SubscriptionHandler) GetUserSubscriptions(w http.ResponseWriter, r *http.Request) { // Get user ID from URL parameter userIDStr := chi.URLParam(r, "user_id") userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid user_id") return } // Get subscriptions subscriptions, err := h.subscriptionService.GetUserSubscriptions(userID) if err != nil { if h.logger != nil { h.logger.Error("action=get_user_subscriptions outcome=failure user_id=%d error=%v", userID, err) } respondError(w, http.StatusInternalServerError, "Failed to get subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) } // GetOrganizationSubscriptions gets all subscriptions for an organization (admin only) // @Summary Get organization subscriptions (Admin) // @Description Get all subscriptions for a specific organization (admin only) // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param org_id path int true "Organization ID" // @Success 200 {object} map[string]interface{} "Subscriptions list" // @Failure 400 {object} ErrorResponse "Invalid organization ID" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/{org_id} [get] func (h *SubscriptionHandler) GetOrganizationSubscriptions(w http.ResponseWriter, r *http.Request) { // Get organization ID from URL parameter orgIDStr := chi.URLParam(r, "org_id") orgID, err := strconv.ParseInt(orgIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid org_id") return } // Get subscriptions subscriptions, err := h.subscriptionService.GetOrganizationSubscriptions(orgID) if err != nil { if h.logger != nil { h.logger.Error("action=get_org_subscriptions outcome=failure org_id=%d error=%v", orgID, err) } respondError(w, http.StatusInternalServerError, "Failed to get subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) } // MarkUserSubscriptionAsPaidRequest represents a request to mark a subscription as paid // @Description Request to mark a subscription as paid type MarkUserSubscriptionAsPaidRequest struct { PaymentDate *string `json:"payment_date" example:"2024-01-15"` // Optional: defaults to now DurationDays *int `json:"duration_days" example:"30"` // Optional: custom duration in days } // MarkUserSubscriptionAsPaid marks a user subscription as paid (admin only) // @Summary Mark user subscription as paid (Admin) // @Description Mark a user subscription as paid and extend the subscription period // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body MarkUserSubscriptionAsPaidRequest false "Payment details" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Cannot mark free subscription as paid" // @Failure 403 {object} ErrorResponse "Cannot modify own subscription" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/{id}/paid [put] func (h *SubscriptionHandler) MarkUserSubscriptionAsPaid(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } // Parse request body (optional) var req MarkUserSubscriptionAsPaidRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { // If body is invalid, use defaults req = MarkUserSubscriptionAsPaidRequest{} } if h.logger != nil { h.logger.Info("action=mark_user_subscription_paid admin_id=%d subscription_id=%d duration_days=%v", adminUserID, subID, req.DurationDays) } // Mark as paid err = h.subscriptionService.MarkUserSubscriptionAsPaid(adminUserID, subID, req.PaymentDate, req.DurationDays) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") case service.ErrCannotModifyOwnSubscription: respondError(w, http.StatusForbidden, "Admins cannot modify their own subscriptions") case service.ErrCannotMarkFreeSubscriptionPaid: respondError(w, http.StatusBadRequest, "Cannot mark free subscription as paid") default: if h.logger != nil { h.logger.Error("action=mark_user_subscription_paid outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to mark subscription as paid") } return } if h.logger != nil { h.logger.Info("action=mark_user_subscription_paid outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription marked as paid successfully", }) } // MarkOrganizationSubscriptionAsPaid marks an organization subscription as paid (admin only) // @Summary Mark organization subscription as paid (Admin) // @Description Mark an organization subscription as paid and extend the subscription period // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body MarkUserSubscriptionAsPaidRequest false "Payment details" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Cannot mark free subscription as paid" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/{id}/paid [put] func (h *SubscriptionHandler) MarkOrganizationSubscriptionAsPaid(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } // Parse request body (optional) var req MarkUserSubscriptionAsPaidRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { // If body is invalid, use defaults req = MarkUserSubscriptionAsPaidRequest{} } if h.logger != nil { h.logger.Info("action=mark_org_subscription_paid admin_id=%d subscription_id=%d duration_days=%v", adminUserID, subID, req.DurationDays) } // Mark as paid err = h.subscriptionService.MarkOrganizationSubscriptionAsPaid(adminUserID, subID, req.PaymentDate, req.DurationDays) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") case service.ErrCannotMarkFreeSubscriptionPaid: respondError(w, http.StatusBadRequest, "Cannot mark free subscription as paid") default: if h.logger != nil { h.logger.Error("action=mark_org_subscription_paid outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to mark subscription as paid") } return } if h.logger != nil { h.logger.Info("action=mark_org_subscription_paid outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription marked as paid successfully", }) } // CancelUserSubscription cancels a user subscription (admin only) // @Summary Cancel user subscription (Admin) // @Description Cancel a user subscription with a reason // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body CancelSubscriptionRequest true "Cancellation reason" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Invalid request or missing reason" // @Failure 403 {object} ErrorResponse "Cannot modify own subscription" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/{id}/cancel [post] func (h *SubscriptionHandler) CancelUserSubscription(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } var req CancelSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } if req.Reason == "" { respondError(w, http.StatusBadRequest, "Cancellation reason is required") return } if h.logger != nil { h.logger.Info("action=cancel_user_subscription admin_id=%d subscription_id=%d reason=%s", adminUserID, subID, req.Reason) } // Cancel subscription err = h.subscriptionService.CancelUserSubscription(adminUserID, subID, req.Reason) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") case service.ErrCannotModifyOwnSubscription: respondError(w, http.StatusForbidden, "Admins cannot modify their own subscriptions") default: if h.logger != nil { h.logger.Error("action=cancel_user_subscription outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to cancel subscription") } return } if h.logger != nil { h.logger.Info("action=cancel_user_subscription outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription cancelled successfully", }) } // SetUserSubscriptionPermanentRequest represents a request to set permanent status // @Description Request to set subscription permanent status type SetUserSubscriptionPermanentRequest struct { IsPermanent bool `json:"is_permanent" example:"true"` } // SetUserSubscriptionPermanent sets the permanent free status of a user subscription (admin only) // @Summary Set user subscription permanent status (Admin) // @Description Set whether a user subscription is permanently free // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body SetUserSubscriptionPermanentRequest true "Permanent status" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Invalid request" // @Failure 403 {object} ErrorResponse "Cannot modify own subscription" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/{id}/permanent [put] func (h *SubscriptionHandler) SetUserSubscriptionPermanent(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } // Parse request body var req SetUserSubscriptionPermanentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } if h.logger != nil { h.logger.Info("action=set_user_subscription_permanent admin_id=%d subscription_id=%d is_permanent=%v", adminUserID, subID, req.IsPermanent) } // Set permanent status err = h.subscriptionService.SetUserSubscriptionPermanent(adminUserID, subID, req.IsPermanent) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") case service.ErrCannotModifyOwnSubscription: respondError(w, http.StatusForbidden, "Admins cannot modify their own subscriptions") default: if h.logger != nil { h.logger.Error("action=set_user_subscription_permanent outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to update subscription") } return } if h.logger != nil { h.logger.Info("action=set_user_subscription_permanent outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription permanent status updated successfully", }) } // CancelOrganizationSubscription cancels an organization subscription (admin only) // @Summary Cancel organization subscription (Admin) // @Description Cancel an organization subscription with a reason // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body CancelSubscriptionRequest true "Cancellation reason" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Invalid request or missing reason" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/{id}/cancel [post] func (h *SubscriptionHandler) CancelOrganizationSubscription(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } var req CancelSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } if req.Reason == "" { respondError(w, http.StatusBadRequest, "Cancellation reason is required") return } if h.logger != nil { h.logger.Info("action=cancel_org_subscription admin_id=%d subscription_id=%d reason=%s", adminUserID, subID, req.Reason) } // Cancel subscription err = h.subscriptionService.CancelOrganizationSubscription(adminUserID, subID, req.Reason) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") default: if h.logger != nil { h.logger.Error("action=cancel_org_subscription outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to cancel subscription") } return } if h.logger != nil { h.logger.Info("action=cancel_org_subscription outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription cancelled successfully", }) } // SetOrganizationSubscriptionPermanent sets the permanent free status of an organization subscription (admin only) // @Summary Set organization subscription permanent status (Admin) // @Description Set whether an organization subscription is permanently free // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Subscription ID" // @Param request body SetUserSubscriptionPermanentRequest true "Permanent status" // @Success 200 {object} MessageResponse "Success message" // @Failure 400 {object} ErrorResponse "Invalid request" // @Failure 404 {object} ErrorResponse "Subscription not found" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/{id}/permanent [put] func (h *SubscriptionHandler) SetOrganizationSubscriptionPermanent(w http.ResponseWriter, r *http.Request) { // Get admin user ID from context adminUserID, ok := middleware.GetUserID(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } // Get subscription ID from URL parameter subIDStr := chi.URLParam(r, "id") subID, err := strconv.ParseInt(subIDStr, 10, 64) if err != nil { respondError(w, http.StatusBadRequest, "Invalid subscription ID") return } // Parse request body var req SetUserSubscriptionPermanentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body") return } if h.logger != nil { h.logger.Info("action=set_org_subscription_permanent admin_id=%d subscription_id=%d is_permanent=%v", adminUserID, subID, req.IsPermanent) } // Set permanent status err = h.subscriptionService.SetOrganizationSubscriptionPermanent(adminUserID, subID, req.IsPermanent) if err != nil { switch err { case service.ErrSubscriptionNotFound: respondError(w, http.StatusNotFound, "Subscription not found") default: if h.logger != nil { h.logger.Error("action=set_org_subscription_permanent outcome=failure admin_id=%d error=%v", adminUserID, err) } respondError(w, http.StatusInternalServerError, "Failed to update subscription") } return } if h.logger != nil { h.logger.Info("action=set_org_subscription_permanent outcome=success admin_id=%d subscription_id=%d", adminUserID, subID) } respondJSON(w, http.StatusOK, map[string]string{ "message": "Subscription permanent status updated successfully", }) } // ListAllUserSubscriptions lists all user subscriptions (admin only) // @Summary List all user subscriptions (Admin) // @Description Get a list of all user subscriptions in the system // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users [get] func (h *SubscriptionHandler) ListAllUserSubscriptions(w http.ResponseWriter, r *http.Request) { // Get all user subscriptions subscriptions, err := h.subscriptionService.ListAllUserSubscriptions() if err != nil { if h.logger != nil { h.logger.Error("action=list_all_user_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) } // ListAllOrganizationSubscriptions lists all organization subscriptions (admin only) // @Summary List all organization subscriptions (Admin) // @Description Get a list of all organization subscriptions in the system // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations [get] func (h *SubscriptionHandler) ListAllOrganizationSubscriptions(w http.ResponseWriter, r *http.Request) { // Get all organization subscriptions subscriptions, err := h.subscriptionService.ListAllOrganizationSubscriptions() if err != nil { if h.logger != nil { h.logger.Error("action=list_all_org_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) } // ListExpiringUserSubscriptions lists user subscriptions expiring within N days (admin only) // @Summary List expiring user subscriptions (Admin) // @Description Get a list of user subscriptions expiring within the specified number of days // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param days query int false "Number of days to look ahead (default: 30)" // @Success 200 {object} map[string]interface{} "Expiring subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/expiring [get] func (h *SubscriptionHandler) ListExpiringUserSubscriptions(w http.ResponseWriter, r *http.Request) { // Get days parameter (default to 30) days := 30 if daysStr := r.URL.Query().Get("days"); daysStr != "" { if d, err := strconv.Atoi(daysStr); err == nil && d > 0 { days = d } } subscriptions, err := h.subscriptionService.ListExpiringUserSubscriptions(days) if err != nil { if h.logger != nil { h.logger.Error("action=list_expiring_user_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get expiring subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), "days": days, }) } // ListExpiredUserSubscriptions lists expired or overdue user subscriptions (admin only) // @Summary List expired user subscriptions (Admin) // @Description Get a list of expired or overdue user subscriptions // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Expired subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/users/expired [get] func (h *SubscriptionHandler) ListExpiredUserSubscriptions(w http.ResponseWriter, r *http.Request) { subscriptions, err := h.subscriptionService.ListExpiredUserSubscriptions() if err != nil { if h.logger != nil { h.logger.Error("action=list_expired_user_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get expired subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) } // ListExpiringOrganizationSubscriptions lists org subscriptions expiring within N days (admin only) // @Summary List expiring organization subscriptions (Admin) // @Description Get a list of organization subscriptions expiring within the specified number of days // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Param days query int false "Number of days to look ahead (default: 30)" // @Success 200 {object} map[string]interface{} "Expiring subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/expiring [get] func (h *SubscriptionHandler) ListExpiringOrganizationSubscriptions(w http.ResponseWriter, r *http.Request) { // Get days parameter (default to 30) days := 30 if daysStr := r.URL.Query().Get("days"); daysStr != "" { if d, err := strconv.Atoi(daysStr); err == nil && d > 0 { days = d } } subscriptions, err := h.subscriptionService.ListExpiringOrganizationSubscriptions(days) if err != nil { if h.logger != nil { h.logger.Error("action=list_expiring_org_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get expiring subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), "days": days, }) } // ListExpiredOrganizationSubscriptions lists expired or overdue organization subscriptions (admin only) // @Summary List expired organization subscriptions (Admin) // @Description Get a list of expired or overdue organization subscriptions // @Tags admin // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Expired subscriptions list" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/subscriptions/organizations/expired [get] func (h *SubscriptionHandler) ListExpiredOrganizationSubscriptions(w http.ResponseWriter, r *http.Request) { subscriptions, err := h.subscriptionService.ListExpiredOrganizationSubscriptions() if err != nil { if h.logger != nil { h.logger.Error("action=list_expired_org_subscriptions outcome=failure error=%v", err) } respondError(w, http.StatusInternalServerError, "Failed to get expired subscriptions") return } respondJSON(w, http.StatusOK, map[string]interface{}{ "subscriptions": subscriptions, "count": len(subscriptions), }) }