feat(channel): integrate channel functionality into post management
All checks were successful
Build Backend / build (push) Successful in 19m14s
Build Backend / build-docker (push) Successful in 1m28s

- Added Channel support in Post model, including ChannelID field in PostRequest and PostResponse DTOs.
- Updated PostService and PostRepository to handle channel-specific logic for creating and listing posts.
- Introduced ChannelRepository and ChannelService, along with corresponding handlers for managing channels.
- Enhanced router to include routes for channel management and public channel listing.
- Seeded default channels in the database during initialization.
This commit is contained in:
lafay
2026-03-24 22:27:53 +08:00
parent 15f0f1605e
commit fc0d6ff19d
19 changed files with 416 additions and 42 deletions

View File

@@ -0,0 +1,123 @@
package handler
import (
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/service"
"github.com/gin-gonic/gin"
)
type ChannelHandler struct {
channelService service.ChannelService
}
func NewChannelHandler(channelService service.ChannelService) *ChannelHandler {
return &ChannelHandler{channelService: channelService}
}
type channelResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description,omitempty"`
SortOrder int `json:"sort_order"`
IsActive bool `json:"is_active"`
}
func toChannelResponse(c *model.Channel) *channelResponse {
if c == nil {
return nil
}
return &channelResponse{
ID: c.ID,
Name: c.Name,
Slug: c.Slug,
Description: c.Description,
SortOrder: c.SortOrder,
IsActive: c.IsActive,
}
}
// ListPublic 公开频道列表(仅启用)
func (h *ChannelHandler) ListPublic(c *gin.Context) {
items, err := h.channelService.ListActive()
if err != nil {
response.InternalServerError(c, "failed to get channels")
return
}
result := make([]*channelResponse, 0, len(items))
for _, item := range items {
result = append(result, toChannelResponse(item))
}
response.Success(c, gin.H{"list": result})
}
// AdminList 管理端频道列表(全部)
func (h *ChannelHandler) AdminList(c *gin.Context) {
items, err := h.channelService.ListAll()
if err != nil {
response.InternalServerError(c, "failed to get channels")
return
}
result := make([]*channelResponse, 0, len(items))
for _, item := range items {
result = append(result, toChannelResponse(item))
}
response.Success(c, gin.H{"list": result})
}
type adminChannelUpsertRequest struct {
Name string `json:"name" binding:"required,max=100"`
Slug string `json:"slug" binding:"required,max=100"`
Description string `json:"description" binding:"max=255"`
SortOrder int `json:"sort_order"`
IsActive *bool `json:"is_active"`
}
func (h *ChannelHandler) AdminCreate(c *gin.Context) {
var req adminChannelUpsertRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
isActive := true
if req.IsActive != nil {
isActive = *req.IsActive
}
item, err := h.channelService.Create(req.Name, req.Slug, req.Description, req.SortOrder, isActive)
if err != nil {
response.InternalServerError(c, "failed to create channel")
return
}
response.Success(c, toChannelResponse(item))
}
func (h *ChannelHandler) AdminUpdate(c *gin.Context) {
id := c.Param("id")
var req adminChannelUpsertRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
isActive := true
if req.IsActive != nil {
isActive = *req.IsActive
}
item, err := h.channelService.Update(id, req.Name, req.Slug, req.Description, req.SortOrder, isActive)
if err != nil {
response.InternalServerError(c, "failed to update channel")
return
}
response.Success(c, toChannelResponse(item))
}
func (h *ChannelHandler) AdminDelete(c *gin.Context) {
id := c.Param("id")
if err := h.channelService.Delete(id); err != nil {
response.InternalServerError(c, "failed to delete channel")
return
}
response.Success(c, gin.H{"success": true})
}

View File

@@ -38,9 +38,10 @@ func (h *PostHandler) Create(c *gin.Context) {
}
type CreateRequest struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
ChannelID *string `json:"channel_id,omitempty"`
}
var req CreateRequest
@@ -49,7 +50,7 @@ func (h *PostHandler) Create(c *gin.Context) {
return
}
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, req.Content, req.Images)
post, err := h.postService.Create(c.Request.Context(), userID, req.Title, req.Content, req.Images, req.ChannelID)
if err != nil {
var moderationErr *service.PostModerationRejectedError
if errors.As(err, &moderationErr) {
@@ -101,6 +102,7 @@ func (h *PostHandler) GetByID(c *gin.Context) {
responseData := &dto.PostResponse{
ID: post.ID,
UserID: post.UserID,
ChannelID: post.ChannelID,
Title: post.Title,
Content: post.Content,
Images: dto.ConvertPostImagesToResponse(post.Images),
@@ -197,6 +199,10 @@ func (h *PostHandler) List(c *gin.Context) {
page, pageSize = normalizePagination(page, pageSize)
userID := c.Query("user_id")
tab := c.Query("tab")
var channelID *string
if cid := c.Query("channel_id"); cid != "" {
channelID = &cid
}
// 获取当前用户ID
currentUserID := c.GetString("user_id")
@@ -220,16 +226,16 @@ func (h *PostHandler) List(c *gin.Context) {
case "latest":
// 最新帖子
if userID != "" && userID == currentUserID {
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID)
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID, channelID)
} else {
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID)
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID, channelID)
}
default:
// 默认获取最新帖子
if userID != "" && userID == currentUserID {
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID)
posts, total, err = h.postService.GetLatestPostsForOwner(c.Request.Context(), page, pageSize, userID, channelID)
} else {
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID)
posts, total, err = h.postService.GetLatestPosts(c.Request.Context(), page, pageSize, userID, channelID)
}
}
@@ -256,6 +262,10 @@ func (h *PostHandler) ListByCursor(c *gin.Context) {
userID := c.Query("user_id")
currentUserID := c.GetString("user_id")
tab := c.Query("tab")
var channelID *string
if cid := c.Query("channel_id"); cid != "" {
channelID = &cid
}
// 解析游标分页请求
req := h.parseCursorRequest(c)
@@ -280,10 +290,10 @@ func (h *PostHandler) ListByCursor(c *gin.Context) {
result, err = h.postService.GetHotPostsByCursor(c.Request.Context(), req)
case "latest":
// 最新帖子
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, req)
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, channelID, req)
default:
// 默认获取最新帖子
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, req)
result, err = h.postService.ListByCursor(c.Request.Context(), userID, includePending, channelID, req)
}
if err != nil {