Skip to content

Commit 35434bd

Browse files
update post types
1 parent a3ca4f5 commit 35434bd

File tree

6 files changed

+482
-53
lines changed

6 files changed

+482
-53
lines changed

internal/handlers/post.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,62 @@ func (h *PostHandler) ListLikesByPostID(c *gin.Context) {
416416
}
417417

418418
c.JSON(http.StatusOK, gin.H{"likes": likes})
419+
}
420+
421+
// @Summary Vote on a poll
422+
// @Description Vote on a poll post
423+
// @Tags Posts
424+
// @Accept json
425+
// @Produce json
426+
// @Param id path int true "Post ID"
427+
// @Param vote body models.PollVoteRequest true "Vote data"
428+
// @Success 200 {object} models.SuccessResponse "Vote recorded successfully"
429+
// @Failure 400 {object} models.ErrorResponse "Bad request"
430+
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
431+
// @Failure 404 {object} models.ErrorResponse "Post not found"
432+
// @Router /posts/{id}/vote [post]
433+
func (h *PostHandler) VoteOnPoll(c *gin.Context) {
434+
postID, err := strconv.ParseUint(c.Param("id"), 10, 32)
435+
if err != nil {
436+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid post ID"})
437+
return
438+
}
439+
440+
userID, _ := c.Get("user_id")
441+
442+
var req models.PollVoteRequest
443+
if err := c.ShouldBindJSON(&req); err != nil {
444+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
445+
return
446+
}
447+
448+
if err := h.postService.VoteOnPoll(uint(postID), userID.(uint), &req); err != nil {
449+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
450+
return
451+
}
452+
453+
c.JSON(http.StatusOK, gin.H{"message": "vote recorded successfully"})
454+
}
455+
456+
// @Summary Get reviews by book
457+
// @Description Get all review posts for a specific book
458+
// @Tags Posts
459+
// @Produce json
460+
// @Param book_id query int true "Book ID"
461+
// @Success 200 {array} models.PostResponse "Reviews retrieved successfully"
462+
// @Router /posts/reviews [get]
463+
func (h *PostHandler) GetReviewsByBook(c *gin.Context) {
464+
bookID, err := strconv.ParseUint(c.Query("book_id"), 10, 32)
465+
if err != nil {
466+
c.JSON(http.StatusBadRequest, gin.H{"error": "book_id is required"})
467+
return
468+
}
469+
470+
reviews, err := h.postService.GetReviewsByBook(uint(bookID))
471+
if err != nil {
472+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
473+
return
474+
}
475+
476+
c.JSON(http.StatusOK, gin.H{"reviews": reviews})
419477
}

