Skip to content

Commit e94035a

Browse files
add meeting type for club and add filtering
1 parent 392984c commit e94035a

File tree

5 files changed

+123
-13
lines changed

5 files changed

+123
-13
lines changed

internal/handlers/club.go

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,58 @@ func (h *ClubHandler) GetClub(c *gin.Context) {
9999
}
100100

101101
// @Summary Get all clubs
102-
// @Description Retrieve a list of all clubs
102+
// @Description Retrieve a list of all clubs with optional filters
103103
// @Tags Clubs
104104
// @Produce json
105+
// @Param location query string false "Filter by location (partial match)"
106+
// @Param genre query string false "Filter by genre (partial match)"
107+
// @Param meeting_type query string false "Filter by meeting type" Enums(online, in-person, hybrid)
108+
// @Param min_members query int false "Minimum member count"
109+
// @Param max_members query int false "Maximum member count"
110+
// @Param limit query int false "Number of results to return" default(20)
111+
// @Param offset query int false "Number of results to skip" default(0)
105112
// @Success 200 {object} map[string]interface{} "Clubs retrieved successfully"
113+
// @Failure 400 {object} map[string]string "Bad request - invalid filter parameters"
106114
// @Failure 500 {object} map[string]string "Internal server error"
107115
// @Router /api/v1/clubs [get]
108116
func (h *ClubHandler) GetAllClubs(c *gin.Context) {
109-
clubs, err := h.clubService.GetAllClubs()
117+
location := c.Query("location")
118+
genre := c.Query("genre")
119+
meetingType := c.Query("meeting_type")
120+
minMembers, _ := strconv.Atoi(c.DefaultQuery("min_members", "0"))
121+
maxMembers, _ := strconv.Atoi(c.DefaultQuery("max_members", "0"))
122+
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
123+
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
124+
125+
hasFilters := location != "" || genre != "" || meetingType != "" || minMembers > 0 || maxMembers > 0
126+
127+
if !hasFilters {
128+
clubs, err := h.clubService.GetAllClubs()
129+
if err != nil {
130+
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve clubs"})
131+
return
132+
}
133+
c.JSON(http.StatusOK, gin.H{"clubs": clubs})
134+
return
135+
}
136+
137+
if meetingType != "" && meetingType != "online" && meetingType != "in-person" && meetingType != "hybrid" {
138+
c.JSON(http.StatusBadRequest, gin.H{"error": "meeting_type must be one of: online, in-person, hybrid"})
139+
return
140+
}
141+
142+
clubs, err := h.clubService.GetClubsWithFilters(location, genre, meetingType, minMembers, maxMembers, limit, offset)
110143
if err != nil {
111144
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve clubs"})
112145
return
113146
}
114147

115-
c.JSON(http.StatusOK, gin.H{"clubs": clubs})
148+
c.JSON(http.StatusOK, gin.H{
149+
"clubs": clubs,
150+
"count": len(clubs),
151+
"limit": limit,
152+
"offset": offset,
153+
})
116154
}
117155

118156
// @Summary Update club
@@ -194,9 +232,9 @@ func (h *ClubHandler) DeleteClub(c *gin.Context) {
194232
}
195233

196234
if _, err := h.clubService.GetClubByID(uint(id)); err != nil {
197-
c.JSON(http.StatusNotFound, gin.H{"error": "club not found"})
198-
return
199-
}
235+
c.JSON(http.StatusNotFound, gin.H{"error": "club not found"})
236+
return
237+
}
200238

201239
uidRaw, ok := c.Get("user_id")
202240
if !ok {
@@ -307,12 +345,12 @@ func (h *ClubHandler) LeaveClub(c *gin.Context) {
307345
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
308346
return
309347
case "owner must choose action: transfer or close",
310-
"new_owner_id is required for transfer",
311-
"only the owner can transfer ownership",
312-
"new owner must be different from current owner",
313-
"new owner must be a member of the club",
314-
"new owner must be an approved member",
315-
"invalid action; must be one of: transfer, close":
348+
"new_owner_id is required for transfer",
349+
"only the owner can transfer ownership",
350+
"new owner must be different from current owner",
351+
"new owner must be a member of the club",
352+
"new owner must be an approved member",
353+
"invalid action; must be one of: transfer, close":
316354
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
317355
return
318356
default:
@@ -580,4 +618,4 @@ func (h *ClubHandler) GetMyClubs(c *gin.Context) {
580618
}
581619

582620
c.JSON(http.StatusOK, gin.H{"clubs": clubs})
583-
}
621+
}

