275 lines
7.8 KiB
Go
275 lines
7.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"aggios-app/backend/internal/domain"
|
|
"aggios-app/backend/internal/service"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// PlanHandler handles plan-related endpoints
|
|
type PlanHandler struct {
|
|
planService *service.PlanService
|
|
}
|
|
|
|
// NewPlanHandler creates a new plan handler
|
|
func NewPlanHandler(planService *service.PlanService) *PlanHandler {
|
|
return &PlanHandler{
|
|
planService: planService,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers plan routes
|
|
func (h *PlanHandler) RegisterRoutes(r *mux.Router) {
|
|
// Note: Route protection is done in main.go with authMiddleware wrapper
|
|
r.HandleFunc("/api/admin/plans", h.CreatePlan).Methods(http.MethodPost)
|
|
r.HandleFunc("/api/admin/plans", h.ListPlans).Methods(http.MethodGet)
|
|
r.HandleFunc("/api/admin/plans/{id}", h.GetPlan).Methods(http.MethodGet)
|
|
r.HandleFunc("/api/admin/plans/{id}", h.UpdatePlan).Methods(http.MethodPut)
|
|
r.HandleFunc("/api/admin/plans/{id}", h.DeletePlan).Methods(http.MethodDelete)
|
|
|
|
// Public routes (for signup flow)
|
|
r.HandleFunc("/api/plans", h.ListActivePlans).Methods(http.MethodGet)
|
|
r.HandleFunc("/api/plans/{id}", h.GetActivePlan).Methods(http.MethodGet)
|
|
}
|
|
|
|
// CreatePlan creates a new plan (admin only)
|
|
func (h *PlanHandler) CreatePlan(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("📋 CREATE PLAN - Method: %s", r.Method)
|
|
|
|
var req domain.CreatePlanRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
log.Printf("❌ Invalid request body: %v", err)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "Invalid request body", "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
plan, err := h.planService.CreatePlan(&req)
|
|
if err != nil {
|
|
log.Printf("❌ Error creating plan: %v", err)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch err {
|
|
case service.ErrPlanSlugTaken:
|
|
w.WriteHeader(http.StatusConflict)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "Slug already taken", "message": err.Error()})
|
|
case service.ErrInvalidUserRange:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "Invalid user range", "message": err.Error()})
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": "Internal server error", "message": err.Error()})
|
|
}
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"message": "Plan created successfully",
|
|
"plan": plan,
|
|
})
|
|
log.Printf("✅ Plan created: %s", plan.ID)
|
|
}
|
|
|
|
// GetPlan retrieves a plan by ID (admin only)
|
|
func (h *PlanHandler) GetPlan(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
idStr := vars["id"]
|
|
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid plan ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
plan, err := h.planService.GetPlan(id)
|
|
if err != nil {
|
|
if err == service.ErrPlanNotFound {
|
|
http.Error(w, "Plan not found", http.StatusNotFound)
|
|
} else {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"plan": plan,
|
|
})
|
|
}
|
|
|
|
// ListPlans retrieves all plans (admin only)
|
|
func (h *PlanHandler) ListPlans(w http.ResponseWriter, r *http.Request) {
|
|
plans, err := h.planService.ListPlans()
|
|
if err != nil {
|
|
log.Printf("❌ Error listing plans: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"plans": plans,
|
|
})
|
|
log.Printf("✅ Listed %d plans", len(plans))
|
|
}
|
|
|
|
// ListActivePlans retrieves all active plans (public)
|
|
func (h *PlanHandler) ListActivePlans(w http.ResponseWriter, r *http.Request) {
|
|
plans, err := h.planService.ListActivePlans()
|
|
if err != nil {
|
|
log.Printf("❌ Error listing active plans: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"plans": plans,
|
|
})
|
|
}
|
|
|
|
// GetActivePlan retrieves an active plan by ID (public)
|
|
func (h *PlanHandler) GetActivePlan(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
idStr := vars["id"]
|
|
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid plan ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
plan, err := h.planService.GetPlan(id)
|
|
if err != nil {
|
|
if err == service.ErrPlanNotFound {
|
|
http.Error(w, "Plan not found", http.StatusNotFound)
|
|
} else {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Check if plan is active
|
|
if !plan.IsActive {
|
|
http.Error(w, "Plan not available", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"plan": plan,
|
|
})
|
|
}
|
|
|
|
// UpdatePlan updates a plan (admin only)
|
|
func (h *PlanHandler) UpdatePlan(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("📋 UPDATE PLAN - Method: %s", r.Method)
|
|
|
|
vars := mux.Vars(r)
|
|
idStr := vars["id"]
|
|
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid plan ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req domain.UpdatePlanRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
log.Printf("❌ Invalid request body: %v", err)
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
plan, err := h.planService.UpdatePlan(id, &req)
|
|
if err != nil {
|
|
log.Printf("❌ Error updating plan: %v", err)
|
|
switch err {
|
|
case service.ErrPlanNotFound:
|
|
http.Error(w, "Plan not found", http.StatusNotFound)
|
|
case service.ErrPlanSlugTaken:
|
|
http.Error(w, err.Error(), http.StatusConflict)
|
|
case service.ErrInvalidUserRange:
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"message": "Plan updated successfully",
|
|
"plan": plan,
|
|
})
|
|
log.Printf("✅ Plan updated: %s", plan.ID)
|
|
}
|
|
|
|
// DeletePlan deletes a plan (admin only)
|
|
func (h *PlanHandler) DeletePlan(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("📋 DELETE PLAN - Method: %s", r.Method)
|
|
|
|
vars := mux.Vars(r)
|
|
idStr := vars["id"]
|
|
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid plan ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err = h.planService.DeletePlan(id)
|
|
if err != nil {
|
|
log.Printf("❌ Error deleting plan: %v", err)
|
|
switch err {
|
|
case service.ErrPlanNotFound:
|
|
http.Error(w, "Plan not found", http.StatusNotFound)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"message": "Plan deleted successfully",
|
|
})
|
|
log.Printf("✅ Plan deleted: %s", idStr)
|
|
}
|
|
|
|
// GetPlanByUserCount returns a plan for a given user count
|
|
func (h *PlanHandler) GetPlanByUserCount(w http.ResponseWriter, r *http.Request) {
|
|
userCountStr := r.URL.Query().Get("user_count")
|
|
if userCountStr == "" {
|
|
http.Error(w, "user_count parameter required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
userCount, err := strconv.Atoi(userCountStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid user_count", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
plan, err := h.planService.GetPlanByUserCount(userCount)
|
|
if err != nil {
|
|
http.Error(w, "No plan available for this user count", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"plan": plan,
|
|
})
|
|
}
|