feat(channel): integrate channel functionality into post management
- 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:
@@ -322,7 +322,7 @@ func convertPostToAdminDetailResponse(post *model.Post) *dto.AdminPostDetailResp
|
||||
return &dto.AdminPostDetailResponse{
|
||||
ID: post.ID,
|
||||
UserID: post.UserID,
|
||||
CommunityID: post.CommunityID,
|
||||
ChannelID: post.ChannelID,
|
||||
Title: post.Title,
|
||||
Content: post.Content,
|
||||
Images: images,
|
||||
|
||||
65
internal/service/channel_service.go
Normal file
65
internal/service/channel_service.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
type ChannelService interface {
|
||||
ListActive() ([]*model.Channel, error)
|
||||
ListAll() ([]*model.Channel, error)
|
||||
Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error)
|
||||
Update(id, name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error)
|
||||
Delete(id string) error
|
||||
}
|
||||
|
||||
type channelServiceImpl struct {
|
||||
channelRepo *repository.ChannelRepository
|
||||
}
|
||||
|
||||
func NewChannelService(channelRepo *repository.ChannelRepository) ChannelService {
|
||||
return &channelServiceImpl{channelRepo: channelRepo}
|
||||
}
|
||||
|
||||
func (s *channelServiceImpl) ListActive() ([]*model.Channel, error) {
|
||||
return s.channelRepo.ListActive()
|
||||
}
|
||||
|
||||
func (s *channelServiceImpl) ListAll() ([]*model.Channel, error) {
|
||||
return s.channelRepo.ListAll()
|
||||
}
|
||||
|
||||
func (s *channelServiceImpl) Create(name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) {
|
||||
channel := &model.Channel{
|
||||
Name: name,
|
||||
Slug: slug,
|
||||
Description: description,
|
||||
SortOrder: sortOrder,
|
||||
IsActive: isActive,
|
||||
}
|
||||
if err := s.channelRepo.Create(channel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (s *channelServiceImpl) Update(id, name, slug, description string, sortOrder int, isActive bool) (*model.Channel, error) {
|
||||
channel, err := s.channelRepo.GetByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channel.Name = name
|
||||
channel.Slug = slug
|
||||
channel.Description = description
|
||||
channel.SortOrder = sortOrder
|
||||
channel.IsActive = isActive
|
||||
if err := s.channelRepo.Update(channel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (s *channelServiceImpl) Delete(id string) error {
|
||||
return s.channelRepo.Delete(id)
|
||||
}
|
||||
|
||||
@@ -25,22 +25,22 @@ const (
|
||||
// PostService 帖子服务接口
|
||||
type PostService interface {
|
||||
// 帖子CRUD
|
||||
Create(ctx context.Context, userID, title, content string, images []string) (*model.Post, error)
|
||||
Create(ctx context.Context, userID, title, content string, images []string, channelID *string) (*model.Post, error)
|
||||
GetByID(ctx context.Context, id string) (*model.Post, error)
|
||||
Update(ctx context.Context, post *model.Post) error
|
||||
UpdateWithImages(ctx context.Context, post *model.Post, images *[]string) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 帖子列表
|
||||
List(ctx context.Context, page, pageSize int, userID string, includePending bool) ([]*model.Post, int64, error)
|
||||
GetLatestPosts(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error)
|
||||
GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error)
|
||||
List(ctx context.Context, page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error)
|
||||
GetLatestPosts(ctx context.Context, page, pageSize int, userID string, channelID *string) ([]*model.Post, int64, error)
|
||||
GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string, channelID *string) ([]*model.Post, int64, error)
|
||||
GetUserPosts(ctx context.Context, userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error)
|
||||
GetFavorites(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
|
||||
// 游标分页方法
|
||||
ListByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
ListByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
SearchByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
@@ -106,12 +106,13 @@ type PostListResult struct {
|
||||
}
|
||||
|
||||
// Create 创建帖子
|
||||
func (s *postServiceImpl) Create(ctx context.Context, userID, title, content string, images []string) (*model.Post, error) {
|
||||
func (s *postServiceImpl) Create(ctx context.Context, userID, title, content string, images []string, channelID *string) (*model.Post, error) {
|
||||
post := &model.Post{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Status: model.PostStatusPending,
|
||||
UserID: userID,
|
||||
ChannelID: channelID,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Status: model.PostStatusPending,
|
||||
}
|
||||
|
||||
err := s.postRepo.Create(post, images)
|
||||
@@ -288,7 +289,7 @@ func (s *postServiceImpl) Delete(ctx context.Context, id string) error {
|
||||
}
|
||||
|
||||
// List 获取帖子列表(带缓存)
|
||||
func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID string, includePending bool) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) {
|
||||
cacheSettings := cache.GetSettings()
|
||||
postListTTL := cacheSettings.PostListTTL
|
||||
if postListTTL <= 0 {
|
||||
@@ -308,6 +309,9 @@ func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID s
|
||||
if includePending && userID != "" {
|
||||
visibilityUserKey = "owner:" + userID
|
||||
}
|
||||
if channelID != nil && *channelID != "" {
|
||||
visibilityUserKey += ":channel:" + *channelID
|
||||
}
|
||||
cacheKey := cache.PostListKey("latest", visibilityUserKey, page, pageSize)
|
||||
|
||||
result, err := cache.GetOrLoadTyped(
|
||||
@@ -317,7 +321,7 @@ func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID s
|
||||
jitter,
|
||||
nullTTL,
|
||||
func() (*PostListResult, error) {
|
||||
posts, total, err := s.postRepo.List(page, pageSize, userID, includePending)
|
||||
posts, total, err := s.postRepo.List(page, pageSize, userID, includePending, channelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -341,7 +345,7 @@ func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID s
|
||||
}
|
||||
}
|
||||
if missingAuthor {
|
||||
posts, total, loadErr := s.postRepo.List(page, pageSize, userID, includePending)
|
||||
posts, total, loadErr := s.postRepo.List(page, pageSize, userID, includePending, channelID)
|
||||
if loadErr != nil {
|
||||
return nil, 0, loadErr
|
||||
}
|
||||
@@ -353,13 +357,13 @@ func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID s
|
||||
}
|
||||
|
||||
// GetLatestPosts 获取最新帖子(语义化别名)
|
||||
func (s *postServiceImpl) GetLatestPosts(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, false)
|
||||
func (s *postServiceImpl) GetLatestPosts(ctx context.Context, page, pageSize int, userID string, channelID *string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, false, channelID)
|
||||
}
|
||||
|
||||
// GetLatestPostsForOwner 获取作者视角帖子列表(包含待审核)
|
||||
func (s *postServiceImpl) GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, true)
|
||||
func (s *postServiceImpl) GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string, channelID *string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, true, channelID)
|
||||
}
|
||||
|
||||
// GetUserPosts 获取用户帖子
|
||||
@@ -647,11 +651,11 @@ func (s *postServiceImpl) DeletePostWithTransaction(ctx context.Context, postID
|
||||
// ========== 游标分页方法 ==========
|
||||
|
||||
// ListByCursor 游标分页获取帖子列表
|
||||
func (s *postServiceImpl) ListByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
func (s *postServiceImpl) ListByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
return s.postRepo.GetPostsByCursor(ctx, userID, includePending, req)
|
||||
return s.postRepo.GetPostsByCursor(ctx, userID, includePending, channelID, req)
|
||||
}
|
||||
|
||||
// SearchByCursor 游标分页搜索帖子
|
||||
|
||||
@@ -52,12 +52,12 @@ func (s *VoteService) CreateVotePost(ctx context.Context, userID string, req *dt
|
||||
|
||||
// 创建普通帖子(设置IsVote=true)
|
||||
post := &model.Post{
|
||||
UserID: userID,
|
||||
CommunityID: req.CommunityID,
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Status: model.PostStatusPending,
|
||||
IsVote: true,
|
||||
UserID: userID,
|
||||
ChannelID: req.ChannelID,
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Status: model.PostStatusPending,
|
||||
IsVote: true,
|
||||
}
|
||||
|
||||
err := s.postRepo.Create(post, req.Images)
|
||||
|
||||
Reference in New Issue
Block a user