Skip to content

Commit 718535e

Browse files
add users preferences
1 parent f602355 commit 718535e

File tree

6 files changed

+185
-33
lines changed

6 files changed

+185
-33
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE users DROP COLUMN IF EXISTS preferences;
2+
DROP INDEX IF EXISTS idx_users_preferences;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ALTER TABLE users ADD COLUMN preferences JSONB DEFAULT '{}';
2+
3+
-- Create index for faster preferences queries
4+
CREATE INDEX idx_users_preferences ON users USING GIN (preferences);

internal/handlers/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func (s *Server) setupRoutes() {
171171
protected.PATCH("/users/:id/profile", userHandler.PatchProfile)
172172
protected.PATCH("/users/:id/account", userHandler.PatchAccount)
173173
protected.PATCH("/users/:id/avatar", userHandler.PatchAvatar)
174+
protected.PATCH("/user/preferences", userHandler.UpdatePreferences)
174175

175176
protected.POST("/clubs", clubHandler.CreateClub)
176177
protected.PUT("/clubs/:id", middleware.RequireClubMembershipWithRoles(clubRepo, "club_admin"), clubHandler.UpdateClub)

internal/handlers/user.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,4 +452,45 @@ func (h *UserHandler) SearchUsers(c *gin.Context) {
452452
"count": len(profiles),
453453
"query": query,
454454
})
455+
}
456+
457+
// @Summary Update user preferences
458+
// @Description Update user preferences including privacy settings and app preferences
459+
// @Tags Users
460+
// @Accept json
461+
// @Produce json
462+
// @Param request body models.UpdatePreferencesRequest true "Preferences data"
463+
// @Success 200 {object} map[string]interface{} "Preferences updated successfully"
464+
// @Failure 400 {object} map[string]string "Bad request"
465+
// @Failure 401 {object} map[string]string "Unauthorized"
466+
// @Security Bearer
467+
// @Router /api/v1/user/preferences [patch]
468+
func (h *UserHandler) UpdatePreferences(c *gin.Context) {
469+
userID, exists := c.Get("user_id")
470+
if !exists {
471+
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found in context"})
472+
return
473+
}
474+
475+
var req models.UpdatePreferencesRequest
476+
if err := c.ShouldBindJSON(&req); err != nil {
477+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
478+
return
479+
}
480+
481+
if err := h.validator.Struct(&req); err != nil {
482+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
483+
return
484+
}
485+
486+
user, err := h.userService.UpdatePreferences(userID.(uint), &req)
487+
if err != nil {
488+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
489+
return
490+
}
491+
492+
c.JSON(http.StatusOK, gin.H{
493+
"message": "preferences updated successfully",
494+
"user": user,
495+
})
455496
}

internal/models/user.go

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,57 @@ import (
77
"gorm.io/gorm"
88
)
99

