Skip to content

Commit c789a9a

Browse files
add post summary to return simple post objects
1 parent a898366 commit c789a9a

File tree

6 files changed

+226
-71
lines changed

6 files changed

+226
-71
lines changed

internal/handlers/post.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,31 @@ func (h *PostHandler) ListAllPosts(c *gin.Context) {
252252
c.JSON(http.StatusOK, gin.H{"posts": posts})
253253
}
254254

255+
// @Summary List post summaries
256+
// @Description Retrieve summaries of posts with pagination
257+
// @Tags Posts
258+
// @Accept json
259+
// @Produce json
260+
// @Param limit query int false "Number of posts to retrieve" default(20)
261+
// @Param offset query int false "Number of posts to skip" default(0)
262+
// @Success 200 {array} models.PostSummary "Post summaries retrieved successfully"
263+
// @Failure 500 {object} models.ErrorResponse
264+
// @Router /posts/summaries [get]
265+
func (h *PostHandler) ListPostSummaries(c *gin.Context) {
266+
limitStr := c.DefaultQuery("limit", "20")
267+
offsetStr := c.DefaultQuery("offset", "0")
268+
limit, _ := strconv.Atoi(limitStr)
269+
offset, _ := strconv.Atoi(offsetStr)
270+
271+
posts, err := h.postService.ListPostSummaries(limit, offset)
272+
if err != nil {
273+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
274+
return
275+
}
276+
277+
c.JSON(http.StatusOK, gin.H{"posts": posts})
278+
}
279+
255280
// @Summary List public posts
256281
// @Description Retrieve all posts from public clubs
257282
// @Tags Posts

