first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:43:40 +03:00
commit f34e54c5a5
100 changed files with 27342 additions and 0 deletions

View File

@@ -0,0 +1,235 @@
package handlers
import (
"gobeyhan/app/blog/services"
"gobeyhan/database/models"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type CategoryHandler struct {
service *services.CategoryService
}
func NewCategoryHandler(service *services.CategoryService) *CategoryHandler {
return &CategoryHandler{service: service}
}
// GetAllCategories godoc
// @Summary Get all active categories
// @Description Get list of all active categories (public endpoint)
// @Tags categories
// @Accept json
// @Produce json
// @Success 200 {array} models.Category
// @Router /api/v1/categories [get]
func (h *CategoryHandler) GetAllCategories(c *gin.Context) {
categories, err := h.service.GetAllCategories(true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": categories})
}
// GetCategoryBySlug godoc
// @Summary Get category by slug
// @Description Get a single category by its slug (public endpoint)
// @Tags categories
// @Accept json
// @Produce json
// @Param slug path string true "Category Slug"
// @Success 200 {object} models.Category
// @Router /api/v1/categories/{slug} [get]
func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) {
slug := c.Param("slug")
category, err := h.service.GetCategoryBySlug(slug)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if category == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": category})
}
// AdminGetAllCategories godoc
// @Summary Get all categories (Admin)
// @Description Get list of all categories including inactive ones
// @Tags admin,categories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} models.Category
// @Router /api/v1/admin/categories [get]
func (h *CategoryHandler) AdminGetAllCategories(c *gin.Context) {
categories, err := h.service.GetAllCategories(false)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": categories})
}
// GetCategoryByID godoc
// @Summary Get category by ID (Admin)
// @Description Get a single category by ID
// @Tags admin,categories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Category ID"
// @Success 200 {object} models.Category
// @Router /api/v1/admin/categories/{id} [get]
func (h *CategoryHandler) GetCategoryByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
return
}
category, err := h.service.GetCategoryByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if category == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": category})
}
// CreateCategory godoc
// @Summary Create a new category (Admin)
// @Description Create a new category
// @Tags admin,categories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param category body models.Category true "Category object"
// @Success 201 {object} models.Category
// @Router /api/v1/admin/categories [post]
func (h *CategoryHandler) CreateCategory(c *gin.Context) {
var input struct {
Title string `json:"title" binding:"required"`
Keywords string `json:"keywords"`
Desc string `json:"description"`
IsActive *bool `json:"is_active"`
Order *int `json:"order"`
Slug string `json:"slug"`
ParentID *uint64 `json:"parent_id"`
Image string `json:"image"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
category := &models.Category{
Title: input.Title,
Keywords: input.Keywords,
Desc: input.Desc,
Slug: input.Slug,
ParentID: input.ParentID,
Image: input.Image,
}
if input.IsActive != nil {
category.IsActive = *input.IsActive
} else {
category.IsActive = true
}
if input.Order != nil {
category.Order = *input.Order
} else {
category.Order = 1
}
if err := h.service.CreateCategory(category); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": category})
}
// UpdateCategory godoc
// @Summary Update a category (Admin)
// @Description Update an existing category
// @Tags admin,categories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Category ID"
// @Param category body models.Category true "Category object"
// @Success 200 {object} models.Category
// @Router /api/v1/admin/categories/{id} [put]
func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
return
}
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.UpdateCategory(id, input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Fetch updated category
category, err := h.service.GetCategoryByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": category})
}
// DeleteCategory godoc
// @Summary Delete a category (Admin)
// @Description Delete a category by ID
// @Tags admin,categories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Category ID"
// @Success 200 {object} map[string]string
// @Router /api/v1/admin/categories/{id} [delete]
func (h *CategoryHandler) DeleteCategory(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
return
}
if err := h.service.DeleteCategory(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"})
}

View File

@@ -0,0 +1,111 @@
package handlers
import (
"gobeyhan/app/blog/services"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type CategoryViewHandler struct {
service *services.CategoryViewService
}
func NewCategoryViewHandler(service *services.CategoryViewService) *CategoryViewHandler {
return &CategoryViewHandler{service: service}
}
// TrackCategoryView godoc
// @Summary Track a category view
// @Description Record a view event for a category (public endpoint)
// @Tags category-views
// @Accept json
// @Produce json
// @Param id path int true "Category ID"
// @Success 200 {object} map[string]string
// @Router /api/v1/categories/{id}/view [post]
func (h *CategoryViewHandler) TrackCategoryView(c *gin.Context) {
idStr := c.Param("id")
categoryID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
return
}
ipAddress := c.ClientIP()
userAgent := c.Request.UserAgent()
if err := h.service.TrackCategoryView(categoryID, ipAddress, userAgent); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "View tracked successfully"})
}
// AdminGetAllCategoryViews godoc
// @Summary Get all category views (Admin)
// @Description Get paginated list of all category views
// @Tags admin,category-views
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/admin/category-views [get]
func (h *CategoryViewHandler) AdminGetAllCategoryViews(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
views, total, err := h.service.GetAllCategoryViews(page, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": views,
"total": total,
"page": page,
"limit": limit,
})
}
// GetCategoryViewStats godoc
// @Summary Get view stats for a category (Admin)
// @Description Get view count and details for a specific category
// @Tags admin,category-views
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Category ID"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/admin/categories/{id}/views [get]
func (h *CategoryViewHandler) GetCategoryViewStats(c *gin.Context) {
idStr := c.Param("id")
categoryID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"})
return
}
count, err := h.service.GetCategoryViewCount(categoryID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"category_id": categoryID,
"view_count": count,
})
}

View File

@@ -0,0 +1,245 @@
package handlers
import (
"gobeyhan/app/blog/services"
"gobeyhan/database/models"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type CommentHandler struct {
service *services.CommentService
}
func NewCommentHandler(service *services.CommentService) *CommentHandler {
return &CommentHandler{service: service}
}
// GetPostComments godoc
// @Summary Get comments for a post
// @Description Get all active comments for a specific post (public endpoint)
// @Tags comments
// @Accept json
// @Produce json
// @Param id path int true "Post ID"
// @Success 200 {array} models.Comment
// @Router /api/v1/posts/{id}/comments [get]
func (h *CommentHandler) GetPostComments(c *gin.Context) {
idStr := c.Param("id")
postID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
return
}
comments, err := h.service.GetCommentsByPost(postID, true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": comments})
}
// CreatePostComment godoc
// @Summary Create a comment on a post
// @Description Create a new comment (requires authentication)
// @Tags comments
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Param comment body models.Comment true "Comment object"
// @Success 201 {object} models.Comment
// @Router /api/v1/posts/{id}/comments [post]
func (h *CommentHandler) CreatePostComment(c *gin.Context) {
idStr := c.Param("id")
postID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
return
}
// Get user ID from context (set by auth middleware)
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var input struct {
Title string `json:"title"`
Body string `json:"body" binding:"required"`
ParentID *uint64 `json:"parent_id"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Convert user_id to uint64
var uid uint64
switch v := userID.(type) {
case string:
uid, _ = strconv.ParseUint(v, 10, 64)
case uint64:
uid = v
case int:
uid = uint64(v)
case float64:
uid = uint64(v)
}
comment := &models.Comment{
UserID: uid,
ProductID: postID,
Title: input.Title,
Body: input.Body,
ParentID: input.ParentID,
IsActive: true,
}
if err := h.service.CreateComment(comment); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": comment})
}
// AdminGetAllComments godoc
// @Summary Get all comments (Admin)
// @Description Get paginated list of all comments
// @Tags admin,comments
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/admin/comments [get]
func (h *CommentHandler) AdminGetAllComments(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
comments, total, err := h.service.GetAllComments(page, limit, false)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": comments,
"total": total,
"page": page,
"limit": limit,
})
}
// AdminGetCommentByID godoc
// @Summary Get comment by ID (Admin)
// @Description Get a single comment by ID
// @Tags admin,comments
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Comment ID"
// @Success 200 {object} models.Comment
// @Router /api/v1/admin/comments/{id} [get]
func (h *CommentHandler) AdminGetCommentByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"})
return
}
comment, err := h.service.GetCommentByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if comment == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": comment})
}
// AdminUpdateComment godoc
// @Summary Update a comment (Admin)
// @Description Update an existing comment
// @Tags admin,comments
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Comment ID"
// @Param comment body models.Comment true "Comment object"
// @Success 200 {object} models.Comment
// @Router /api/v1/admin/comments/{id} [put]
func (h *CommentHandler) AdminUpdateComment(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"})
return
}
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.UpdateComment(id, input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Fetch updated comment
comment, err := h.service.GetCommentByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": comment})
}
// AdminDeleteComment godoc
// @Summary Delete a comment (Admin)
// @Description Delete a comment by ID
// @Tags admin,comments
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Comment ID"
// @Success 200 {object} map[string]string
// @Router /api/v1/admin/comments/{id} [delete]
func (h *CommentHandler) AdminDeleteComment(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"})
return
}
if err := h.service.DeleteComment(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Comment deleted successfully"})
}

