first commit
This commit is contained in:
235
app/blog/handlers/category_handler.go
Normal file
235
app/blog/handlers/category_handler.go
Normal 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"})
|
||||
}
|
||||
111
app/blog/handlers/category_view_handler.go
Normal file
111
app/blog/handlers/category_view_handler.go
Normal 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,
|
||||
})
|
||||
}
|
||||
245
app/blog/handlers/comment_handler.go
Normal file
245
app/blog/handlers/comment_handler.go
Normal 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"})
|
||||
}
|
||||
306
app/blog/handlers/post_handler.go
Normal file
306
app/blog/handlers/post_handler.go
Normal 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"})
|
||||
}
|
||||
220
app/blog/handlers/tag_handler.go
Normal file
220
app/blog/handlers/tag_handler.go
Normal 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"})
|
||||
}
|
||||
107
app/blog/services/category_service.go
Normal file
107
app/blog/services/category_service.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gobeyhan/database"
|
||||
"gobeyhan/database/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CategoryService struct{}
|
||||
|
||||
func NewCategoryService() *CategoryService {
|
||||
return &CategoryService{}
|
||||
}
|
||||
|
||||
// GetAllCategories retrieves all categories, optionally filtering by active status
|
||||
func (s *CategoryService) GetAllCategories(activeOnly bool) ([]models.Category, error) {
|
||||
var categories []models.Category
|
||||
query := database.DB.Preload("Parent").Preload("Children")
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
err := query.Order("`order` ASC, created_at DESC").Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// GetCategoryByID retrieves a category by ID with parent and children relationships
|
||||
func (s *CategoryService) GetCategoryByID(id uint64) (*models.Category, error) {
|
||||
var category models.Category
|
||||
err := database.DB.
|
||||
Preload("Parent").
|
||||
Preload("Children").
|
||||
First(&category, id).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &category, nil
|
||||
}
|
||||
|
||||
// GetCategoryBySlug retrieves a category by slug
|
||||
func (s *CategoryService) GetCategoryBySlug(slug string) (*models.Category, error) {
|
||||
var category models.Category
|
||||
err := database.DB.
|
||||
Preload("Parent").
|
||||
Preload("Children").
|
||||
Where("slug = ?", slug).
|
||||
First(&category).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &category, nil
|
||||
}
|
||||
|
||||
// CreateCategory creates a new category
|
||||
func (s *CategoryService) CreateCategory(category *models.Category) error {
|
||||
return database.DB.Create(category).Error
|
||||
}
|
||||
|
||||
// UpdateCategory updates an existing category
|
||||
func (s *CategoryService) UpdateCategory(id uint64, updates map[string]interface{}) error {
|
||||
result := database.DB.Model(&models.Category{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCategory deletes a category by ID
|
||||
func (s *CategoryService) DeleteCategory(id uint64) error {
|
||||
result := database.DB.Delete(&models.Category{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCategoriesByParent retrieves child categories of a parent
|
||||
func (s *CategoryService) GetCategoriesByParent(parentID uint64, activeOnly bool) ([]models.Category, error) {
|
||||
var categories []models.Category
|
||||
query := database.DB.Where("parent_id = ?", parentID)
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
err := query.Order("`order` ASC, created_at DESC").Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
67
app/blog/services/category_view_service.go
Normal file
67
app/blog/services/category_view_service.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"gobeyhan/database"
|
||||
"gobeyhan/database/models"
|
||||
)
|
||||
|
||||
type CategoryViewService struct{}
|
||||
|
||||
func NewCategoryViewService() *CategoryViewService {
|
||||
return &CategoryViewService{}
|
||||
}
|
||||
|
||||
// TrackCategoryView records a category view
|
||||
func (s *CategoryViewService) TrackCategoryView(categoryID uint64, ipAddress, userAgent string) error {
|
||||
view := &models.CategoryView{
|
||||
CategoryID: categoryID,
|
||||
IPAddress: ipAddress,
|
||||
UserAgent: userAgent,
|
||||
}
|
||||
return database.DB.Create(view).Error
|
||||
}
|
||||
|
||||
// GetCategoryViewCount gets total view count for a category
|
||||
func (s *CategoryViewService) GetCategoryViewCount(categoryID uint64) (int64, error) {
|
||||
var count int64
|
||||
err := database.DB.Model(&models.CategoryView{}).
|
||||
Where("category_id = ?", categoryID).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetAllCategoryViews gets all views for a category with pagination
|
||||
func (s *CategoryViewService) GetAllCategoryViews(page, limit int) ([]models.CategoryView, int64, error) {
|
||||
var views []models.CategoryView
|
||||
var total int64
|
||||
|
||||
query := database.DB.Preload("Category")
|
||||
|
||||
query.Model(&models.CategoryView{}).Count(&total)
|
||||
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("created_at DESC").
|
||||
Find(&views).Error
|
||||
|
||||
return views, total, err
|
||||
}
|
||||
|
||||
// GetViewsByCategory gets views for a specific category
|
||||
func (s *CategoryViewService) GetViewsByCategory(categoryID uint64, page, limit int) ([]models.CategoryView, int64, error) {
|
||||
var views []models.CategoryView
|
||||
var total int64
|
||||
|
||||
query := database.DB.Where("category_id = ?", categoryID)
|
||||
|
||||
query.Model(&models.CategoryView{}).Count(&total)
|
||||
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("created_at DESC").
|
||||
Find(&views).Error
|
||||
|
||||
return views, total, err
|
||||
}
|
||||
115
app/blog/services/comment_service.go
Normal file
115
app/blog/services/comment_service.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gobeyhan/database"
|
||||
"gobeyhan/database/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentService struct{}
|
||||
|
||||
func NewCommentService() *CommentService {
|
||||
return &CommentService{}
|
||||
}
|
||||
|
||||
// GetCommentsByPost retrieves comments for a specific post
|
||||
func (s *CommentService) GetCommentsByPost(postID uint64, activeOnly bool) ([]models.Comment, error) {
|
||||
var comments []models.Comment
|
||||
query := database.DB.
|
||||
Where("product_id = ?", postID).
|
||||
Preload("Parent").
|
||||
Preload("Children")
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
err := query.Order("created_at DESC").Find(&comments).Error
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// GetAllComments retrieves all comments with pagination
|
||||
func (s *CommentService) GetAllComments(page, limit int, activeOnly bool) ([]models.Comment, int64, error) {
|
||||
var comments []models.Comment
|
||||
var total int64
|
||||
|
||||
query := database.DB.
|
||||
Preload("Product").
|
||||
Preload("Parent").
|
||||
Preload("Children")
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
query.Model(&models.Comment{}).Count(&total)
|
||||
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("created_at DESC").
|
||||
Find(&comments).Error
|
||||
|
||||
return comments, total, err
|
||||
}
|
||||
|
||||
// GetCommentByID retrieves a comment by ID
|
||||
func (s *CommentService) GetCommentByID(id uint64) (*models.Comment, error) {
|
||||
var comment models.Comment
|
||||
err := database.DB.
|
||||
Preload("Product").
|
||||
Preload("Parent").
|
||||
Preload("Children").
|
||||
First(&comment, id).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
// CreateComment creates a new comment
|
||||
func (s *CommentService) CreateComment(comment *models.Comment) error {
|
||||
return database.DB.Create(comment).Error
|
||||
}
|
||||
|
||||
// UpdateComment updates an existing comment
|
||||
func (s *CommentService) UpdateComment(id uint64, updates map[string]interface{}) error {
|
||||
result := database.DB.Model(&models.Comment{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteComment deletes a comment by ID
|
||||
func (s *CommentService) DeleteComment(id uint64) error {
|
||||
result := database.DB.Delete(&models.Comment{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCommentReplies retrieves replies to a specific comment
|
||||
func (s *CommentService) GetCommentReplies(commentID uint64) ([]models.Comment, error) {
|
||||
var replies []models.Comment
|
||||
err := database.DB.
|
||||
Where("parent_id = ? AND is_active = ?", commentID, true).
|
||||
Order("created_at ASC").
|
||||
Find(&replies).Error
|
||||
|
||||
return replies, err
|
||||
}
|
||||
202
app/blog/services/post_service.go
Normal file
202
app/blog/services/post_service.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gobeyhan/database"
|
||||
"gobeyhan/database/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PostService struct{}
|
||||
|
||||
func NewPostService() *PostService {
|
||||
return &PostService{}
|
||||
}
|
||||
|
||||
// GetAllPosts retrieves all posts with pagination, optionally filtering by active status
|
||||
func (s *PostService) GetAllPosts(page, limit int, activeOnly bool) ([]models.Post, int64, error) {
|
||||
var posts []models.Post
|
||||
var total int64
|
||||
|
||||
query := database.DB.
|
||||
Preload("User").
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Parent").
|
||||
Preload("Children")
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
// Count total
|
||||
query.Model(&models.Post{}).Count(&total)
|
||||
|
||||
// Get paginated results
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("created_at DESC").
|
||||
Find(&posts).Error
|
||||
|
||||
return posts, total, err
|
||||
}
|
||||
|
||||
// GetPostByID retrieves a post by ID with all relationships
|
||||
func (s *PostService) GetPostByID(id uint64) (*models.Post, error) {
|
||||
var post models.Post
|
||||
err := database.DB.
|
||||
Preload("User").
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Parent").
|
||||
Preload("Children").
|
||||
First(&post, id).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// GetPostBySlug retrieves a post by slug
|
||||
func (s *PostService) GetPostBySlug(slug string) (*models.Post, error) {
|
||||
var post models.Post
|
||||
err := database.DB.
|
||||
Preload("User").
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Parent").
|
||||
Preload("Children").
|
||||
Where("slug = ? AND is_active = ?", slug, true).
|
||||
First(&post).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// CreatePost creates a new post
|
||||
func (s *PostService) CreatePost(post *models.Post) error {
|
||||
return database.DB.Create(post).Error
|
||||
}
|
||||
|
||||
// UpdatePost updates an existing post
|
||||
func (s *PostService) UpdatePost(id uint64, updates map[string]interface{}) error {
|
||||
// Handle many-to-many relationships separately if they're in updates
|
||||
var categoryIDs []*models.Category
|
||||
var tagIDs []*models.Tag
|
||||
|
||||
if categories, ok := updates["categories"]; ok {
|
||||
if catSlice, ok := categories.([]*models.Category); ok {
|
||||
categoryIDs = catSlice
|
||||
delete(updates, "categories")
|
||||
}
|
||||
}
|
||||
|
||||
if tags, ok := updates["tags"]; ok {
|
||||
if tagSlice, ok := tags.([]*models.Tag); ok {
|
||||
tagIDs = tagSlice
|
||||
delete(updates, "tags")
|
||||
}
|
||||
}
|
||||
|
||||
// Update basic fields
|
||||
result := database.DB.Model(&models.Post{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// Update relationships if provided
|
||||
if len(categoryIDs) > 0 || len(tagIDs) > 0 {
|
||||
var post models.Post
|
||||
if err := database.DB.First(&post, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(categoryIDs) > 0 {
|
||||
if err := database.DB.Model(&post).Association("Categories").Replace(categoryIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagIDs) > 0 {
|
||||
if err := database.DB.Model(&post).Association("Tags").Replace(tagIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePost deletes a post by ID
|
||||
func (s *PostService) DeletePost(id uint64) error {
|
||||
result := database.DB.Delete(&models.Post{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPostsByCategory retrieves posts by category ID
|
||||
func (s *PostService) GetPostsByCategory(categoryID uint64, page, limit int) ([]models.Post, int64, error) {
|
||||
var posts []models.Post
|
||||
var total int64
|
||||
|
||||
query := database.DB.
|
||||
Joins("JOIN post_categories ON post_categories.post_id = posts.id").
|
||||
Where("post_categories.category_id = ? AND posts.is_active = ?", categoryID, true).
|
||||
Preload("User").
|
||||
Preload("Categories").
|
||||
Preload("Tags")
|
||||
|
||||
query.Model(&models.Post{}).Count(&total)
|
||||
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("posts.created_at DESC").
|
||||
Find(&posts).Error
|
||||
|
||||
return posts, total, err
|
||||
}
|
||||
|
||||
// GetPostsByTag retrieves posts by tag ID
|
||||
func (s *PostService) GetPostsByTag(tagID uint64, page, limit int) ([]models.Post, int64, error) {
|
||||
var posts []models.Post
|
||||
var total int64
|
||||
|
||||
query := database.DB.
|
||||
Joins("JOIN post_tags ON post_tags.post_id = posts.id").
|
||||
Where("post_tags.tag_id = ? AND posts.is_active = ?", tagID, true).
|
||||
Preload("User").
|
||||
Preload("Categories").
|
||||
Preload("Tags")
|
||||
|
||||
query.Model(&models.Post{}).Count(&total)
|
||||
|
||||
err := query.
|
||||
Offset((page - 1) * limit).
|
||||
Limit(limit).
|
||||
Order("posts.created_at DESC").
|
||||
Find(&posts).Error
|
||||
|
||||
return posts, total, err
|
||||
}
|
||||
87
app/blog/services/tag_service.go
Normal file
87
app/blog/services/tag_service.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gobeyhan/database"
|
||||
"gobeyhan/database/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TagService struct{}
|
||||
|
||||
func NewTagService() *TagService {
|
||||
return &TagService{}
|
||||
}
|
||||
|
||||
// GetAllTags retrieves all tags, optionally filtering by active status
|
||||
func (s *TagService) GetAllTags(activeOnly bool) ([]models.Tag, error) {
|
||||
var tags []models.Tag
|
||||
query := database.DB
|
||||
|
||||
if activeOnly {
|
||||
query = query.Where("is_active = ?", true)
|
||||
}
|
||||
|
||||
err := query.Order("tag ASC").Find(&tags).Error
|
||||
return tags, err
|
||||
}
|
||||
|
||||
// GetTagByID retrieves a tag by ID
|
||||
func (s *TagService) GetTagByID(id uint64) (*models.Tag, error) {
|
||||
var tag models.Tag
|
||||
err := database.DB.First(&tag, id).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// GetTagBySlug retrieves a tag by slug
|
||||
func (s *TagService) GetTagBySlug(slug string) (*models.Tag, error) {
|
||||
var tag models.Tag
|
||||
err := database.DB.Where("slug = ?", slug).First(&tag).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// CreateTag creates a new tag
|
||||
func (s *TagService) CreateTag(tag *models.Tag) error {
|
||||
return database.DB.Create(tag).Error
|
||||
}
|
||||
|
||||
// UpdateTag updates an existing tag
|
||||
func (s *TagService) UpdateTag(id uint64, updates map[string]interface{}) error {
|
||||
result := database.DB.Model(&models.Tag{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag by ID
|
||||
func (s *TagService) DeleteTag(id uint64) error {
|
||||
result := database.DB.Delete(&models.Tag{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user