802 lines
21 KiB
Go
802 lines
21 KiB
Go
package usecase
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"math"
|
||
|
||
errs "backend/pkg/library/errors"
|
||
"backend/pkg/post/domain/entity"
|
||
"backend/pkg/post/domain/post"
|
||
domainRepo "backend/pkg/post/domain/repository"
|
||
domainUsecase "backend/pkg/post/domain/usecase"
|
||
"backend/pkg/post/repository"
|
||
|
||
"github.com/gocql/gocql"
|
||
)
|
||
|
||
// PostUseCaseParam 定義 PostUseCase 的初始化參數
|
||
type PostUseCaseParam struct {
|
||
Post domainRepo.PostRepository
|
||
Comment domainRepo.CommentRepository
|
||
Like domainRepo.LikeRepository
|
||
Tag domainRepo.TagRepository
|
||
Category domainRepo.CategoryRepository
|
||
Logger errs.Logger
|
||
}
|
||
|
||
// PostUseCase 實作 domain usecase 介面
|
||
type PostUseCase struct {
|
||
PostUseCaseParam
|
||
}
|
||
|
||
// MustPostUseCase 創建新的 PostUseCase(如果失敗會 panic)
|
||
func MustPostUseCase(param PostUseCaseParam) domainUsecase.PostUseCase {
|
||
return &PostUseCase{
|
||
PostUseCaseParam: param,
|
||
}
|
||
}
|
||
|
||
// CreatePost 創建新貼文
|
||
func (uc *PostUseCase) CreatePost(ctx context.Context, req domainUsecase.CreatePostRequest) (*domainUsecase.PostResponse, error) {
|
||
// 驗證輸入
|
||
if err := uc.validateCreatePostRequest(req); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 建立貼文實體
|
||
post := &entity.Post{
|
||
AuthorUID: req.AuthorUID,
|
||
Title: req.Title,
|
||
Content: req.Content,
|
||
Type: req.Type,
|
||
CategoryID: req.CategoryID,
|
||
Tags: req.Tags,
|
||
Images: req.Images,
|
||
VideoURL: req.VideoURL,
|
||
LinkURL: req.LinkURL,
|
||
Status: req.Status,
|
||
}
|
||
|
||
// 如果狀態未指定,預設為草稿
|
||
if post.Status == 0 {
|
||
post.Status = post.PostStatusDraft
|
||
}
|
||
|
||
// 插入資料庫
|
||
if err := uc.Post.Insert(ctx, post); err != nil {
|
||
return nil, uc.handleDBError("Post.Insert", req, err)
|
||
}
|
||
|
||
// 處理標籤(更新標籤的貼文數)
|
||
if err := uc.updateTagPostCounts(ctx, req.Tags, true); err != nil {
|
||
// 記錄錯誤但不中斷流程
|
||
uc.Logger.Error(fmt.Sprintf("failed to update tag post counts: %v", err))
|
||
}
|
||
|
||
// 處理分類(更新分類的貼文數)
|
||
if req.CategoryID != nil {
|
||
if err := uc.Category.IncrementPostCount(ctx, *req.CategoryID); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to increment category post count: %v", err))
|
||
}
|
||
}
|
||
|
||
return uc.mapPostToResponse(post), nil
|
||
}
|
||
|
||
// GetPost 取得貼文
|
||
func (uc *PostUseCase) GetPost(ctx context.Context, req domainUsecase.GetPostRequest) (*domainUsecase.PostResponse, error) {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return nil, errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return nil, errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return nil, uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 如果提供了 UserUID,增加瀏覽數
|
||
if req.UserUID != nil {
|
||
if err := uc.Post.IncrementViewCount(ctx, req.PostID); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to increment view count: %v", err))
|
||
}
|
||
}
|
||
|
||
return uc.mapPostToResponse(post), nil
|
||
}
|
||
|
||
// UpdatePost 更新貼文
|
||
func (uc *PostUseCase) UpdatePost(ctx context.Context, req domainUsecase.UpdatePostRequest) (*domainUsecase.PostResponse, error) {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return nil, errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return nil, errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢現有貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return nil, errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return nil, uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return nil, errs.ResNotFoundError("not authorized to update this post")
|
||
}
|
||
|
||
// 檢查是否可編輯
|
||
if !post.IsEditable() {
|
||
return nil, errs.ResNotFoundError("post is not editable")
|
||
}
|
||
|
||
// 更新欄位
|
||
if req.Title != nil {
|
||
post.Title = *req.Title
|
||
}
|
||
if req.Content != nil {
|
||
post.Content = *req.Content
|
||
}
|
||
if req.Type != nil {
|
||
post.Type = *req.Type
|
||
}
|
||
if req.CategoryID != nil {
|
||
// 更新分類計數
|
||
if post.CategoryID != nil && *post.CategoryID != *req.CategoryID {
|
||
if err := uc.Category.DecrementPostCount(ctx, *post.CategoryID); err != nil {
|
||
uc.Logger.Error("failed to decrement category post count", errs.LogField{Key: "error", Val: err.Error()})
|
||
}
|
||
if err := uc.Category.IncrementPostCount(ctx, *req.CategoryID); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to increment category post count: %v", err))
|
||
}
|
||
}
|
||
post.CategoryID = req.CategoryID
|
||
}
|
||
if req.Tags != nil {
|
||
// 更新標籤計數
|
||
oldTags := post.Tags
|
||
post.Tags = req.Tags
|
||
if err := uc.updateTagPostCountsDiff(ctx, oldTags, req.Tags); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to update tag post counts: %v", err))
|
||
}
|
||
}
|
||
if req.Images != nil {
|
||
post.Images = req.Images
|
||
}
|
||
if req.VideoURL != nil {
|
||
post.VideoURL = req.VideoURL
|
||
}
|
||
if req.LinkURL != nil {
|
||
post.LinkURL = req.LinkURL
|
||
}
|
||
|
||
// 更新資料庫
|
||
if err := uc.Post.Update(ctx, post); err != nil {
|
||
return nil, uc.handleDBError("Post.Update", req, err)
|
||
}
|
||
|
||
return uc.mapPostToResponse(post), nil
|
||
}
|
||
|
||
// DeletePost 刪除貼文(軟刪除)
|
||
func (uc *PostUseCase) DeletePost(ctx context.Context, req domainUsecase.DeletePostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return errs.ResNotFoundError("not authorized to delete this post")
|
||
}
|
||
|
||
// 刪除貼文
|
||
if err := uc.Post.Delete(ctx, req.PostID); err != nil {
|
||
return uc.handleDBError("Post.Delete", req, err)
|
||
}
|
||
|
||
// 更新標籤和分類計數
|
||
if len(post.Tags) > 0 {
|
||
if err := uc.updateTagPostCounts(ctx, post.Tags, false); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to update tag post counts: %v", err))
|
||
}
|
||
}
|
||
if post.CategoryID != nil {
|
||
if err := uc.Category.DecrementPostCount(ctx, *post.CategoryID); err != nil {
|
||
uc.Logger.Error("failed to decrement category post count", errs.LogField{Key: "error", Val: err.Error()})
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// PublishPost 發布貼文
|
||
func (uc *PostUseCase) PublishPost(ctx context.Context, req domainUsecase.PublishPostRequest) (*domainUsecase.PostResponse, error) {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return nil, errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return nil, errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return nil, errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return nil, uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return nil, errs.ResNotFoundError("not authorized to publish this post")
|
||
}
|
||
|
||
// 發布貼文
|
||
post.Publish()
|
||
|
||
// 更新資料庫
|
||
if err := uc.Post.Update(ctx, post); err != nil {
|
||
return nil, uc.handleDBError("Post.Update", req, err)
|
||
}
|
||
|
||
return uc.mapPostToResponse(post), nil
|
||
}
|
||
|
||
// ArchivePost 歸檔貼文
|
||
func (uc *PostUseCase) ArchivePost(ctx context.Context, req domainUsecase.ArchivePostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return errs.ResNotFoundError("not authorized to archive this post")
|
||
}
|
||
|
||
// 歸檔貼文
|
||
post.Archive()
|
||
|
||
// 更新資料庫
|
||
return uc.Post.Update(ctx, post)
|
||
}
|
||
|
||
// ListPosts 列出貼文
|
||
func (uc *PostUseCase) ListPosts(ctx context.Context, req domainUsecase.ListPostsRequest) (*domainUsecase.ListPostsResponse, error) {
|
||
// 驗證分頁參數
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
if req.PageIndex <= 0 {
|
||
req.PageIndex = 1
|
||
}
|
||
|
||
// 構建查詢參數
|
||
params := &domainRepo.PostQueryParams{
|
||
CategoryID: req.CategoryID,
|
||
Tag: req.Tag,
|
||
Status: req.Status,
|
||
Type: req.Type,
|
||
AuthorUID: req.AuthorUID,
|
||
CreateStartTime: req.CreateStartTime,
|
||
CreateEndTime: req.CreateEndTime,
|
||
PageSize: req.PageSize,
|
||
PageIndex: req.PageIndex,
|
||
OrderBy: req.OrderBy,
|
||
OrderDirection: req.OrderDirection,
|
||
}
|
||
|
||
// 執行查詢
|
||
var posts []*entity.Post
|
||
var total int64
|
||
var err error
|
||
|
||
if req.CategoryID != nil {
|
||
posts, total, err = uc.Post.FindByCategoryID(ctx, *req.CategoryID, params)
|
||
} else if req.Tag != nil {
|
||
posts, total, err = uc.Post.FindByTag(ctx, *req.Tag, params)
|
||
} else if req.AuthorUID != nil {
|
||
posts, total, err = uc.Post.FindByAuthorUID(ctx, *req.AuthorUID, params)
|
||
} else if req.Status != nil {
|
||
posts, total, err = uc.Post.FindByStatus(ctx, *req.Status, params)
|
||
} else {
|
||
// 預設查詢所有已發布的貼文
|
||
published := post.PostStatusPublished
|
||
params.Status = &published
|
||
posts, total, err = uc.Post.FindByStatus(ctx, published, params)
|
||
}
|
||
|
||
if err != nil {
|
||
return nil, uc.handleDBError("Post.FindBy*", req, err)
|
||
}
|
||
|
||
// 轉換為 Response
|
||
responses := make([]domainUsecase.PostResponse, len(posts))
|
||
for i, p := range posts {
|
||
responses[i] = *uc.mapPostToResponse(p)
|
||
}
|
||
|
||
return &domainUsecase.ListPostsResponse{
|
||
Data: responses,
|
||
Page: domainUsecase.Pager{
|
||
PageIndex: req.PageIndex,
|
||
PageSize: req.PageSize,
|
||
Total: total,
|
||
TotalPage: calculateTotalPages(total, req.PageSize),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// ListPostsByAuthor 根據作者列出貼文
|
||
func (uc *PostUseCase) ListPostsByAuthor(ctx context.Context, req domainUsecase.ListPostsByAuthorRequest) (*domainUsecase.ListPostsResponse, error) {
|
||
if req.AuthorUID == "" {
|
||
return nil, errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
if req.PageIndex <= 0 {
|
||
req.PageIndex = 1
|
||
}
|
||
|
||
params := &domainRepo.PostQueryParams{
|
||
Status: req.Status,
|
||
PageSize: req.PageSize,
|
||
PageIndex: req.PageIndex,
|
||
OrderBy: "created_at",
|
||
OrderDirection: "DESC",
|
||
}
|
||
|
||
posts, total, err := uc.Post.FindByAuthorUID(ctx, req.AuthorUID, params)
|
||
if err != nil {
|
||
return nil, uc.handleDBError("Post.FindByAuthorUID", req, err)
|
||
}
|
||
|
||
responses := make([]domainUsecase.PostResponse, len(posts))
|
||
for i, p := range posts {
|
||
responses[i] = *uc.mapPostToResponse(p)
|
||
}
|
||
|
||
return &domainUsecase.ListPostsResponse{
|
||
Data: responses,
|
||
Page: domainUsecase.Pager{
|
||
PageIndex: req.PageIndex,
|
||
PageSize: req.PageSize,
|
||
Total: total,
|
||
TotalPage: calculateTotalPages(total, req.PageSize),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// ListPostsByCategory 根據分類列出貼文
|
||
func (uc *PostUseCase) ListPostsByCategory(ctx context.Context, req domainUsecase.ListPostsByCategoryRequest) (*domainUsecase.ListPostsResponse, error) {
|
||
var zeroUUID gocql.UUID
|
||
if req.CategoryID == zeroUUID {
|
||
return nil, errs.InputInvalidRangeError("category_id is required")
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
if req.PageIndex <= 0 {
|
||
req.PageIndex = 1
|
||
}
|
||
|
||
params := &domainRepo.PostQueryParams{
|
||
Status: req.Status,
|
||
PageSize: req.PageSize,
|
||
PageIndex: req.PageIndex,
|
||
OrderBy: "created_at",
|
||
OrderDirection: "DESC",
|
||
}
|
||
|
||
posts, total, err := uc.Post.FindByCategoryID(ctx, req.CategoryID, params)
|
||
if err != nil {
|
||
return nil, uc.handleDBError("Post.FindByCategoryID", req, err)
|
||
}
|
||
|
||
responses := make([]domainUsecase.PostResponse, len(posts))
|
||
for i, p := range posts {
|
||
responses[i] = *uc.mapPostToResponse(p)
|
||
}
|
||
|
||
return &domainUsecase.ListPostsResponse{
|
||
Data: responses,
|
||
Page: domainUsecase.Pager{
|
||
PageIndex: req.PageIndex,
|
||
PageSize: req.PageSize,
|
||
Total: total,
|
||
TotalPage: calculateTotalPages(total, req.PageSize),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// ListPostsByTag 根據標籤列出貼文
|
||
func (uc *PostUseCase) ListPostsByTag(ctx context.Context, req domainUsecase.ListPostsByTagRequest) (*domainUsecase.ListPostsResponse, error) {
|
||
if req.Tag == "" {
|
||
return nil, errs.InputInvalidRangeError("tag is required")
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
if req.PageIndex <= 0 {
|
||
req.PageIndex = 1
|
||
}
|
||
|
||
params := &domainRepo.PostQueryParams{
|
||
Status: req.Status,
|
||
PageSize: req.PageSize,
|
||
PageIndex: req.PageIndex,
|
||
OrderBy: "created_at",
|
||
OrderDirection: "DESC",
|
||
}
|
||
|
||
posts, total, err := uc.Post.FindByTag(ctx, req.Tag, params)
|
||
if err != nil {
|
||
return nil, uc.handleDBError("Post.FindByTag", req, err)
|
||
}
|
||
|
||
responses := make([]domainUsecase.PostResponse, len(posts))
|
||
for i, p := range posts {
|
||
responses[i] = *uc.mapPostToResponse(p)
|
||
}
|
||
|
||
return &domainUsecase.ListPostsResponse{
|
||
Data: responses,
|
||
Page: domainUsecase.Pager{
|
||
PageIndex: req.PageIndex,
|
||
PageSize: req.PageSize,
|
||
Total: total,
|
||
TotalPage: calculateTotalPages(total, req.PageSize),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetPinnedPosts 取得置頂貼文
|
||
func (uc *PostUseCase) GetPinnedPosts(ctx context.Context, req domainUsecase.GetPinnedPostsRequest) (*domainUsecase.ListPostsResponse, error) {
|
||
limit := int64(10)
|
||
if req.Limit > 0 {
|
||
limit = req.Limit
|
||
}
|
||
|
||
posts, err := uc.Post.FindPinnedPosts(ctx, limit)
|
||
if err != nil {
|
||
return nil, uc.handleDBError("Post.FindPinnedPosts", req, err)
|
||
}
|
||
|
||
responses := make([]domainUsecase.PostResponse, len(posts))
|
||
for i, p := range posts {
|
||
responses[i] = *uc.mapPostToResponse(p)
|
||
}
|
||
|
||
return &domainUsecase.ListPostsResponse{
|
||
Data: responses,
|
||
Page: domainUsecase.Pager{
|
||
PageIndex: 1,
|
||
PageSize: limit,
|
||
Total: int64(len(responses)),
|
||
TotalPage: 1,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// LikePost 按讚貼文
|
||
func (uc *PostUseCase) LikePost(ctx context.Context, req domainUsecase.LikePostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.UserUID == "" {
|
||
return errs.InputInvalidRangeError("user_uid is required")
|
||
}
|
||
|
||
// 檢查是否已經按讚
|
||
existingLike, err := uc.Like.FindByTargetAndUser(ctx, req.PostID, req.UserUID, "post")
|
||
if err == nil && existingLike != nil {
|
||
// 已經按讚,直接返回成功
|
||
return nil
|
||
}
|
||
if err != nil && !repository.IsNotFound(err) {
|
||
return uc.handleDBError("Like.FindByTargetAndUser", req, err)
|
||
}
|
||
|
||
// 建立按讚記錄
|
||
like := &entity.Like{
|
||
TargetID: req.PostID,
|
||
UserUID: req.UserUID,
|
||
TargetType: "post",
|
||
}
|
||
|
||
if err := uc.Like.Insert(ctx, like); err != nil {
|
||
return uc.handleDBError("Like.Insert", req, err)
|
||
}
|
||
|
||
// 增加貼文的按讚數
|
||
if err := uc.Post.IncrementLikeCount(ctx, req.PostID); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to increment like count: %v", err))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UnlikePost 取消按讚
|
||
func (uc *PostUseCase) UnlikePost(ctx context.Context, req domainUsecase.UnlikePostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.UserUID == "" {
|
||
return errs.InputInvalidRangeError("user_uid is required")
|
||
}
|
||
|
||
// 刪除按讚記錄
|
||
if err := uc.Like.DeleteByTargetAndUser(ctx, req.PostID, req.UserUID, "post"); err != nil {
|
||
if repository.IsNotFound(err) {
|
||
// 已經取消按讚,直接返回成功
|
||
return nil
|
||
}
|
||
return uc.handleDBError("Like.DeleteByTargetAndUser", req, err)
|
||
}
|
||
|
||
// 減少貼文的按讚數
|
||
if err := uc.Post.DecrementLikeCount(ctx, req.PostID); err != nil {
|
||
uc.Logger.Error(fmt.Sprintf("failed to decrement like count: %v", err))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ViewPost 瀏覽貼文(增加瀏覽數)
|
||
func (uc *PostUseCase) ViewPost(ctx context.Context, req domainUsecase.ViewPostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
|
||
// 增加瀏覽數
|
||
if err := uc.Post.IncrementViewCount(ctx, req.PostID); err != nil {
|
||
return uc.handleDBError("Post.IncrementViewCount", req, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// PinPost 置頂貼文
|
||
func (uc *PostUseCase) PinPost(ctx context.Context, req domainUsecase.PinPostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return errs.ResNotFoundError("not authorized to pin this post")
|
||
}
|
||
|
||
// 置頂貼文
|
||
return uc.Post.PinPost(ctx, req.PostID)
|
||
}
|
||
|
||
// UnpinPost 取消置頂
|
||
func (uc *PostUseCase) UnpinPost(ctx context.Context, req domainUsecase.UnpinPostRequest) error {
|
||
// 驗證輸入
|
||
var zeroUUID gocql.UUID
|
||
if req.PostID == zeroUUID {
|
||
return errs.InputInvalidRangeError("post_id is required")
|
||
}
|
||
if req.AuthorUID == "" {
|
||
return errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
|
||
// 查詢貼文
|
||
post, err := uc.Post.FindOne(ctx, req.PostID)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
return errs.ResNotFoundError(fmt.Sprintf("post not found: %s", req.PostID))
|
||
}
|
||
return uc.handleDBError("Post.FindOne", req, err)
|
||
}
|
||
|
||
// 驗證權限
|
||
if post.AuthorUID != req.AuthorUID {
|
||
return errs.ResNotFoundError("not authorized to unpin this post")
|
||
}
|
||
|
||
// 取消置頂
|
||
return uc.Post.UnpinPost(ctx, req.PostID)
|
||
}
|
||
|
||
// validateCreatePostRequest 驗證建立貼文請求
|
||
func (uc *PostUseCase) validateCreatePostRequest(req domainUsecase.CreatePostRequest) error {
|
||
if req.AuthorUID == "" {
|
||
return errs.InputInvalidRangeError("author_uid is required")
|
||
}
|
||
if req.Title == "" {
|
||
return errs.InputInvalidRangeError("title is required")
|
||
}
|
||
if req.Content == "" {
|
||
return errs.InputInvalidRangeError("content is required")
|
||
}
|
||
if !req.Type.IsValid() {
|
||
return errs.InputInvalidRangeError("invalid post type")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// mapPostToResponse 將 Post 實體轉換為 PostResponse
|
||
func (uc *PostUseCase) mapPostToResponse(post *entity.Post) *domainUsecase.PostResponse {
|
||
return &domainUsecase.PostResponse{
|
||
ID: post.ID,
|
||
AuthorUID: post.AuthorUID,
|
||
Title: post.Title,
|
||
Content: post.Content,
|
||
Type: post.Type,
|
||
Status: post.Status,
|
||
CategoryID: post.CategoryID,
|
||
Tags: post.Tags,
|
||
Images: post.Images,
|
||
VideoURL: post.VideoURL,
|
||
LinkURL: post.LinkURL,
|
||
LikeCount: post.LikeCount,
|
||
CommentCount: post.CommentCount,
|
||
ViewCount: post.ViewCount,
|
||
IsPinned: post.IsPinned,
|
||
PinnedAt: post.PinnedAt,
|
||
PublishedAt: post.PublishedAt,
|
||
CreatedAt: post.CreatedAt,
|
||
UpdatedAt: post.UpdatedAt,
|
||
}
|
||
}
|
||
|
||
// handleDBError 處理資料庫錯誤
|
||
func (uc *PostUseCase) handleDBError(funcName string, req any, err error) error {
|
||
return errs.DBErrorErrorL(
|
||
uc.Logger,
|
||
[]errs.LogField{
|
||
{Key: "func", Val: funcName},
|
||
{Key: "req", Val: req},
|
||
{Key: "error", Val: err.Error()},
|
||
},
|
||
fmt.Sprintf("database operation failed: %s", funcName),
|
||
).Wrap(err)
|
||
}
|
||
|
||
// updateTagPostCounts 更新標籤的貼文數
|
||
func (uc *PostUseCase) updateTagPostCounts(ctx context.Context, tags []string, increment bool) error {
|
||
if len(tags) == 0 {
|
||
return nil
|
||
}
|
||
|
||
// 查詢或建立標籤
|
||
for _, tagName := range tags {
|
||
tag, err := uc.Tag.FindByName(ctx, tagName)
|
||
if err != nil {
|
||
if repository.IsNotFound(err) {
|
||
// 建立新標籤
|
||
newTag := &entity.Tag{
|
||
Name: tagName,
|
||
}
|
||
if err := uc.Tag.Insert(ctx, newTag); err != nil {
|
||
return fmt.Errorf("failed to create tag: %w", err)
|
||
}
|
||
tag = newTag
|
||
} else {
|
||
return fmt.Errorf("failed to find tag: %w", err)
|
||
}
|
||
}
|
||
|
||
// 更新計數
|
||
if increment {
|
||
if err := uc.Tag.IncrementPostCount(ctx, tag.ID); err != nil {
|
||
return fmt.Errorf("failed to increment tag count: %w", err)
|
||
}
|
||
} else {
|
||
if err := uc.Tag.DecrementPostCount(ctx, tag.ID); err != nil {
|
||
return fmt.Errorf("failed to decrement tag count: %w", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// updateTagPostCountsDiff 更新標籤計數(處理差異)
|
||
func (uc *PostUseCase) updateTagPostCountsDiff(ctx context.Context, oldTags, newTags []string) error {
|
||
// 找出新增和刪除的標籤
|
||
oldTagMap := make(map[string]bool)
|
||
for _, tag := range oldTags {
|
||
oldTagMap[tag] = true
|
||
}
|
||
|
||
newTagMap := make(map[string]bool)
|
||
for _, tag := range newTags {
|
||
newTagMap[tag] = true
|
||
}
|
||
|
||
// 新增的標籤
|
||
for _, tag := range newTags {
|
||
if !oldTagMap[tag] {
|
||
if err := uc.updateTagPostCounts(ctx, []string{tag}, true); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
// 刪除的標籤
|
||
for _, tag := range oldTags {
|
||
if !newTagMap[tag] {
|
||
if err := uc.updateTagPostCounts(ctx, []string{tag}, false); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// calculateTotalPages 計算總頁數
|
||
func calculateTotalPages(total, pageSize int64) int64 {
|
||
if pageSize <= 0 {
|
||
return 0
|
||
}
|
||
return int64(math.Ceil(float64(total) / float64(pageSize)))
|
||
}
|
||
|