internal/handlers/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (s *Server) setupRoutes() {
9696
bookService := services.NewBookService(bookRepo, s.config)
9797
bookHandler := NewBookHandler(bookService)
9898

99-
postService := services.NewPostService(postRepo, userRepo, clubRepo, s.config)
99+
postService := services.NewPostService(postRepo, userRepo, clubRepo, bookRepo, s.db, s.config)
100100
postHandler := NewPostHandler(postService)
101101

102102
commentService := services.NewCommentService(commentRepo, postRepo, userRepo, s.config)

internal/models/post.go

Lines changed: 166 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,59 @@
11
package models
22

33
import (
4+
"database/sql/driver"
5+
"encoding/json"
6+
"errors"
47
"time"
58

69
"gorm.io/gorm"
710
)
811

12+
type Post struct {
13+
ID uint `json:"id" gorm:"primaryKey"`
14+
Title string `json:"title" gorm:"size:255;not null"`
15+
Content string `json:"content" gorm:"type:text;not null"`
16+
Type string `json:"type" gorm:"not null" validate:"required,oneof=discussion announcement post poll review annotation" default:"discussion"`
17+
TypeData PostTypeData `json:"type_data,omitempty" gorm:"type:jsonb"`
18+
IsPinned bool `json:"is_pinned" gorm:"default:false"`
19+
LikesCount int `json:"likes_count" gorm:"default:0"`
20+
CommentsCount int `json:"comments_count" gorm:"default:0"`
21+
ViewsCount int `json:"views_count" gorm:"default:0"`
22+
UserID uint `json:"user_id"`
23+
ClubID uint `json:"club_id"`
24+
25+
Club Club `json:"club" gorm:"foreignKey:ClubID" swaggerignore:"true"`
26+
User User `json:"user" gorm:"foreignKey:UserID" swaggerignore:"true"`
27+
Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:PostID" swaggerignore:"true"`
28+
Likes []PostLike `json:"likes,omitempty" gorm:"foreignKey:PostID" swaggerignore:"true"`
29+
30+
CreatedAt time.Time `json:"created_at"`
31+
UpdatedAt time.Time `json:"updated_at"`
32+
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
33+
}
34+
35+
type PostTypeData json.RawMessage
36+
37+
func (ptd *PostTypeData) Scan(value interface{}) error {
38+
if value == nil {
39+
*ptd = nil
40+
return nil
41+
}
42+
bytes, ok := value.([]byte)
43+
if !ok {
44+
return errors.New("cannot scan into PostTypeData")
45+
}
46+
*ptd = bytes
47+
return nil
48+
}
49+
50+
func (ptd PostTypeData) Value() (driver.Value, error) {
51+
if len(ptd) == 0 {
52+
return nil, nil
53+
}
54+
return []byte(ptd), nil
55+
}
56+
957
type PostLike struct {
1058
ID uint `json:"id" gorm:"primaryKey"`
1159
UserID uint `json:"user_id" gorm:"uniqueIndex:idx_user_post_like"`
@@ -17,62 +65,132 @@ type PostLike struct {
1765
UpdatedAt time.Time `json:"updated_at"`
1866
}
1967

20-
type Post struct {
21-
ID uint `json:"id" gorm:"primaryKey"`
22-
Title string `json:"title" gorm:"size:255;not null"`
23-
Content string `json:"content" gorm:"type:text;not null"`
24-
Type string `json:"type" gorm:"not null" validate:"required,oneof=discussion announcement event poll review annotation" default:"discussion"`
25-
IsPinned bool `json:"is_pinned" gorm:"default:false"`
26-
LikesCount int `json:"likes_count" gorm:"default:0"`
27-
CommentsCount int `json:"comments_count" gorm:"default:0"`
28-
ViewsCount int `json:"views_count" gorm:"default:0"`
29-
UserID uint `json:"user_id"`
30-
ClubID uint `json:"club_id"`
68+
type PollVote struct {
69+
ID uint `json:"id" gorm:"primaryKey"`
70+
PostID uint `json:"post_id" gorm:"index"`
71+
UserID uint `json:"user_id" gorm:"index"`
72+
OptionID string `json:"option_id"`
3173

32-
Club Club `json:"club" gorm:"foreignKey:ClubID" swaggerignore:"true"`
33-
User User `json:"user" gorm:"foreignKey:UserID" swaggerignore:"true"`
34-
Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:PostID" swaggerignore:"true"`
35-
Likes []PostLike `json:"likes,omitempty" gorm:"foreignKey:PostID" swaggerignore:"true"`
74+
CreatedAt time.Time `json:"created_at"`
75+
UpdatedAt time.Time `json:"updated_at"`
76+
}
3677

37-
CreatedAt time.Time `json:"created_at"`
38-
UpdatedAt time.Time `json:"updated_at"`
39-
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
78+
type ReviewData struct {
79+
BookID uint `json:"book_id" validate:"required"`
80+
Rating float32 `json:"rating" validate:"required,gte=1,lte=5"`
81+
BookTitle string `json:"book_title,omitempty"`
82+
BookAuthor string `json:"book_author,omitempty"`
83+
ReviewText *string `json:"review_text,omitempty"`
84+
}
85+
86+
type PollData struct {
87+
Question string `json:"question" validate:"required,min=1,max=500"`
88+
Options []PollOption `json:"options" validate:"required,min=2,max=10,dive"`
89+
AllowMultiple bool `json:"allow_multiple"`
90+
ExpiresAt *time.Time `json:"expires_at,omitempty"`
91+
}
92+
93+
type PollOption struct {
94+
ID string `json:"id"`
95+
Text string `json:"text" validate:"required,min=1,max=200"`
96+
Votes int `json:"votes"`
97+
}
98+
99+
type AnnotationData struct {
100+
BookID uint `json:"book_id" validate:"required"`
101+
Page *int `json:"page,omitempty" validate:"omitempty,gte=1"`
102+
Chapter *int `json:"chapter,omitempty" validate:"omitempty,gte=1"`
103+
Quote string `json:"quote,omitempty" validate:"omitempty,max=1000"`
104+
BookTitle string `json:"book_title,omitempty"`
105+
BookAuthor string `json:"book_author,omitempty"`
106+
}
107+
108+
type PostData struct {
109+
PostID uint `json:"post_id,omitempty"`
110+
PostTitle string `json:"post_title,omitempty"`
111+
PostContent string `json:"post_content,omitempty"`
112+
}
113+
114+
func (p *Post) GetReviewData() (*ReviewData, error) {
115+
if p.Type != "review" || len(p.TypeData) == 0 {
116+
return nil, nil
117+
}
118+
var data ReviewData
119+
err := json.Unmarshal(p.TypeData, &data)
120+
return &data, err
121+
}
122+
123+
func (p *Post) GetPollData() (*PollData, error) {
124+
if p.Type != "poll" || len(p.TypeData) == 0 {
125+
return nil, nil
126+
}
127+
var data PollData
128+
err := json.Unmarshal(p.TypeData, &data)
129+
return &data, err
130+
}
131+
132+
func (p *Post) GetAnnotationData() (*AnnotationData, error) {
133+
if p.Type != "annotation" || len(p.TypeData) == 0 {
134+
return nil, nil
135+
}
136+
var data AnnotationData
137+
err := json.Unmarshal(p.TypeData, &data)
138+
return &data, err
139+
}
140+
141+
func (p *Post) GetPostData() (*PostData, error) {
142+
if p.Type != "post" || len(p.TypeData) == 0 {
143+
return nil, nil
144+
}
145+
var data PostData
146+
err := json.Unmarshal(p.TypeData, &data)
147+
return &data, err
40148
}
41149

42150
type CreatePostRequest struct {
43-
Title string `json:"title" validate:"required,min=1,max=255"`
44-
Content string `json:"content" validate:"required,min=1"`
45-
Type string `json:"type" validate:"required,oneof=discussion announcement event poll review annotation"`
46-
ClubID uint `json:"club_id" validate:"required"`
151+
Title string `json:"title" validate:"required,min=1,max=255"`
152+
Content string `json:"content" validate:"required,min=1"`
153+
Type string `json:"type" validate:"required,oneof=discussion announcement post poll review annotation"`
154+
ClubID uint `json:"club_id" validate:"required"`
155+
TypeData interface{} `json:"type_data,omitempty"`
47156
}
48157

49158
type UpdatePostRequest struct {
50-
Title *string `json:"title,omitempty" validate:"omitempty,min=1,max=255"`
51-
Content *string `json:"content,omitempty" validate:"omitempty,min=1"`
52-
Type *string `json:"type,omitempty" validate:"omitempty,oneof=discussion announcement event poll review annotation"`
53-
ClubID *uint `json:"club_id,omitempty" validate:"omitempty"`
54-
IsPinned *bool `json:"is_pinned,omitempty"`
159+
Title *string `json:"title,omitempty" validate:"omitempty,min=1,max=255"`
160+
Content *string `json:"content,omitempty" validate:"omitempty,min=1"`
161+
Type *string `json:"type,omitempty" validate:"omitempty,oneof=discussion announcement post poll review annotation"`
162+
ClubID *uint `json:"club_id,omitempty" validate:"omitempty"`
163+
IsPinned *bool `json:"is_pinned,omitempty"`
164+
TypeData interface{} `json:"type_data,omitempty"`
55165
}
56166

57167
type PostResponse struct {
58-
ID uint `json:"id"`
59-
Title string `json:"title"`
60-
Content string `json:"content"`
61-
Type string `json:"type"`
62-
IsPinned bool `json:"is_pinned"`
63-
LikesCount int `json:"likes_count"`
64-
CommentsCount int `json:"comments_count"`
65-
ViewsCount int `json:"views_count"`
66-
UserID uint `json:"user_id"`
67-
ClubID uint `json:"club_id"`
68-
User User `json:"user" swaggerignore:"true"`
69-
Comments []Comment `json:"comments,omitempty" swaggerignore:"true"`
70-
Likes []PostLike `json:"likes,omitempty" swaggerignore:"true"`
168+
ID uint `json:"id"`
169+
Title string `json:"title"`
170+
Content string `json:"content"`
171+
Type string `json:"type"`
172+
TypeData interface{} `json:"type_data,omitempty"`
173+
IsPinned bool `json:"is_pinned"`
174+
LikesCount int `json:"likes_count"`
175+
CommentsCount int `json:"comments_count"`
176+
ViewsCount int `json:"views_count"`
177+
UserID uint `json:"user_id"`
178+
ClubID uint `json:"club_id"`
179+
User User `json:"user" swaggerignore:"true"`
180+
Comments []Comment `json:"comments,omitempty" swaggerignore:"true"`
181+
Likes []PostLike `json:"likes,omitempty" swaggerignore:"true"`
182+
183+
UserVoted bool `json:"user_voted,omitempty"`
184+
UserVotes []string `json:"user_votes,omitempty"`
71185

72186
CreatedAt time.Time `json:"created_at"`
73187
UpdatedAt time.Time `json:"updated_at"`
74188
}
75189

190+
type PollVoteRequest struct {
191+
OptionIDs []string `json:"option_ids" validate:"required,min=1"`
192+
}
193+
76194
type PostLikeResponse struct {
77195
ID uint `json:"id"`
78196
User UserResponse `json:"user"`
@@ -88,7 +206,7 @@ func (pl PostLike) ToResponse() PostLikeResponse {
88206
}
89207

90208
func (p *Post) ToResponse() PostResponse {
91-
return PostResponse{
209+
response := PostResponse{
92210
ID: p.ID,
93211
Title: p.Title,
94212
Content: p.Content,
@@ -105,4 +223,11 @@ func (p *Post) ToResponse() PostResponse {
105223
CreatedAt: p.CreatedAt,
106224
UpdatedAt: p.UpdatedAt,
107225
}
226+
227+
if len(p.TypeData) > 0 {
228+
var typeData interface{}
229+
json.Unmarshal(p.TypeData, &typeData)
230+
response.TypeData = typeData
231+
}
232+
return response
108233
}

internal/repository/interfaces.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ type PostRepository interface {
7373
HasUserLiked(userID, postID uint) (bool, error)
7474
CountLikes(postID uint) (int64, error)
7575
UpdateLikesCount(postID uint, count int) error
76+
VoteOnPoll(vote *models.PollVote) error
77+
RemoveVoteFromPoll(postID, userID uint, optionID string) error
78+
GetUserPollVotes(postID, userID uint) ([]models.PollVote, error)
79+
UpdatePollVoteCounts(postID uint) error
80+
GetPostsByType(postType string, limit, offset int) ([]models.Post, error)
81+
GetReviewPostsByBookID(bookID uint) ([]models.Post, error)
82+
GetPollPostsByClubID(clubID uint, includeExpired bool) ([]models.Post, error)
7683
}
7784

7885
type CommentRepository interface {

0 commit comments

Comments
 (0)