10+
type UserPreferences map[string]interface{}
11+
12+
const (
13+
PREF_SHOW_LOCATION = "privacy.show_location"
14+
PREF_SHOW_LAST_SEEN = "privacy.show_last_seen"
15+
PREF_ALLOW_SEARCH = "privacy.allow_search"
16+
17+
PREF_LANGUAGE = "app.language"
18+
PREF_THEME = "app.theme"
19+
PREF_TIMEZONE = "app.timezone"
20+
PREF_NOTIFICATIONS = "notifications.enabled"
21+
PREF_EMAIL_NOTIFICATIONS = "notifications.email"
22+
PREF_PUSH_NOTIFICATIONS = "notifications.push"
23+
)
24+
25+
func DefaultUserPreferences() UserPreferences {
26+
return UserPreferences{
27+
PREF_SHOW_LOCATION: true,
28+
PREF_SHOW_LAST_SEEN: true,
29+
PREF_ALLOW_SEARCH: true,
30+
PREF_LANGUAGE: "tr",
31+
PREF_THEME: "auto",
32+
PREF_TIMEZONE: "Europe/Istanbul",
33+
PREF_NOTIFICATIONS: true,
34+
PREF_EMAIL_NOTIFICATIONS: false,
35+
PREF_PUSH_NOTIFICATIONS: true,
36+
}
37+
}
38+
39+
func (up UserPreferences) GetBool(key string, defaultVal bool) bool {
40+
if val, exists := up[key]; exists {
41+
if boolVal, ok := val.(bool); ok {
42+
return boolVal
43+
}
44+
}
45+
return defaultVal
46+
}
47+
48+
func (up UserPreferences) GetString(key string, defaultVal string) string {
49+
if val, exists := up[key]; exists {
50+
if strVal, ok := val.(string); ok {
51+
return strVal
52+
}
53+
}
54+
return defaultVal
55+
}
56+
57+
func (up UserPreferences) Set(key string, value interface{}) {
58+
up[key] = value
59+
}
60+
1061
type User struct {
1162
ID uint `json:"id" gorm:"primaryKey"`
1263
Username string `json:"username" gorm:"uniqueIndex;not null" validate:"required,min=3,max=50"`
@@ -27,6 +78,8 @@ type User struct {
2778
IsOnline bool `json:"is_online" gorm:"default:false"`
2879
LastSeen *time.Time `json:"last_seen"`
2980

81+
Preferences UserPreferences `json:"preferences" gorm:"type:jsonb;default:'{}'"`
82+
3083
OwnedClubs []Club `json:"owned_clubs,omitempty" gorm:"foreignKey:OwnerID" swaggerignore:"true"`
3184
ClubMemberships []ClubMembership `json:"club_memberships,omitempty" gorm:"foreignKey:UserID" swaggerignore:"true"`
3285
Posts []Post `json:"posts,omitempty" gorm:"foreignKey:UserID" swaggerignore:"true"`
@@ -41,23 +94,23 @@ type User struct {
4194

4295
type PublicUserProfile struct {
4396
ID uint `json:"id"`
44-
Username string `json:"username"`
45-
FirstName string `json:"first_name"`
46-
LastName string `json:"last_name"`
47-
AvatarURL *string `json:"avatar_url"`
48-
Location *string `json:"location"`
49-
FavoriteGenres pq.StringArray `json:"favorite_genres"`
50-
Bio *string `json:"bio"`
51-
BooksRead int `json:"books_read"`
52-
Badges pq.StringArray `json:"badges"`
53-
IsOnline bool `json:"is_online"`
54-
LastSeen *time.Time `json:"last_seen,omitempty"`
55-
JoinedAt time.Time `json:"joined_at"`
56-
57-
TotalPosts int `json:"total_posts"`
58-
TotalComments int `json:"total_comments"`
59-
ClubsCount int `json:"clubs_count"`
60-
ReadingStreak int `json:"reading_streak,omitempty"`
97+
Username string `json:"username"`
98+
FirstName string `json:"first_name"`
99+
LastName string `json:"last_name"`
100+
AvatarURL *string `json:"avatar_url"`
101+
Location *string `json:"location"`
102+
FavoriteGenres pq.StringArray `json:"favorite_genres"`
103+
Bio *string `json:"bio"`
104+
BooksRead int `json:"books_read"`
105+
Badges pq.StringArray `json:"badges"`
106+
IsOnline bool `json:"is_online"`
107+
LastSeen *time.Time `json:"last_seen,omitempty"`
108+
JoinedAt time.Time `json:"joined_at"`
109+
110+
TotalPosts int `json:"total_posts"`
111+
TotalComments int `json:"total_comments"`
112+
ClubsCount int `json:"clubs_count"`
113+
ReadingStreak int `json:"reading_streak,omitempty"`
61114
}
62115

63116
type UserResponse struct {
@@ -79,6 +132,8 @@ type UserResponse struct {
79132
IsOnline bool `json:"is_online"`
80133
LastSeen *time.Time `json:"last_seen"`
81134

135+
Preferences UserPreferences `json:"preferences"`
136+
82137
OwnedClubs []Club `json:"owned_clubs,omitempty" swaggerignore:"true"`
83138
ClubMemberships []ClubMembership `json:"club_memberships,omitempty" swaggerignore:"true"`
84139
Posts []Post `json:"posts,omitempty" swaggerignore:"true"`
@@ -148,7 +203,15 @@ type UpdateAccountRequest struct {
148203
Username *string `json:"username,omitempty" validate:"omitempty,min=3,max=50"`
149204
}
150205

206+
type UpdatePreferencesRequest struct {
207+
Preferences UserPreferences `json:"preferences" validate:"required"`
208+
}
209+
151210
func (u *User) ToResponse() UserResponse {
211+
if len(u.Preferences) == 0 {
212+
u.Preferences = DefaultUserPreferences()
213+
}
214+
152215
return UserResponse{
153216
ID: u.ID,
154217
Username: u.Username,
@@ -166,6 +229,7 @@ func (u *User) ToResponse() UserResponse {
166229
Badges: u.Badges,
167230
IsOnline: u.IsOnline,
168231
LastSeen: u.LastSeen,
232+
Preferences: u.Preferences,
169233
OwnedClubs: u.OwnedClubs,
170234
ClubMemberships: u.ClubMemberships,
171235
Posts: u.Posts,
@@ -187,19 +251,34 @@ type SuccessResponse struct {
187251
}
188252

189253
func (u *User) ToPublicProfile() PublicUserProfile {
190-
return PublicUserProfile{
191-
ID: u.ID,
192-
Username: u.Username,
193-
FirstName: u.FirstName,
194-
LastName: u.LastName,
195-
AvatarURL: u.AvatarURL,
196-
Location: u.Location,
197-
FavoriteGenres: u.FavoriteGenres,
198-
Bio: u.Bio,
199-
BooksRead: u.BooksRead,
200-
Badges: u.Badges,
201-
IsOnline: u.IsOnline,
202-
LastSeen: u.LastSeen,
203-
JoinedAt: u.CreatedAt,
204-
}
205-
}
254+
prefs := u.Preferences
255+
if len(prefs) == 0 {
256+
prefs = DefaultUserPreferences()
257+
}
258+
259+
profile := PublicUserProfile{
260+
ID: u.ID,
261+
Username: u.Username,
262+
FirstName: u.FirstName,
263+
LastName: u.LastName,
264+
JoinedAt: u.CreatedAt,
265+
BooksRead: u.BooksRead,
266+
Badges: u.Badges,
267+
IsOnline: u.IsOnline,
268+
}
269+
270+
if prefs.GetBool(PREF_SHOW_LOCATION, true) {
271+
profile.Location = u.Location
272+
}
273+
274+
if prefs.GetBool(PREF_SHOW_LAST_SEEN, true) && u.LastSeen != nil && u.IsOnline {
275+
since := time.Since(*u.LastSeen)
276+
if since <= 15*time.Minute {
277+
profile.LastSeen = u.LastSeen
278+
}
279+
}
280+
281+
profile.AvatarURL = u.AvatarURL
282+
283+
return profile
284+
}

internal/services/user.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,28 @@ func (s *UserService) UpdateAccount(id uint, req *models.UpdateAccountRequest) (
404404
response := user.ToResponse()
405405
return &response, nil
406406
}
407+
408+
func (s *UserService) UpdatePreferences(userID uint, req *models.UpdatePreferencesRequest) (*models.UserResponse, error) {
409+
user, err := s.userRepo.GetByID(userID)
410+
if err != nil {
411+
if errors.Is(err, gorm.ErrRecordNotFound) {
412+
return nil, errors.New("user not found")
413+
}
414+
return nil, err
415+
}
416+
417+
if len(user.Preferences) == 0 {
418+
user.Preferences = models.DefaultUserPreferences()
419+
}
420+
421+
for key, value := range req.Preferences {
422+
user.Preferences[key] = value
423+
}
424+
425+
if err := s.userRepo.Update(user); err != nil {
426+
return nil, errors.New("failed to update preferences")
427+
}
428+
429+
response := user.ToResponse()
430+
return &response, nil
431+
}

0 commit comments

Comments
 (0)