Add tag filtering to ListPosts and enhance ListTags with post count
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 53s
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 53s
This commit is contained in:
10
api/Blog.api
10
api/Blog.api
@@ -80,8 +80,9 @@ type (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
ListPostsReq {
|
ListPostsReq {
|
||||||
Page int `json:"page"` // Page number for pagination (1-based)
|
TagIds []string `json:"tagIds,optional"` // Array of tag IDs to filter posts (optional)
|
||||||
PageSize int `json:"pageSize"` // Number of posts per page
|
Page int `json:"page"` // Page number for pagination (1-based)
|
||||||
|
PageSize int `json:"pageSize"` // Number of posts per page
|
||||||
}
|
}
|
||||||
ListPostsResp {
|
ListPostsResp {
|
||||||
Posts []ListPostsRespPosts `json:"posts"` // Array of blog posts
|
Posts []ListPostsRespPosts `json:"posts"` // Array of blog posts
|
||||||
@@ -102,8 +103,9 @@ type (
|
|||||||
Tags []ListTagsRespTags `json:"tags"` // Array of blog tags
|
Tags []ListTagsRespTags `json:"tags"` // Array of blog tags
|
||||||
}
|
}
|
||||||
ListTagsRespTags {
|
ListTagsRespTags {
|
||||||
TagId string `json:"tagId"` // Unique identifier of the tag
|
TagId string `json:"tagId"` // Unique identifier of the tag
|
||||||
TagName string `json:"tagName"` // Name of the tag
|
TagName string `json:"tagName"` // Name of the tag
|
||||||
|
PostCount int `json:"postCount"` // Number of posts associated with the tag
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,23 +39,71 @@ func (l *ListPostsLogic) ListPosts(req *types.ListPostsReq) (resp *types.ListPos
|
|||||||
}
|
}
|
||||||
offset := (page - 1) * pageSize
|
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
|
// Get total count
|
||||||
var totalCount int
|
var totalCount int
|
||||||
countQuery := `SELECT COUNT(*) FROM posts`
|
if len(req.TagIds) > 0 {
|
||||||
err = l.svcCtx.DB.QueryRowContext(l.ctx, countQuery).Scan(&totalCount)
|
// Count posts matching tag filter
|
||||||
if err != nil {
|
rows, err := l.svcCtx.DB.QueryContext(l.ctx, countQuery, req.TagIds, len(req.TagIds))
|
||||||
l.Errorf("Failed to get total count: %v", err)
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Get posts with pagination
|
||||||
postsQuery := `
|
rows, err := l.svcCtx.DB.QueryContext(l.ctx, postsQuery, args...)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("Failed to get posts: %v", err)
|
l.Errorf("Failed to get posts: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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) {
|
func (l *ListTagsLogic) ListTags(req *types.ListTagsReq) (resp *types.ListTagsResp, err error) {
|
||||||
// Query all hashtags
|
// Query all hashtags with post count
|
||||||
query := `SELECT id, name FROM hashtags ORDER BY name`
|
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)
|
rows, err := l.svcCtx.DB.QueryContext(l.ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("Failed to get tags: %v", err)
|
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
|
var tags []types.ListTagsRespTags
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var tag types.ListTagsRespTags
|
var tag types.ListTagsRespTags
|
||||||
err := rows.Scan(&tag.TagId, &tag.TagName)
|
err := rows.Scan(&tag.TagId, &tag.TagName, &tag.PostCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("Failed to scan tag: %v", err)
|
l.Errorf("Failed to scan tag: %v", err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ type GetPostResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListPostsReq struct {
|
type ListPostsReq struct {
|
||||||
Page int `json:"page"` // Page number for pagination (1-based)
|
TagIds []string `json:"tagIds,optional"` // Array of tag IDs to filter posts (optional)
|
||||||
PageSize int `json:"pageSize"` // Number of posts per page
|
Page int `json:"page"` // Page number for pagination (1-based)
|
||||||
|
PageSize int `json:"pageSize"` // Number of posts per page
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListPostsResp struct {
|
type ListPostsResp struct {
|
||||||
@@ -78,8 +79,9 @@ type ListTagsResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListTagsRespTags struct {
|
type ListTagsRespTags struct {
|
||||||
TagId string `json:"tagId"` // Unique identifier of the tag
|
TagId string `json:"tagId"` // Unique identifier of the tag
|
||||||
TagName string `json:"tagName"` // Name 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 {
|
type PingReq struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user