internal/models/club.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Club struct {
3838
Name string `json:"name" gorm:"size:100;not null;unique"`
3939
Description string `json:"description" gorm:"type:text"`
4040
Location *string `json:"location" gorm:"size:255"`
41+
MeetingType *string `json:"meeting_type" gorm:"size:50" default:"online"`
4142
Genre *string `json:"genre" gorm:"size:100"`
4243
CoverImageURL *string `json:"cover_image_url" gorm:"type:text"`
4344
IsPrivate bool `json:"is_private" gorm:"default:false"`
@@ -80,6 +81,7 @@ type CreateClubRequest struct {
8081
Name string `json:"name" validate:"required,min=3,max=100"`
8182
Description string `json:"description" validate:"max=1000"`
8283
Location *string `json:"location" validate:"omitempty,max=255"`
84+
MeetingType *string `json:"meeting_type" validate:"omitempty,oneof=online in-person hybrid"`
8385
Genre *string `json:"genre" validate:"omitempty,max=100"`
8486
CoverImageURL *string `json:"cover_image_url" validate:"omitempty,url"`
8587
IsPrivate bool `json:"is_private"`
@@ -91,6 +93,7 @@ type UpdateClubRequest struct {
9193
Name *string `json:"name" validate:"omitempty,min=3,max=100"`
9294
Description *string `json:"description" validate:"omitempty,max=1000"`
9395
Location *string `json:"location" validate:"omitempty,max=255"`
96+
MeetingType *string `json:"meeting_type" validate:"omitempty,oneof=online in-person hybrid"`
9497
Genre *string `json:"genre" validate:"omitempty,max=100"`
9598
CoverImageURL *string `json:"cover_image_url" validate:"omitempty,url"`
9699
IsPrivate *bool `json:"is_private"`
@@ -115,6 +118,16 @@ type OwnerLeaveRequest struct {
115118
NewOwnerID *uint `json:"new_owner_id,omitempty"`
116119
}
117120

121+
type ClubFilterRequest struct {
122+
Location string `form:"location" validate:"omitempty,max=255"`
123+
Genre string `form:"genre" validate:"omitempty,max=100"`
124+
MeetingType string `form:"meeting_type" validate:"omitempty,oneof=online in-person hybrid"`
125+
MinMembers int `form:"min_members" validate:"omitempty,gte=0"`
126+
MaxMembers int `form:"max_members" validate:"omitempty,gte=0"`
127+
Limit int `form:"limit" validate:"omitempty,gte=1,lte=100"`
128+
Offset int `form:"offset" validate:"omitempty,gte=0"`
129+
}
130+
118131
type ClubMembershipResponse struct {
119132
ID uint `json:"id"`
120133
UserID uint `json:"user_id"`
@@ -130,6 +143,7 @@ type ClubResponse struct {
130143
Name string `json:"name"`
131144
Description string `json:"description"`
132145
Location *string `json:"location,omitempty"`
146+
MeetingType *string `json:"meeting_type,omitempty"`
133147
Genre *string `json:"genre,omitempty"`
134148
CoverImageURL *string `json:"cover_image_url,omitempty"`
135149
IsPrivate bool `json:"is_private"`
@@ -172,6 +186,7 @@ func (c *Club) ToResponse() ClubResponse {
172186
Name: c.Name,
173187
Description: c.Description,
174188
Location: c.Location,
189+
MeetingType: c.MeetingType,
175190
Genre: c.Genre,
176191
CoverImageURL: c.CoverImageURL,
177192
IsPrivate: c.IsPrivate,

internal/repository/club.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,47 @@ func (r *clubRepository) List(limit, offset int) ([]*models.Club, error) {
8383
return clubs, nil
8484
}
8585

86+
func (r *clubRepository) ListWithFilters(location, genre, meetingType string, minMembers, maxMembers, limit, offset int) ([]*models.Club, error) {
87+
var clubs []*models.Club
88+
query := r.db.Model(&models.Club{}).Preload("Owner")
89+
90+
if location != "" {
91+
query = query.Where("LOWER(location) LIKE LOWER(?)", "%"+location+"%")
92+
}
93+
94+
if genre != "" {
95+
query = query.Where("LOWER(genre) LIKE LOWER(?)", "%"+genre+"%")
96+
}
97+
98+
if meetingType != "" {
99+
query = query.Where("meeting_type = ?", meetingType)
100+
}
101+
102+
if minMembers > 0 {
103+
query = query.Where("members_count >= ?", minMembers)
104+
}
105+
106+
if maxMembers > 0 {
107+
query = query.Where("members_count <= ?", maxMembers)
108+
}
109+
110+
if limit <= 0 {
111+
limit = 20
112+
}
113+
query = query.Limit(limit).Offset(offset)
114+
115+
err := query.Order("created_at DESC").Find(&clubs).Error
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
if len(clubs) == 0 {
121+
return []*models.Club{}, nil
122+
}
123+
124+
return clubs, nil
125+
}
126+
86127
func (r *clubRepository) JoinClub(membership *models.ClubMembership) error {
87128
return r.db.Create(membership).Error
88129
}

internal/repository/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type ClubRepository interface {
2020
Update(club *models.Club) error
2121
Delete(id uint) error
2222
List(limit, offset int) ([]*models.Club, error)
23+
ListWithFilters(location, genre, meetingType string, minMembers, maxMembers, limit, offset int) ([]*models.Club, error)
2324
GetByName(name string) (*models.Club, error)
2425
JoinClub(membership *models.ClubMembership) error
2526
LeaveClub(clubID, userID uint) error

internal/services/club.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,19 @@ func (s *ClubService) ListUserClubs(userID uint) ([]models.ClubResponse, error)
526526
responses = append(responses, club.ToResponse())
527527
}
528528
return responses, nil
529+
}
530+
531+
func (s *ClubService) GetClubsWithFilters(location, genre, meetingType string, minMembers, maxMembers, limit, offset int) ([]*models.ClubResponse, error) {
532+
clubs, err := s.clubRepo.ListWithFilters(location, genre, meetingType, minMembers, maxMembers, limit, offset)
533+
if err != nil {
534+
return nil, err
535+
}
536+
537+
var responses []*models.ClubResponse
538+
for _, club := range clubs {
539+
resp := club.ToResponse()
540+
responses = append(responses, &resp)
541+
}
542+
543+
return responses, nil
529544
}

0 commit comments

Comments
 (0)