package handlers import ( "net/http" "os" "strings" "gauth-central/config" "gauth-central/internal/services" "gauth-central/pkg/utils" "github.com/gin-gonic/gin" ) type ServiceHandler struct { serviceService *services.ServiceService } func NewServiceHandler(serviceService *services.ServiceService) *ServiceHandler { return &ServiceHandler{serviceService: serviceService} } // GetAllServices godoc // @Summary Get all active services // @Description Retrieve a list of active services // @Tags services // @Produce json // @Success 200 {array} models.Service // @Failure 500 {object} map[string]string // @Router /services [get] func (h *ServiceHandler) GetAllServices(c *gin.Context) { items, err := h.serviceService.GetAllServices(true) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, items) } // GetServiceBySlug godoc // @Summary Get service by slug // @Description Retrieve a single active service by slug // @Tags services // @Produce json // @Param slug path string true "Service Slug" // @Success 200 {object} models.Service // @Failure 404 {object} map[string]string // @Router /services/{slug} [get] func (h *ServiceHandler) GetServiceBySlug(c *gin.Context) { slug := c.Param("slug") item, err := h.serviceService.GetServiceBySlug(slug, true) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, item) } // AdminGetAllServices godoc // @Summary Get all services (Admin) // @Description Retrieve a list of all services including inactive ones // @Tags admin // @Security ApiKeyAuth // @Produce json // @Success 200 {array} models.Service // @Failure 500 {object} map[string]string // @Router /admin/services [get] func (h *ServiceHandler) AdminGetAllServices(c *gin.Context) { items, err := h.serviceService.GetAllServices(false) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, items) } // AdminGetServiceByID godoc // @Summary Get a service by ID (Admin) // @Description Retrieve details of a specific service // @Tags admin // @Security ApiKeyAuth // @Produce json // @Param id path string true "Service ID" // @Success 200 {object} models.Service // @Failure 404 {object} map[string]string // @Router /admin/services/{id} [get] func (h *ServiceHandler) AdminGetServiceByID(c *gin.Context) { id := c.Param("id") item, err := h.serviceService.GetServiceByID(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, item) } // CreateService godoc // @Summary Create a new service (Admin) // @Description Create a new service entry // @Tags admin // @Security ApiKeyAuth // @Accept multipart/form-data // @Produce json // @Param title formData string true "Title" // @Param content formData string false "Content" // @Param image formData file false "Image" // @Param is_active formData bool false "Is active" // @Success 201 {object} models.Service // @Failure 400 {object} map[string]string // @Router /admin/services [post] func (h *ServiceHandler) CreateService(c *gin.Context) { title := strings.TrimSpace(c.PostForm("title")) content := strings.TrimSpace(c.PostForm("content")) if title == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "title is required"}) return } isActive := false isActivePtr, err := parseOptionalBool(c, "is_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"}) return } if isActivePtr != nil { isActive = *isActivePtr } item, err := h.serviceService.CreateService(title, content, "", isActive) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } file, fileErr := c.FormFile("image") if fileErr == nil { if file.Size > 5*1024*1024 { c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"}) return } imageURL, saveErr := utils.SaveOptimizedImage(file, "./uploads/services", item.ID.String(), &utils.ImageOptions{ Width: config.AppConfig.ServiceImageWidth, Height: config.AppConfig.ServiceImageHeight, Quality: float32(config.AppConfig.ServiceImageQuality), Format: config.AppConfig.ServiceImageFormat, Mode: config.AppConfig.ServiceImageMode, }) if saveErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()}) return } updated, updateErr := h.serviceService.UpdateService( item.ID.String(), nil, nil, &imageURL, nil, nil, ) if updateErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": updateErr.Error()}) return } item = updated } c.JSON(http.StatusCreated, item) } // UpdateService godoc // @Summary Update a service (Admin) // @Description Update an existing service entry // @Tags admin // @Security ApiKeyAuth // @Accept multipart/form-data // @Produce json // @Param id path string true "Service ID" // @Param title formData string false "Title" // @Param content formData string false "Content" // @Param image formData file false "Image" // @Param slug formData string false "Slug" // @Param is_active formData bool false "Is active" // @Success 200 {object} models.Service // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /admin/services/{id} [put] func (h *ServiceHandler) UpdateService(c *gin.Context) { id := c.Param("id") title, hasTitle := getOptionalFormValue(c, "title") content, hasContent := getOptionalFormValue(c, "content") slug, hasSlug := getOptionalFormValue(c, "slug") var titlePtr *string var contentPtr *string var slugPtr *string if hasTitle { titlePtr = &title } if hasContent { contentPtr = &content } if hasSlug { slugPtr = &slug } isActivePtr, err := parseOptionalBool(c, "is_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"}) return } var imagePtr *string file, fileErr := c.FormFile("image") if fileErr == nil { if file.Size > 5*1024*1024 { c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"}) return } item, fetchErr := h.serviceService.GetServiceByID(id) if fetchErr != nil { c.JSON(http.StatusNotFound, gin.H{"error": fetchErr.Error()}) return } if item.Image != "" && strings.HasPrefix(item.Image, "/uploads/") { oldPath := "." + item.Image _ = os.Remove(oldPath) } imageURL, saveErr := utils.SaveOptimizedImage(file, "./uploads/services", id, &utils.ImageOptions{ Width: config.AppConfig.ServiceImageWidth, Height: config.AppConfig.ServiceImageHeight, Quality: float32(config.AppConfig.ServiceImageQuality), Format: config.AppConfig.ServiceImageFormat, Mode: config.AppConfig.ServiceImageMode, }) if saveErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()}) return } imagePtr = &imageURL } item, err := h.serviceService.UpdateService( id, titlePtr, contentPtr, imagePtr, slugPtr, isActivePtr, ) if err != nil { status := http.StatusInternalServerError if err.Error() == "service not found" { status = http.StatusNotFound } else if err.Error() == "slug already exists" || err.Error() == "slug cannot be empty" { status = http.StatusBadRequest } c.JSON(status, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, item) } // DeleteService godoc // @Summary Delete a service (Admin) // @Description Delete a service by ID // @Tags admin // @Security ApiKeyAuth // @Param id path string true "Service ID" // @Success 200 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /admin/services/{id} [delete] func (h *ServiceHandler) DeleteService(c *gin.Context) { id := c.Param("id") if err := h.serviceService.DeleteService(id); err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Service deleted successfully"}) }