internal/handlers/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func (s *Server) setupRoutes() {
128128

129129
api.GET("/posts/:id/likes", postHandler.ListLikesByPostID)
130130
api.GET("/posts", postHandler.ListAllPosts)
131+
api.GET("/posts/summaries", postHandler.ListPostSummaries)
131132
api.GET("/posts/:id", postHandler.GetPostByID)
132133

133134
api.GET("/posts/:id/comments", commentHandler.ListCommentsByPostID)

internal/models/post.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,33 @@ type PostData struct {
110110
PostContent string `json:"post_content,omitempty"`
111111
}
112112

113+
type UserSummary struct {
114+
ID uint `json:"id"`
115+
Username string `json:"username"`
116+
AvatarURL *string `json:"avatar_url,omitempty"`
117+
}
118+
119+
type ClubSummary struct {
120+
ID uint `json:"id"`
121+
Name string `json:"name"`
122+
}
123+
124+
type PostSummary struct {
125+
ID uint `json:"id" gorm:"column:id"`
126+
Title string `json:"title" gorm:"column:title"`
127+
Type string `json:"type" gorm:"column:type"`
128+
IsPinned bool `json:"is_pinned" gorm:"column:is_pinned"`
129+
LikesCount int `json:"likes_count" gorm:"column:likes_count"`
130+
CommentsCount int `json:"comments_count" gorm:"column:comments_count"`
131+
ViewsCount int `json:"views_count" gorm:"column:views_count"`
132+
UserID uint `json:"user_id" gorm:"column:post_user_id"`
133+
ClubID *uint `json:"club_id" gorm:"column:post_club_id"`
134+
User UserSummary `json:"user"`
135+
Club *ClubSummary `json:"club,omitempty"`
136+
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
137+
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"`
138+
}
139+
113140
func (p *Post) GetReviewData() (*ReviewData, error) {
114141
if p.Type != "review" || len(p.TypeData) == 0 {
115142
return nil, nil
@@ -187,7 +214,7 @@ type PostResponse struct {
187214
}
188215

189216
type PollVoteRequest struct {
190-
OptionIDs []string `json:"option_ids" validate:"required,min=1"`
217+
OptionIDs []string `json:"option_ids" validate:"required,min=1"`
191218
}
192219

193220
type PostLikeResponse struct {

internal/repository/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type PostRepository interface {
8080
GetPostsByType(postType string, limit, offset int) ([]models.Post, error)
8181
GetReviewPostsByBookID(bookID uint) ([]models.Post, error)
8282
GetPollPostsByClubID(clubID uint, includeExpired bool) ([]models.Post, error)
83+
ListPostSummaries(limit, offset int) ([]models.PostSummary, error)
8384
}
8485

8586
type CommentRepository interface {

internal/repository/post.go

Lines changed: 162 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -81,38 +81,130 @@ func (r *postRepository) ListAll() ([]models.Post, error) {
8181
return posts, nil
8282
}
8383

84+
func (r *postRepository) ListPostSummaries(limit, offset int) ([]models.PostSummary, error) {
85+
type row struct {
86+
ID uint `gorm:"column:id"`
87+
Title string `gorm:"column:title"`
88+
Type string `gorm:"column:type"`
89+
IsPinned bool `gorm:"column:is_pinned"`
90+
LikesCount int `gorm:"column:likes_count"`
91+
CommentsCount int `gorm:"column:comments_count"`
92+
ViewsCount int `gorm:"column:views_count"`
93+
PostUserID uint `gorm:"column:post_user_id"`
94+
PostClubID *uint `gorm:"column:post_club_id"`
95+
CreatedAt time.Time `gorm:"column:created_at"`
96+
UpdatedAt time.Time `gorm:"column:updated_at"`
97+
98+
UserID *uint `gorm:"column:user_id"`
99+
UserUsername *string `gorm:"column:user_username"`
100+
UserAvatarURL *string `gorm:"column:user_avatar_url"`
101+
102+
ClubID *uint `gorm:"column:club_id"`
103+
ClubName *string `gorm:"column:club_name"`
104+
}
105+
106+
var rows []row
107+
108+
err := r.db.Table("posts").
109+
Select(`posts.id, posts.title, posts.type, posts.is_pinned, posts.likes_count, posts.comments_count, posts.views_count,
110+
posts.user_id as post_user_id, posts.club_id as post_club_id, posts.created_at, posts.updated_at,
111+
users.id as user_id, users.username as user_username, users.avatar_url as user_avatar_url,
112+
clubs.id as club_id, clubs.name as club_name`).
113+
Joins("LEFT JOIN users ON users.id = posts.user_id").
114+
Joins("LEFT JOIN clubs ON clubs.id = posts.club_id").
115+
Limit(limit).
116+
Offset(offset).
117+
Order("posts.created_at DESC").
118+
Scan(&rows).Error
119+
if err != nil {
120+
return nil, err
121+
}
122+
123+
out := make([]models.PostSummary, 0, len(rows))
124+
for _, rrow := range rows {
125+
ps := models.PostSummary{
126+
ID: rrow.ID,
127+
Title: rrow.Title,
128+
Type: rrow.Type,
129+
IsPinned: rrow.IsPinned,
130+
LikesCount: rrow.LikesCount,
131+
CommentsCount: rrow.CommentsCount,
132+
ViewsCount: rrow.ViewsCount,
133+
UserID: rrow.PostUserID,
134+
ClubID: rrow.PostClubID,
135+
CreatedAt: rrow.CreatedAt,
136+
UpdatedAt: rrow.UpdatedAt,
137+
}
138+
139+
if rrow.UserID != nil {
140+
ps.User = models.UserSummary{
141+
ID: *rrow.UserID,
142+
Username: safeString(rrow.UserUsername),
143+
AvatarURL: func() *string {
144+
if rrow.UserAvatarURL != nil && *rrow.UserAvatarURL != "" {
145+
return rrow.UserAvatarURL
146+
}
147+
return nil
148+
}(),
149+
}
150+
} else {
151+
ps.User = models.UserSummary{}
152+
}
153+
154+
if rrow.ClubID != nil && rrow.ClubName != nil {
155+
ps.Club = &models.ClubSummary{
156+
ID: *rrow.ClubID,
157+
Name: *rrow.ClubName,
158+
}
159+
} else {
160+
ps.Club = nil
161+
}
162+
163+
out = append(out, ps)
164+
}
165+
166+
return out, nil
167+
}
168+
169+
func safeString(s *string) string {
170+
if s == nil {
171+
return ""
172+
}
173+
return *s
174+
}
175+
84176
func (r *postRepository) ListPublicPosts() ([]models.Post, error) {
85-
var posts []models.Post
86-
if err := r.db.
87-
Preload("User").
88-
Preload("Club"). // Add this to show which club the post belongs to
89-
Preload("Comments").
90-
Preload("Likes").
91-
Joins("JOIN clubs ON posts.club_id = clubs.id").
92-
Where("clubs.is_private = ?", false). // Fix: false for public clubs
93-
Order("posts.likes_count DESC, posts.created_at DESC"). // Popular first
94-
Limit(20). // Limit for homepage
95-
Find(&posts).Error; err != nil {
96-
return nil, err
97-
}
98-
return posts, nil
177+
var posts []models.Post
178+
if err := r.db.
179+
Preload("User").
180+
Preload("Club"). // Add this to show which club the post belongs to
181+
Preload("Comments").
182+
Preload("Likes").
183+
Joins("JOIN clubs ON posts.club_id = clubs.id").
184+
Where("clubs.is_private = ?", false). // Fix: false for public clubs
185+
Order("posts.likes_count DESC, posts.created_at DESC"). // Popular first
186+
Limit(20). // Limit for homepage
187+
Find(&posts).Error; err != nil {
188+
return nil, err
189+
}
190+
return posts, nil
99191
}
100192

101193
func (r *postRepository) ListPopularPublicPosts(limit int) ([]models.Post, error) {
102-
var posts []models.Post
103-
if err := r.db.
104-
Preload("User").
105-
Preload("Club").
106-
Preload("Comments").
107-
Preload("Likes").
108-
Joins("JOIN clubs ON posts.club_id = clubs.id").
109-
Where("clubs.is_private = ? AND posts.created_at > ?", false, time.Now().AddDate(0, 0, -30)). // Last 30 days
110-
Order("posts.likes_count DESC, posts.comments_count DESC, posts.created_at DESC").
111-
Limit(limit).
112-
Find(&posts).Error; err != nil {
113-
return nil, err
114-
}
115-
return posts, nil
194+
var posts []models.Post
195+
if err := r.db.
196+
Preload("User").
197+
Preload("Club").
198+
Preload("Comments").
199+
Preload("Likes").
200+
Joins("JOIN clubs ON posts.club_id = clubs.id").
201+
Where("clubs.is_private = ? AND posts.created_at > ?", false, time.Now().AddDate(0, 0, -30)). // Last 30 days
202+
Order("posts.likes_count DESC, posts.comments_count DESC, posts.created_at DESC").
203+
Limit(limit).
204+
Find(&posts).Error; err != nil {
205+
return nil, err
206+
}
207+
return posts, nil
116208
}
117209

118210
func (r *postRepository) AddLike(like *models.PostLike) error {
@@ -148,21 +240,21 @@ func (r *postRepository) ListLikesByPostID(postID uint) ([]models.PostLikeRespon
148240
}
149241

150242
res := make([]models.PostLikeResponse, 0, len(likes))
151-
for _, l := range likes {
152-
res = append(res, l.ToResponse())
153-
}
154-
return res, nil
243+
for _, l := range likes {
244+
res = append(res, l.ToResponse())
245+
}
246+
return res, nil
155247
}
156248

157249
func (r *postRepository) HasUserLiked(userID, postID uint) (bool, error) {
158-
var count int64
159-
err := r.db.Model(&models.PostLike{}).
160-
Where("user_id = ? AND post_id = ?", userID, postID).
161-
Count(&count).Error
162-
if err != nil {
163-
return false, err
164-
}
165-
return count > 0, nil
250+
var count int64
251+
err := r.db.Model(&models.PostLike{}).
252+
Where("user_id = ? AND post_id = ?", userID, postID).
253+
Count(&count).Error
254+
if err != nil {
255+
return false, err
256+
}
257+
return count > 0, nil
166258
}
167259

168260
func (r *postRepository) UpdateLikesCount(postID uint, count int) error {
@@ -172,52 +264,52 @@ func (r *postRepository) UpdateLikesCount(postID uint, count int) error {
172264
}
173265

174266
func (r *postRepository) VoteOnPoll(vote *models.PollVote) error {
175-
return r.db.Create(vote).Error
267+
return r.db.Create(vote).Error
176268
}
177269

178270
func (r *postRepository) RemoveVoteFromPoll(postID, userID uint, optionID string) error {
179-
return r.db.Where("post_id = ? AND user_id = ? AND option_id = ?", postID, userID, optionID).
180-
Delete(&models.PollVote{}).Error
271+
return r.db.Where("post_id = ? AND user_id = ? AND option_id = ?", postID, userID, optionID).
272+
Delete(&models.PollVote{}).Error
181273
}
182274

183275
func (r *postRepository) GetUserPollVotes(postID, userID uint) ([]models.PollVote, error) {
184-
var votes []models.PollVote
185-
err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).Find(&votes).Error
186-
return votes, err
276+
var votes []models.PollVote
277+
err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).Find(&votes).Error
278+
return votes, err
187279
}
188280

189281
// TODO: Find a way to update poll vote counts
190282
func (r *postRepository) UpdatePollVoteCounts(postID uint) error {
191-
return nil
283+
return nil
192284
}
193285

194286
func (r *postRepository) GetPostsByType(postType string, limit, offset int) ([]models.Post, error) {
195-
var posts []models.Post
196-
err := r.db.Where("type = ?", postType).
197-
Preload("User").
198-
Preload("Club").
199-
Limit(limit).
200-
Offset(offset).
201-
Find(&posts).Error
202-
return posts, err
287+
var posts []models.Post
288+
err := r.db.Where("type = ?", postType).
289+
Preload("User").
290+
Preload("Club").
291+
Limit(limit).
292+
Offset(offset).
293+
Find(&posts).Error
294+
return posts, err
203295
}
204296

205297
func (r *postRepository) GetReviewPostsByBookID(bookID uint) ([]models.Post, error) {
206-
var posts []models.Post
207-
err := r.db.Where("type = ? AND type_data->>'book_id' = ?", "review", strconv.Itoa(int(bookID))).
208-
Preload("User").
209-
Find(&posts).Error
210-
return posts, err
298+
var posts []models.Post
299+
err := r.db.Where("type = ? AND type_data->>'book_id' = ?", "review", strconv.Itoa(int(bookID))).
300+
Preload("User").
301+
Find(&posts).Error
302+
return posts, err
211303
}
212304

213305
func (r *postRepository) GetPollPostsByClubID(clubID uint, includeExpired bool) ([]models.Post, error) {
214-
query := r.db.Where("type = ? AND club_id = ?", "poll", clubID)
215-
216-
if !includeExpired {
217-
query = query.Where("(type_data->>'expires_at' IS NULL OR type_data->>'expires_at'::timestamp > NOW())")
218-
}
219-
220-
var posts []models.Post
221-
err := query.Preload("User").Find(&posts).Error
222-
return posts, err
223-
}
306+
query := r.db.Where("type = ? AND club_id = ?", "poll", clubID)
307+
308+
if !includeExpired {
309+
query = query.Where("(type_data->>'expires_at' IS NULL OR type_data->>'expires_at'::timestamp > NOW())")
310+
}
311+
312+
var posts []models.Post
313+
err := query.Preload("User").Find(&posts).Error
314+
return posts, err
315+
}

internal/services/post.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ func (s *PostService) ListAllPosts() ([]models.PostResponse, error) {
236236
return responses, nil
237237
}
238238

239+
func (s *PostService) ListPostSummaries(limit, offset int) ([]models.PostSummary, error) {
240+
posts, err := s.postRepo.ListPostSummaries(limit, offset)
241+
if err != nil {
242+
return nil, err
243+
}
244+
245+
return posts, nil
246+
}
247+
239248
func (s *PostService) ListPublicPosts() ([]models.PostResponse, error) {
240249
posts, err := s.postRepo.ListPublicPosts()
241250
if err != nil {

0 commit comments

Comments
 (0)