first commit
This commit is contained in:
359
api/handlers/post_category_handler.go
Normal file
359
api/handlers/post_category_handler.go
Normal file
@@ -0,0 +1,359 @@
|
||||
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"})
|
||||
}
|
||||
Reference in New Issue
Block a user