Files
goaresv3/app/blog/controllers/blog.go
Beyhan Oğur b6e74bd024 first commit
2026-04-26 21:41:46 +03:00

426 lines
12 KiB
Go

package controllers
import (
"errors"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
blogModels "goaresv3/app/blog/models"
"goaresv3/config"
)
type UpsertCategoryRequest struct {
Title string `json:"title" binding:"required,max=254"`
Slug string `json:"slug" binding:"required,max=254"`
Description string `json:"description"`
ParentID *uint `json:"parent_id"`
}
type UpsertTagRequest struct {
Name string `json:"name" binding:"required,max=254"`
}
type UpsertPostRequest struct {
Title string `json:"title" binding:"required,max=254"`
Images string `json:"images" binding:"required"`
ImagesMid string `json:"images_mid" binding:"required"`
ImagesMin string `json:"images_min" binding:"required"`
Width int `json:"width"`
Height int `json:"height"`
Quality int `json:"quality"`
Format string `json:"format" binding:"omitempty,max=10"`
Content string `json:"content"`
Slug string `json:"slug" binding:"required,max=254"`
CategoryIDs []uint `json:"category_ids"`
TagIDs []uint `json:"tag_ids"`
}
func parseBlogID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(strings.TrimSpace(c.Param("id")), 10, 64)
if err != nil || id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return 0, false
}
return uint(id), true
}
// ListCategories godoc
// @Summary List blog categories
// @Tags Blog
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /api/v1/blog/categories [get]
func ListCategories(c *gin.Context) {
var items []blogModels.Category
if err := config.DB.Preload("Children").Order("id DESC").Find(&items).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch categories"})
return
}
c.JSON(http.StatusOK, items)
}
// CreateCategory godoc
// @Summary Create blog category
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body UpsertCategoryRequest true "category payload"
// @Success 201 {object} map[string]interface{}
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/v1/blog/categories [post]
func CreateCategory(c *gin.Context) {
var req UpsertCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item := blogModels.Category{
Title: req.Title,
Slug: req.Slug,
Description: req.Description,
ParentID: req.ParentID,
}
if err := config.DB.Create(&item).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to create category"})
return
}
c.JSON(http.StatusCreated, item)
}
// UpdateCategory godoc
// @Summary Update blog category
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param id path int true "category id"
// @Param request body UpsertCategoryRequest true "category payload"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/v1/blog/categories/{id} [put]
func UpdateCategory(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
var req UpsertCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var item blogModels.Category
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "category not found"})
return
}
if err := config.DB.Model(&item).Updates(map[string]any{
"title": req.Title,
"slug": req.Slug,
"description": req.Description,
"parent_id": req.ParentID,
}).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to update category"})
return
}
_ = config.DB.First(&item, id).Error
c.JSON(http.StatusOK, item)
}
// DeleteCategory godoc
// @Summary Delete blog category
// @Tags Blog
// @Security BearerAuth
// @Produce json
// @Param id path int true "category id"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /api/v1/blog/categories/{id} [delete]
func DeleteCategory(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
res := config.DB.Delete(&blogModels.Category{}, id)
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "category not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "category deleted"})
}
// ListTags godoc
// @Summary List blog tags
// @Tags Blog
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Router /api/v1/blog/tags [get]
func ListTags(c *gin.Context) {
var items []blogModels.Tag
if err := config.DB.Order("id DESC").Find(&items).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch tags"})
return
}
c.JSON(http.StatusOK, items)
}
// CreateTag godoc
// @Summary Create blog tag
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body UpsertTagRequest true "tag payload"
// @Success 201 {object} map[string]interface{}
// @Router /api/v1/blog/tags [post]
func CreateTag(c *gin.Context) {
var req UpsertTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item := blogModels.Tag{Name: req.Name}
if err := config.DB.Create(&item).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to create tag"})
return
}
c.JSON(http.StatusCreated, item)
}
// UpdateTag godoc
// @Summary Update blog tag
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param id path int true "tag id"
// @Param request body UpsertTagRequest true "tag payload"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/blog/tags/{id} [put]
func UpdateTag(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
var req UpsertTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var item blogModels.Tag
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "tag not found"})
return
}
if err := config.DB.Model(&item).Update("name", req.Name).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to update tag"})
return
}
_ = config.DB.First(&item, id).Error
c.JSON(http.StatusOK, item)
}
// DeleteTag godoc
// @Summary Delete blog tag
// @Tags Blog
// @Security BearerAuth
// @Produce json
// @Param id path int true "tag id"
// @Success 200 {object} map[string]string
// @Router /api/v1/blog/tags/{id} [delete]
func DeleteTag(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
res := config.DB.Delete(&blogModels.Tag{}, id)
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "tag not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "tag deleted"})
}
// ListPosts godoc
// @Summary List blog posts
// @Tags Blog
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Router /api/v1/blog/posts [get]
func ListPosts(c *gin.Context) {
var items []blogModels.Post
if err := config.DB.Preload("Categories").Preload("Tags").Order("id DESC").Find(&items).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch posts"})
return
}
c.JSON(http.StatusOK, items)
}
// GetPost godoc
// @Summary Get blog post
// @Tags Blog
// @Produce json
// @Param id path int true "post id"
// @Success 200 {object} map[string]interface{}
// @Failure 404 {object} map[string]string
// @Router /api/v1/blog/posts/{id} [get]
func GetPost(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
var item blogModels.Post
if err := config.DB.Preload("Categories").Preload("Tags").First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "post not found"})
return
}
c.JSON(http.StatusOK, item)
}
// CreatePost godoc
// @Summary Create blog post
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body UpsertPostRequest true "post payload"
// @Success 201 {object} map[string]interface{}
// @Router /api/v1/blog/posts [post]
func CreatePost(c *gin.Context) {
var req UpsertPostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item := blogModels.Post{
Title: req.Title,
Images: req.Images,
ImagesMid: req.ImagesMid,
ImagesMin: req.ImagesMin,
Width: req.Width,
Height: req.Height,
Quality: req.Quality,
Format: req.Format,
Content: req.Content,
Slug: req.Slug,
}
if err := config.DB.Create(&item).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to create post"})
return
}
if err := assignPostRelations(item.ID, req.CategoryIDs, req.TagIDs); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to assign relations"})
return
}
_ = config.DB.Preload("Categories").Preload("Tags").First(&item, item.ID).Error
c.JSON(http.StatusCreated, item)
}
// UpdatePost godoc
// @Summary Update blog post
// @Tags Blog
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param id path int true "post id"
// @Param request body UpsertPostRequest true "post payload"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/blog/posts/{id} [put]
func UpdatePost(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
var req UpsertPostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var item blogModels.Post
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "post not found"})
return
}
if err := config.DB.Model(&item).Updates(map[string]any{
"title": req.Title,
"images": req.Images,
"images_mid": req.ImagesMid,
"images_min": req.ImagesMin,
"width": req.Width,
"height": req.Height,
"quality": req.Quality,
"format": req.Format,
"content": req.Content,
"slug": req.Slug,
}).Error; err != nil {
c.JSON(http.StatusConflict, gin.H{"error": "failed to update post"})
return
}
if err := assignPostRelations(item.ID, req.CategoryIDs, req.TagIDs); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to assign relations"})
return
}
_ = config.DB.Preload("Categories").Preload("Tags").First(&item, item.ID).Error
c.JSON(http.StatusOK, item)
}
// DeletePost godoc
// @Summary Delete blog post
// @Tags Blog
// @Security BearerAuth
// @Produce json
// @Param id path int true "post id"
// @Success 200 {object} map[string]string
// @Router /api/v1/blog/posts/{id} [delete]
func DeletePost(c *gin.Context) {
id, ok := parseBlogID(c)
if !ok {
return
}
res := config.DB.Delete(&blogModels.Post{}, id)
if res.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "post not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "post deleted"})
}
func assignPostRelations(postID uint, categoryIDs, tagIDs []uint) error {
var p blogModels.Post
if err := config.DB.First(&p, postID).Error; err != nil {
return err
}
if categoryIDs != nil {
var categories []blogModels.Category
if len(categoryIDs) > 0 {
if err := config.DB.Where("id IN ?", categoryIDs).Find(&categories).Error; err != nil {
return err
}
}
if err := config.DB.Model(&p).Association("Categories").Replace(categories); err != nil {
return err
}
}
if tagIDs != nil {
var tags []blogModels.Tag
if len(tagIDs) > 0 {
if err := config.DB.Where("id IN ?", tagIDs).Find(&tags).Error; err != nil {
return err
}
}
if err := config.DB.Model(&p).Association("Tags").Replace(tags); err != nil {
return err
}
}
return nil
}