View File

@@ -0,0 +1,306 @@
package handlers
import (
"gobeyhan/app/blog/services"
"gobeyhan/database/models"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type PostHandler struct {
service *services.PostService
}
func NewPostHandler(service *services.PostService) *PostHandler {
return &PostHandler{service: service}
}
// GetAllPosts godoc
// @Summary Get all active posts
// @Description Get paginated list of active posts (public endpoint)
// @Tags posts
// @Accept json
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/posts [get]
func (h *PostHandler) GetAllPosts(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
posts, total, err := h.service.GetAllPosts(page, limit, true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": posts,
"total": total,
"page": page,
"limit": limit,
})
}
// GetPostBySlug godoc
// @Summary Get post by slug
// @Description Get a single post by its slug (public endpoint)
// @Tags posts
// @Accept json
// @Produce json
// @Param slug path string true "Post Slug"
// @Success 200 {object} models.Post
// @Router /api/v1/posts/{slug} [get]
func (h *PostHandler) GetPostBySlug(c *gin.Context) {
slug := c.Param("slug")
post, err := h.service.GetPostBySlug(slug)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if post == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": post})
}
// AdminGetAllPosts godoc
// @Summary Get all posts (Admin)
// @Description Get paginated list of all posts including inactive
// @Tags admin,posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/admin/posts [get]
func (h *PostHandler) AdminGetAllPosts(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
posts, total, err := h.service.GetAllPosts(page, limit, false)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": posts,
"total": total,
"page": page,
"limit": limit,
})
}
// GetPostByID godoc
// @Summary Get post by ID (Admin)
// @Description Get a single post by ID
// @Tags admin,posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Success 200 {object} models.Post
// @Router /api/v1/admin/posts/{id} [get]
func (h *PostHandler) GetPostByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
return
}
post, err := h.service.GetPostByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if post == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": post})
}
// CreatePost godoc
// @Summary Create a new post (Admin)
// @Description Create a new post
// @Tags admin,posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param post body models.Post true "Post object"
// @Success 201 {object} models.Post
// @Router /api/v1/admin/posts [post]
func (h *PostHandler) CreatePost(c *gin.Context) {
var input struct {
Title string `json:"title" binding:"required"`
UserID *uint64 `json:"user_id"`
Content string `json:"content"`
Keywords string `json:"keywords"`
Image string `json:"image"`
Thumb string `json:"thumb"`
Video string `json:"video"`
Slug string `json:"slug"`
IsActive *bool `json:"is_active"`
IsFront *bool `json:"is_front"`
ParentID *uint64 `json:"parent_id"`
Categories []uint64 `json:"categories"`
Tags []uint64 `json:"tags"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
post := &models.Post{
Title: input.Title,
UserID: input.UserID,
Content: input.Content,
Keywords: input.Keywords,
Image: input.Image,
Thumb: input.Thumb,
Video: input.Video,
Slug: input.Slug,
ParentID: input.ParentID,
}
if input.IsActive != nil {
post.IsActive = *input.IsActive
} else {
post.IsActive = true
}
if input.IsFront != nil {
post.IsFront = *input.IsFront
} else {
post.IsFront = true
}
// Create post first
if err := h.service.CreatePost(post); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Add categories and tags if provided
if len(input.Categories) > 0 || len(input.Tags) > 0 {
updates := make(map[string]interface{})
if len(input.Categories) > 0 {
var categories []*models.Category
for _, catID := range input.Categories {
categories = append(categories, &models.Category{ID: catID})
}
updates["categories"] = categories
}
if len(input.Tags) > 0 {
var tags []*models.Tag
for _, tagID := range input.Tags {
tags = append(tags, &models.Tag{ID: tagID})
}
updates["tags"] = tags
}
if err := h.service.UpdatePost(post.ID, updates); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
// Fetch created post with relationships
createdPost, _ := h.service.GetPostByID(post.ID)
c.JSON(http.StatusCreated, gin.H{"data": createdPost})
}
// UpdatePost godoc
// @Summary Update a post (Admin)
// @Description Update an existing post
// @Tags admin,posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Param post body models.Post true "Post object"
// @Success 200 {object} models.Post
// @Router /api/v1/admin/posts/{id} [put]
func (h *PostHandler) UpdatePost(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
return
}
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.UpdatePost(id, input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Fetch updated post
post, err := h.service.GetPostByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": post})
}
// DeletePost godoc
// @Summary Delete a post (Admin)
// @Description Delete a post by ID
// @Tags admin,posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Success 200 {object} map[string]string
// @Router /api/v1/admin/posts/{id} [delete]
func (h *PostHandler) DeletePost(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"})
return
}
if err := h.service.DeletePost(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Post deleted successfully"})
}

View File

@@ -0,0 +1,220 @@
package handlers
import (
"gobeyhan/app/blog/services"
"gobeyhan/database/models"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type TagHandler struct {
service *services.TagService
}
func NewTagHandler(service *services.TagService) *TagHandler {
return &TagHandler{service: service}
}
// GetAllTags godoc
// @Summary Get all active tags
// @Description Get list of all active tags (public endpoint)
// @Tags tags
// @Accept json
// @Produce json
// @Success 200 {array} models.Tag
// @Router /api/v1/tags [get]
func (h *TagHandler) GetAllTags(c *gin.Context) {
tags, err := h.service.GetAllTags(true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": tags})
}
// GetTagBySlug godoc
// @Summary Get tag by slug
// @Description Get a single tag by its slug (public endpoint)
// @Tags tags
// @Accept json
// @Produce json
// @Param slug path string true "Tag Slug"
// @Success 200 {object} models.Tag
// @Router /api/v1/tags/{slug} [get]
func (h *TagHandler) GetTagBySlug(c *gin.Context) {
slug := c.Param("slug")
tag, err := h.service.GetTagBySlug(slug)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if tag == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": tag})
}
// AdminGetAllTags godoc
// @Summary Get all tags (Admin)
// @Description Get list of all tags including inactive ones
// @Tags admin,tags
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} models.Tag
// @Router /api/v1/admin/tags [get]
func (h *TagHandler) AdminGetAllTags(c *gin.Context) {
tags, err := h.service.GetAllTags(false)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": tags})
}
// GetTagByID godoc
// @Summary Get tag by ID (Admin)
// @Description Get a single tag by ID
// @Tags admin,tags
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Tag ID"
// @Success 200 {object} models.Tag
// @Router /api/v1/admin/tags/{id} [get]
func (h *TagHandler) GetTagByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"})
return
}
tag, err := h.service.GetTagByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if tag == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": tag})
}
// CreateTag godoc
// @Summary Create a new tag (Admin)
// @Description Create a new tag
// @Tags admin,tags
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param tag body models.Tag true "Tag object"
// @Success 201 {object} models.Tag
// @Router /api/v1/admin/tags [post]
func (h *TagHandler) CreateTag(c *gin.Context) {
var input struct {
Tag string `json:"tag" binding:"required"`
Slug string `json:"slug"`
IsActive *bool `json:"is_active"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tag := &models.Tag{
Tag: input.Tag,
Slug: input.Slug,
}
if input.IsActive != nil {
tag.IsActive = *input.IsActive
} else {
tag.IsActive = true
}
if err := h.service.CreateTag(tag); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": tag})
}
// UpdateTag godoc
// @Summary Update a tag (Admin)
// @Description Update an existing tag
// @Tags admin,tags
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Tag ID"
// @Param tag body models.Tag true "Tag object"
// @Success 200 {object} models.Tag
// @Router /api/v1/admin/tags/{id} [put]
func (h *TagHandler) UpdateTag(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"})
return
}
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.UpdateTag(id, input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Fetch updated tag
tag, err := h.service.GetTagByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": tag})
}
// DeleteTag godoc
// @Summary Delete a tag (Admin)
// @Description Delete a tag by ID
// @Tags admin,tags
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Tag ID"
// @Success 200 {object} map[string]string
// @Router /api/v1/admin/tags/{id} [delete]
func (h *TagHandler) DeleteTag(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"})
return
}
if err := h.service.DeleteTag(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Tag deleted successfully"})
}