Skip to content

Commit 85221a2

Browse files
user endpoints added
1 parent 6672fa3 commit 85221a2

File tree

4 files changed

+166
-1
lines changed

4 files changed

+166
-1
lines changed

internal/handlers/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func (s *Server) setupRoutes() {
8888
protected.GET("/profile", userHandler.GetProfile)
8989
protected.GET("/users/:id", middleware.AuthorizeSelf(), userHandler.GetUser)
9090
protected.GET("/users", middleware.RestrictToRoles("admin", "superuser"), userHandler.GetAllUsers)
91+
protected.PUT("/users/:id", middleware.AuthorizeSelf(), userHandler.UpdateUser)
92+
protected.DELETE("/users/:id", middleware.AuthorizeSelf(), userHandler.DeleteUser)
9193
}
9294
}
9395

internal/handlers/user.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,75 @@ func (h *UserHandler) GetAllUsers(c *gin.Context) {
156156
}
157157

158158
c.JSON(http.StatusOK, gin.H{"users": users})
159+
}
160+
161+
// @Summary Update user
162+
// @Description Update user information
163+
// @Tags Users
164+
// @Accept json
165+
// @Produce json
166+
// @Param id path int true "User ID"
167+
// @Param request body models.UpdateUserRequest true "Update user data"
168+
// @Success 200 {object} map[string]interface{} "User updated successfully"
169+
// @Failure 400 {object} map[string]string "Bad request"
170+
// @Failure 404 {object} map[string]string "User not found"
171+
// @Failure 500 {object} map[string]string "Internal server error"
172+
// @Security Bearer
173+
// @Router /api/v1/users/{id} [put]
174+
func (h *UserHandler) UpdateUser(c *gin.Context) {
175+
var req models.UpdateUserRequest
176+
if err := c.ShouldBindJSON(&req); err != nil {
177+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
178+
return
179+
}
180+
181+
if err := h.validator.Struct(&req); err != nil {
182+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
183+
return
184+
}
185+
186+
userID, exists := c.Get("user_id")
187+
if !exists {
188+
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found in context"})
189+
return
190+
}
191+
192+
user, err := h.userService.UpdateUser(userID.(uint), &req)
193+
if err != nil {
194+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
195+
return
196+
}
197+
198+
c.JSON(http.StatusOK, gin.H{
199+
"message": "user updated successfully",
200+
"user": user,
201+
})
202+
}
203+
204+
// @Summary Delete user
205+
// @Description Delete a user by ID
206+
// @Tags Users
207+
// @Produce json
208+
// @Param id path int true "User ID"
209+
// @Success 204 {object} nil "User deleted successfully"
210+
// @Failure 400 {object} map[string]string "Bad request"
211+
// @Failure 404 {object} map[string]string "User not found"
212+
// @Failure 500 {object} map[string]string "Internal server error"
213+
// @Security Bearer
214+
// @Router /api/v1/users/{id} [delete]
215+
func (h *UserHandler) DeleteUser(c *gin.Context) {
216+
idParam := c.Param("id")
217+
id, err := strconv.ParseUint(idParam, 10, 32)
218+
if err != nil {
219+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})
220+
return
221+
}
222+
223+
err = h.userService.DeleteUser(uint(id))
224+
if err != nil {
225+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
226+
return
227+
}
228+
229+
c.JSON(http.StatusNoContent, nil)
159230
}

internal/models/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ type User struct {
2020
AvatarURL *string `json:"avatar_url" gorm:"type:text"`
2121
Location *string `json:"location" gorm:"size:255"`
2222
FavoriteGenres pq.StringArray `json:"favorite_genres" gorm:"type:text[]"`
23-
BooksRead int `json:"books_read" gorm:"default:0"`
2423
Bio *string `json:"bio" gorm:"type:text"`
2524
ReadingGoal int `json:"reading_goal" gorm:"default:0"`
25+
BooksRead int `json:"books_read" gorm:"default:0"`
2626
Badges pq.StringArray `json:"badges" gorm:"type:text[]"`
2727
IsOnline bool `json:"is_online" gorm:"default:false"`
2828
LastSeen *time.Time `json:"last_seen"`

internal/services/user.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,96 @@ func (s *UserService) GetAllUsers() ([]models.UserResponse, error) {
124124
}
125125

126126
return responses, nil
127+
}
128+
129+
func (s *UserService) UpdateUser(id uint, req *models.UpdateUserRequest) (*models.UserResponse, error) {
130+
user, err := s.userRepo.GetByID(id)
131+
if err != nil {
132+
if errors.Is(err, gorm.ErrRecordNotFound) {
133+
return nil, errors.New("user not found")
134+
}
135+
return nil, err
136+
}
137+
138+
if req.Username != nil && *req.Username != user.Username {
139+
existingUser, err := s.userRepo.GetByUsername(*req.Username)
140+
if err == nil && existingUser.ID != user.ID {
141+
return nil, errors.New("username already exists")
142+
}
143+
user.Username = *req.Username
144+
}
145+
146+
if req.Email != nil && *req.Email != user.Email {
147+
existingUser, err := s.userRepo.GetByEmail(*req.Email)
148+
if err == nil && existingUser.ID != user.ID {
149+
return nil, errors.New("email already exists")
150+
}
151+
user.Email = *req.Email
152+
}
153+
154+
if req.Password != nil {
155+
hashedPassword, err := utils.HashPassword(*req.Password)
156+
if err != nil {
157+
return nil, errors.New("failed to hash password")
158+
}
159+
user.PasswordHash = hashedPassword
160+
}
161+
162+
if req.FirstName != nil {
163+
user.FirstName = *req.FirstName
164+
}
165+
166+
if req.LastName != nil {
167+
user.LastName = *req.LastName
168+
}
169+
170+
if req.Role != nil {
171+
user.Role = *req.Role
172+
}
173+
174+
if req.IsActive != nil {
175+
user.IsActive = *req.IsActive
176+
}
177+
178+
if req.AvatarURL != nil {
179+
user.AvatarURL = req.AvatarURL
180+
}
181+
182+
if req.Location != nil {
183+
user.Location = req.Location
184+
}
185+
186+
if req.FavoriteGenres != nil {
187+
user.FavoriteGenres = *req.FavoriteGenres
188+
}
189+
190+
if req.Bio != nil {
191+
user.Bio = req.Bio
192+
}
193+
194+
if req.ReadingGoal != nil {
195+
user.ReadingGoal = *req.ReadingGoal
196+
}
197+
198+
if err := s.userRepo.Update(user); err != nil {
199+
return nil, errors.New("failed to update user")
200+
}
201+
202+
response := user.ToResponse()
203+
return &response, nil
204+
}
205+
206+
func (s *UserService) DeleteUser(id uint) error {
207+
err := s.userRepo.Delete(id)
208+
if err != nil {
209+
if errors.Is(err, gorm.ErrRecordNotFound) {
210+
return errors.New("user not found")
211+
}
212+
return err
213+
}
214+
215+
// decrement user count metric
216+
metrics.IncrementUserCount(-1)
217+
218+
return nil
127219
}

0 commit comments

Comments
 (0)