diff --git a/api/Blog.api b/api/Blog.api index e4052b1..56d881f 100644 --- a/api/Blog.api +++ b/api/Blog.api @@ -80,8 +80,9 @@ type ( type ( ListPostsReq { - Page int `json:"page"` // Page number for pagination (1-based) - PageSize int `json:"pageSize"` // Number of posts per page + TagIds []string `json:"tagIds,optional"` // Array of tag IDs to filter posts (optional) + Page int `json:"page"` // Page number for pagination (1-based) + PageSize int `json:"pageSize"` // Number of posts per page } ListPostsResp { Posts []ListPostsRespPosts `json:"posts"` // Array of blog posts @@ -102,8 +103,9 @@ type ( Tags []ListTagsRespTags `json:"tags"` // Array of blog tags } ListTagsRespTags { - TagId string `json:"tagId"` // Unique identifier of the tag - TagName string `json:"tagName"` // Name of the tag + TagId string `json:"tagId"` // Unique identifier of the tag + TagName string `json:"tagName"` // Name of the tag + PostCount int `json:"postCount"` // Number of posts associated with the tag } ) diff --git a/app/internal/logic/listpostslogic.go b/app/internal/logic/listpostslogic.go index 3c0def3..026a9b4 100644 --- a/app/internal/logic/listpostslogic.go +++ b/app/internal/logic/listpostslogic.go @@ -39,23 +39,71 @@ func (l *ListPostsLogic) ListPosts(req *types.ListPostsReq) (resp *types.ListPos } offset := (page - 1) * pageSize + // Build query with optional tag filter + var countQuery string + var postsQuery string + var args []interface{} + + if len(req.TagIds) > 0 { + // Filter by tags - posts that have ALL specified tags + countQuery = ` + SELECT COUNT(DISTINCT p.id) + FROM posts p + INNER JOIN post_hashtags ph ON p.id = ph.post_id + WHERE ph.hashtag_id = ANY($1) + GROUP BY p.id + HAVING COUNT(DISTINCT ph.hashtag_id) = $2 + ` + + postsQuery = ` + SELECT DISTINCT p.id, p.title, p.created_at, p.updated_at, p.cover_id + FROM posts p + INNER JOIN post_hashtags ph ON p.id = ph.post_id + WHERE ph.hashtag_id = ANY($1) + GROUP BY p.id, p.title, p.created_at, p.updated_at, p.cover_id + HAVING COUNT(DISTINCT ph.hashtag_id) = $2 + ORDER BY p.created_at DESC + LIMIT $3 OFFSET $4 + ` + + args = []interface{}{req.TagIds, len(req.TagIds), pageSize, offset} + } else { + // No tag filter - get all posts + countQuery = `SELECT COUNT(*) FROM posts` + postsQuery = ` + SELECT p.id, p.title, p.created_at, p.updated_at, p.cover_id + FROM posts p + ORDER BY p.created_at DESC + LIMIT $1 OFFSET $2 + ` + args = []interface{}{pageSize, offset} + } + // Get total count var totalCount int - countQuery := `SELECT COUNT(*) FROM posts` - err = l.svcCtx.DB.QueryRowContext(l.ctx, countQuery).Scan(&totalCount) - if err != nil { - l.Errorf("Failed to get total count: %v", err) - return nil, err + if len(req.TagIds) > 0 { + // Count posts matching tag filter + rows, err := l.svcCtx.DB.QueryContext(l.ctx, countQuery, req.TagIds, len(req.TagIds)) + if err != nil { + l.Errorf("Failed to get total count: %v", err) + return nil, err + } + defer rows.Close() + + totalCount = 0 + for rows.Next() { + totalCount++ + } + } else { + err = l.svcCtx.DB.QueryRowContext(l.ctx, countQuery).Scan(&totalCount) + if err != nil { + l.Errorf("Failed to get total count: %v", err) + return nil, err + } } // Get posts with pagination - postsQuery := ` - SELECT p.id, p.title, p.created_at, p.updated_at, p.cover_id - FROM posts p - ORDER BY p.created_at DESC - LIMIT $1 OFFSET $2 - ` - rows, err := l.svcCtx.DB.QueryContext(l.ctx, postsQuery, pageSize, offset) + rows, err := l.svcCtx.DB.QueryContext(l.ctx, postsQuery, args...) if err != nil { l.Errorf("Failed to get posts: %v", err) return nil, err diff --git a/app/internal/logic/listtagslogic.go b/app/internal/logic/listtagslogic.go index b62d601..048c84b 100644 --- a/app/internal/logic/listtagslogic.go +++ b/app/internal/logic/listtagslogic.go @@ -25,8 +25,14 @@ func NewListTagsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListTags } func (l *ListTagsLogic) ListTags(req *types.ListTagsReq) (resp *types.ListTagsResp, err error) { - // Query all hashtags - query := `SELECT id, name FROM hashtags ORDER BY name` + // Query all hashtags with post count + query := ` + SELECT h.id, h.name, COUNT(ph.post_id) as post_count + FROM hashtags h + LEFT JOIN post_hashtags ph ON h.id = ph.hashtag_id + GROUP BY h.id, h.name + ORDER BY h.name + ` rows, err := l.svcCtx.DB.QueryContext(l.ctx, query) if err != nil { l.Errorf("Failed to get tags: %v", err) @@ -37,7 +43,7 @@ func (l *ListTagsLogic) ListTags(req *types.ListTagsReq) (resp *types.ListTagsRe var tags []types.ListTagsRespTags for rows.Next() { var tag types.ListTagsRespTags - err := rows.Scan(&tag.TagId, &tag.TagName) + err := rows.Scan(&tag.TagId, &tag.TagName, &tag.PostCount) if err != nil { l.Errorf("Failed to scan tag: %v", err) continue diff --git a/app/internal/types/types.go b/app/internal/types/types.go index 5b237cc..b97e992 100644 --- a/app/internal/types/types.go +++ b/app/internal/types/types.go @@ -53,8 +53,9 @@ type GetPostResp struct { } type ListPostsReq struct { - Page int `json:"page"` // Page number for pagination (1-based) - PageSize int `json:"pageSize"` // Number of posts per page + TagIds []string `json:"tagIds,optional"` // Array of tag IDs to filter posts (optional) + Page int `json:"page"` // Page number for pagination (1-based) + PageSize int `json:"pageSize"` // Number of posts per page } type ListPostsResp struct { @@ -78,8 +79,9 @@ type ListTagsResp struct { } type ListTagsRespTags struct { - TagId string `json:"tagId"` // Unique identifier of the tag - TagName string `json:"tagName"` // Name of the tag + TagId string `json:"tagId"` // Unique identifier of the tag + TagName string `json:"tagName"` // Name of the tag + PostCount int `json:"postCount"` // Number of posts associated with the tag } type PingReq struct {