Skip to content

Commit 3c22c9a

Browse files
committed
example2: sample code for paginated posts
1 parent 8f29b54 commit 3c22c9a

File tree

5 files changed

+261
-43
lines changed

5 files changed

+261
-43
lines changed

example2/README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,40 @@ Delete authenticated user account and all related posts/hobbies. _(Requires JWT)
240240

241241
#### GET /posts
242242

243-
Get all posts.
243+
Get all posts with pagination support.
244+
245+
Query parameters:
246+
247+
- `page` (optional): Page number, default: 1
248+
- `pageSize` (optional): Number of posts per page, default: 10, max: 100
244249

245250
```bash
251+
# Get first page with default page size (10)
246252
curl http://localhost:8999/api/v1/posts
253+
254+
# Get page 2 with 1 post per page
255+
curl "http://localhost:8999/api/v1/posts?page=2&pageSize=1"
256+
```
257+
258+
Response:
259+
260+
```json
261+
{
262+
"hasNext": true,
263+
"hasPrevious": true,
264+
"page": 2,
265+
"pageSize": 1,
266+
"posts": [
267+
{
268+
"postID": 8,
269+
"createdAt": 1771575386,
270+
"updatedAt": 1771575386,
271+
"title": "My First Post",
272+
"body": "Hello, gorest!"
273+
}
274+
],
275+
"total": 9
276+
}
247277
```
248278

249279
#### GET /posts/:id

example2/internal/handler/post.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,45 @@ func NewPostAPI(postService *service.PostService) *PostAPI {
2727
}
2828
}
2929

30-
// GetPosts handles the HTTP GET request to retrieve all posts.
30+
// GetPosts handles the HTTP GET request to retrieve paginated posts.
3131
//
32-
// Endpoint: GET /api/v1/posts
32+
// Endpoint: GET /api/v1/posts?page=1&pageSize=10
33+
//
34+
// Optional query parameters:
35+
// - page: page number (default: 1)
36+
// - pageSize: number of posts per page (default: 10, max: 100)
3337
//
3438
// Authorization: None
3539
func (api *PostAPI) GetPosts(c *gin.Context) {
40+
pageStr := strings.TrimSpace(c.Query("page"))
41+
pageSizeStr := strings.TrimSpace(c.Query("pageSize"))
42+
43+
var page, pageSize int
44+
var err error
45+
46+
if pageStr == "" {
47+
page = 1
48+
} else {
49+
page, err = strconv.Atoi(pageStr)
50+
if err != nil {
51+
grenderer.Render(c, gin.H{"message": "invalid page parameter"}, http.StatusBadRequest)
52+
return
53+
}
54+
}
55+
if pageSizeStr == "" {
56+
pageSize = 10
57+
} else {
58+
pageSize, err = strconv.Atoi(pageSizeStr)
59+
if err != nil {
60+
grenderer.Render(c, gin.H{"message": "invalid pageSize parameter"}, http.StatusBadRequest)
61+
return
62+
}
63+
}
64+
3665
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
3766
defer cancel()
3867

39-
resp, statusCode := api.postService.GetPosts(ctx)
68+
resp, statusCode := api.postService.GetPosts(ctx, page, pageSize)
4069
grenderer.Render(c, resp, statusCode)
4170
}
4271

