360 lines
10 KiB
Go
360 lines
10 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gauth-central/config"
|
|
"gauth-central/internal/services"
|
|
"gauth-central/pkg/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type PostCategoryHandler struct {
|
|
categoryService *services.PostCategoryService
|
|
}
|
|
|
|
func NewPostCategoryHandler(categoryService *services.PostCategoryService) *PostCategoryHandler {
|
|
return &PostCategoryHandler{categoryService: categoryService}
|
|
}
|
|
|
|
// GetAllPostCategories godoc
|
|
// @Summary Get all active post categories
|
|
// @Description Retrieve a list of active post categories
|
|
// @Tags post-categories
|
|
// @Produce json
|
|
// @Success 200 {array} models.PostCategory
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /post-categories [get]
|
|
// GetAllPostCategories returns active post categories.
|
|
func (h *PostCategoryHandler) GetAllPostCategories(c *gin.Context) {
|
|
items, err := h.categoryService.GetAllPostCategories(true)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, items)
|
|
}
|
|
|
|
// GetPostCategoryBySlug godoc
|
|
// @Summary Get post category by slug
|
|
// @Description Retrieve an active post category by slug
|
|
// @Tags post-categories
|
|
// @Produce json
|
|
// @Param slug path string true "Category Slug"
|
|
// @Success 200 {object} models.PostCategory
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /post-categories/{slug} [get]
|
|
// GetPostCategoryBySlug returns a post category by slug.
|
|
func (h *PostCategoryHandler) GetPostCategoryBySlug(c *gin.Context) {
|
|
slug := c.Param("slug")
|
|
item, err := h.categoryService.GetPostCategoryBySlug(slug, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
// AdminGetAllPostCategories godoc
|
|
// @Summary Get all post categories (Admin)
|
|
// @Description Retrieve a list of all post categories including inactive ones
|
|
// @Tags admin
|
|
// @Security ApiKeyAuth
|
|
// @Produce json
|
|
// @Success 200 {array} models.PostCategory
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /admin/post-categories [get]
|
|
// AdminGetAllPostCategories returns all categories.
|
|
func (h *PostCategoryHandler) AdminGetAllPostCategories(c *gin.Context) {
|
|
items, err := h.categoryService.GetAllPostCategories(false)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, items)
|
|
}
|
|
|
|
// AdminGetPostCategoryByID godoc
|
|
// @Summary Get post category by ID (Admin)
|
|
// @Description Retrieve a post category by ID
|
|
// @Tags admin
|
|
// @Security ApiKeyAuth
|
|
// @Produce json
|
|
// @Param id path string true "Category ID"
|
|
// @Success 200 {object} models.PostCategory
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /admin/post-categories/{id} [get]
|
|
// AdminGetPostCategoryByID returns a category by ID.
|
|
func (h *PostCategoryHandler) AdminGetPostCategoryByID(c *gin.Context) {
|
|
id := c.Param("id")
|
|
item, err := h.categoryService.GetPostCategoryByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
// CreatePostCategory godoc
|
|
// @Summary Create a post category (Admin)
|
|
// @Description Create a new post category
|
|
// @Tags admin
|
|
// @Security ApiKeyAuth
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param title formData string true "Title"
|
|
// @Param keywords formData string true "Keywords"
|
|
// @Param description formData string true "Description"
|
|
// @Param order formData int false "Order"
|
|
// @Param parent_id formData string false "Parent ID"
|
|
// @Param image formData file false "Image"
|
|
// @Param is_active formData bool false "Is active"
|
|
// @Success 201 {object} models.PostCategory
|
|
// @Failure 400 {object} map[string]string
|
|
// @Router /admin/post-categories [post]
|
|
// CreatePostCategory creates a new category.
|
|
func (h *PostCategoryHandler) CreatePostCategory(c *gin.Context) {
|
|
title := strings.TrimSpace(c.PostForm("title"))
|
|
keywords := strings.TrimSpace(c.PostForm("keywords"))
|
|
description := strings.TrimSpace(c.PostForm("description"))
|
|
if title == "" || keywords == "" || description == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "title, keywords, and description are required"})
|
|
return
|
|
}
|
|
|
|
order := 1
|
|
if rawOrder := strings.TrimSpace(c.PostForm("order")); rawOrder != "" {
|
|
parsed, err := strconv.Atoi(rawOrder)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "order must be an integer"})
|
|
return
|
|
}
|
|
order = parsed
|
|
}
|
|
|
|
parentID, err := parseUUIDPtr(c.PostForm("parent_id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"})
|
|
return
|
|
}
|
|
|
|
isActive := true
|
|
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
|
|
}
|
|
|
|
imageURL := ""
|
|
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
|
|
}
|
|
|
|
saved, saveErr := utils.SaveOptimizedImage(file, "./uploads/post-categories", "category", &utils.ImageOptions{
|
|
Width: config.AppConfig.PostCategoryImageWidth,
|
|
Height: config.AppConfig.PostCategoryImageHeight,
|
|
Quality: float32(config.AppConfig.PostCategoryImageQuality),
|
|
Format: config.AppConfig.PostCategoryImageFormat,
|
|
Mode: config.AppConfig.PostCategoryImageMode,
|
|
})
|
|
if saveErr != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()})
|
|
return
|
|
}
|
|
imageURL = saved
|
|
}
|
|
|
|
item, err := h.categoryService.CreatePostCategory(
|
|
title,
|
|
keywords,
|
|
description,
|
|
imageURL,
|
|
order,
|
|
parentID,
|
|
isActive,
|
|
)
|
|
if err != nil {
|
|
status := http.StatusInternalServerError
|
|
if err.Error() == "slug cannot be empty" || err.Error() == "slug already exists" {
|
|
status = http.StatusBadRequest
|
|
}
|
|
c.JSON(status, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, item)
|
|
}
|
|
|
|
// UpdatePostCategory godoc
|
|
// @Summary Update a post category (Admin)
|
|
// @Description Update an existing post category
|
|
// @Tags admin
|
|
// @Security ApiKeyAuth
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param id path string true "Category ID"
|
|
// @Param title formData string false "Title"
|
|
// @Param keywords formData string false "Keywords"
|
|
// @Param description formData string false "Description"
|
|
// @Param order formData int false "Order"
|
|
// @Param parent_id formData string false "Parent ID"
|
|
// @Param image formData file false "Image"
|
|
// @Param slug formData string false "Slug"
|
|
// @Param is_active formData bool false "Is active"
|
|
// @Success 200 {object} models.PostCategory
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /admin/post-categories/{id} [put]
|
|
// UpdatePostCategory updates a category.
|
|
func (h *PostCategoryHandler) UpdatePostCategory(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
title, hasTitle := getOptionalFormValue(c, "title")
|
|
keywords, hasKeywords := getOptionalFormValue(c, "keywords")
|
|
description, hasDescription := getOptionalFormValue(c, "description")
|
|
slug, hasSlug := getOptionalFormValue(c, "slug")
|
|
|
|
var titlePtr *string
|
|
var keywordsPtr *string
|
|
var descriptionPtr *string
|
|
var slugPtr *string
|
|
|
|
if hasTitle {
|
|
titlePtr = &title
|
|
}
|
|
if hasKeywords {
|
|
keywordsPtr = &keywords
|
|
}
|
|
if hasDescription {
|
|
descriptionPtr = &description
|
|
}
|
|
if hasSlug {
|
|
slugPtr = &slug
|
|
}
|
|
|
|
var orderPtr *int
|
|
if rawOrder, hasOrder := getOptionalFormValue(c, "order"); hasOrder {
|
|
if rawOrder == "" {
|
|
orderPtr = nil
|
|
} else {
|
|
parsed, err := strconv.Atoi(rawOrder)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "order must be an integer"})
|
|
return
|
|
}
|
|
orderPtr = &parsed
|
|
}
|
|
}
|
|
|
|
parentIDValue, hasParent := getOptionalFormValue(c, "parent_id")
|
|
parentIDSet := false
|
|
var parentIDPtr *uuid.UUID
|
|
if hasParent {
|
|
parentIDSet = true
|
|
if parentIDValue == "" {
|
|
parentIDPtr = nil
|
|
} else {
|
|
parsed, err := parseUUIDPtr(parentIDValue)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"})
|
|
return
|
|
}
|
|
parentIDPtr = parsed
|
|
}
|
|
}
|
|
|
|
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.categoryService.GetPostCategoryByID(id)
|
|
if fetchErr != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": fetchErr.Error()})
|
|
return
|
|
}
|
|
|
|
if item.Image != "" && strings.HasPrefix(item.Image, "/uploads/") {
|
|
_ = os.Remove("." + item.Image)
|
|
}
|
|
|
|
saved, saveErr := utils.SaveOptimizedImage(file, "./uploads/post-categories", id, &utils.ImageOptions{
|
|
Width: config.AppConfig.PostCategoryImageWidth,
|
|
Height: config.AppConfig.PostCategoryImageHeight,
|
|
Quality: float32(config.AppConfig.PostCategoryImageQuality),
|
|
Format: config.AppConfig.PostCategoryImageFormat,
|
|
Mode: config.AppConfig.PostCategoryImageMode,
|
|
})
|
|
if saveErr != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()})
|
|
return
|
|
}
|
|
imagePtr = &saved
|
|
}
|
|
|
|
item, err := h.categoryService.UpdatePostCategory(
|
|
id,
|
|
titlePtr,
|
|
keywordsPtr,
|
|
descriptionPtr,
|
|
imagePtr,
|
|
orderPtr,
|
|
parentIDPtr,
|
|
parentIDSet,
|
|
slugPtr,
|
|
isActivePtr,
|
|
)
|
|
if err != nil {
|
|
status := http.StatusInternalServerError
|
|
switch err.Error() {
|
|
case "post category not found":
|
|
status = http.StatusNotFound
|
|
case "slug cannot be empty", "slug already exists":
|
|
status = http.StatusBadRequest
|
|
}
|
|
c.JSON(status, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
// DeletePostCategory godoc
|
|
// @Summary Delete a post category (Admin)
|
|
// @Description Delete a post category by ID
|
|
// @Tags admin
|
|
// @Security ApiKeyAuth
|
|
// @Param id path string true "Category ID"
|
|
// @Success 200 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /admin/post-categories/{id} [delete]
|
|
// DeletePostCategory deletes a category by ID.
|
|
func (h *PostCategoryHandler) DeletePostCategory(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if err := h.categoryService.DeletePostCategory(id); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "Post category deleted successfully"})
|
|
}
|