example2/internal/repo/post.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ func NewPostRepo(conn *gorm.DB) *PostRepo {
2424
// PostRepository defines the contract for post-related operations.
2525
type PostRepository interface {
2626
GetPosts(ctx context.Context) ([]model.Post, error)
27+
ListPosts(ctx context.Context, limit, offset int) ([]model.Post, error)
28+
CountPosts(ctx context.Context) (int64, error)
2729
GetPost(ctx context.Context, postID uint64) (*model.Post, error)
2830
GetPostsByUserID(ctx context.Context, userID uint64) ([]model.Post, error)
2931
CreatePost(ctx context.Context, post *model.Post) error
@@ -45,6 +47,27 @@ func (r *PostRepo) GetPosts(ctx context.Context) ([]model.Post, error) {
4547
return posts, nil
4648
}
4749

50+
// ListPosts returns a paginated list of posts ordered by post_id descending.
51+
func (r *PostRepo) ListPosts(ctx context.Context, limit, offset int) ([]model.Post, error) {
52+
var posts []model.Post
53+
err := r.db.WithContext(ctx).
54+
Order("post_id desc").
55+
Limit(limit).
56+
Offset(offset).
57+
Find(&posts).Error
58+
if err != nil {
59+
return nil, err
60+
}
61+
return posts, nil
62+
}
63+
64+
// CountPosts returns the total number of posts in the database.
65+
func (r *PostRepo) CountPosts(ctx context.Context) (int64, error) {
66+
var total int64
67+
err := r.db.WithContext(ctx).Model(&model.Post{}).Count(&total).Error
68+
return total, err
69+
}
70+
4871
// GetPost returns a post with the given postID from the database.
4972
func (r *PostRepo) GetPost(ctx context.Context, postID uint64) (*model.Post, error) {
5073
var post model.Post

example2/internal/service/post.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,22 @@ func NewPostService(postRepo repo.PostRepository, userRepo repo.UserRepository)
2727
}
2828
}
2929

30-
// GetPosts retrieves all posts.
31-
func (s *PostService) GetPosts(ctx context.Context) (httpResponse gmodel.HTTPResponse, httpStatusCode int) {
32-
posts, err := s.postRepo.GetPosts(ctx)
30+
// GetPosts retrieves a paginated list of posts.
31+
func (s *PostService) GetPosts(ctx context.Context, page, pageSize int) (httpResponse gmodel.HTTPResponse, httpStatusCode int) {
32+
// normalize pagination params
33+
if page <= 0 {
34+
page = 1
35+
}
36+
if pageSize <= 0 {
37+
pageSize = 10
38+
}
39+
if pageSize > 100 {
40+
pageSize = 100
41+
}
42+
offset := (page - 1) * pageSize
43+
44+
// get total count
45+
total, err := s.postRepo.CountPosts(ctx)
3346
if err != nil {
3447
if errors.Is(err, context.Canceled) {
3548
httpResponse.Message = "request canceled"
@@ -43,13 +56,46 @@ func (s *PostService) GetPosts(ctx context.Context) (httpResponse gmodel.HTTPRes
4356
return
4457
}
4558

59+
if total == 0 {
60+
httpResponse.Message = "no post found"
61+
httpStatusCode = http.StatusNotFound
62+
return
63+
}
64+
65+
// get paginated posts
66+
posts, err := s.postRepo.ListPosts(ctx, pageSize, offset)
67+
if err != nil {
68+
if errors.Is(err, context.Canceled) {
69+
httpResponse.Message = "request canceled"
70+
httpStatusCode = http.StatusRequestTimeout
71+
return
72+
}
73+
74+
log.WithError(err).Error("GetPosts.s.2")
75+
httpResponse.Message = "internal server error"
76+
httpStatusCode = http.StatusInternalServerError
77+
return
78+
}
79+
4680
if len(posts) == 0 {
4781
httpResponse.Message = "no post found"
4882
httpStatusCode = http.StatusNotFound
4983
return
5084
}
5185

52-
httpResponse.Message = posts
86+
hasNext := int64(page*pageSize) < total
87+
hasPrevious := page > 1
88+
89+
result := map[string]any{
90+
"hasNext": hasNext,
91+
"hasPrevious": hasPrevious,
92+
"page": page,
93+
"pageSize": pageSize,
94+
"posts": posts,
95+
"total": total,
96+
}
97+
98+
httpResponse.Message = result
5399
httpStatusCode = http.StatusOK
54100
return
55101
}

0 commit comments

Comments
 (0)