package main import ( "context" "errors" "log" "net/http" "strconv" "github.com/FernandoVideira/LK_API_Temp/internal/store" "github.com/go-chi/chi/v5" ) type userKey string const userContextKey userKey = "user" // GetUser godoc // // @Summary Fetches a user Profile // @Description Fetches a user Profile by ID // @Tags users // @Accept json // @Produce json // @Param id path int true "User ID" // @Success 200 {object} store.User // @Failure 400 {object} error // @Failure 404 {object} error // @Failure 500 {object} error // @Security ApiKeyAuth // @Router /users/{id} [get] func (api *api) getUserHandler(w http.ResponseWriter, r *http.Request) { userID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil || userID < 1 { api.badRequestError(w, r, err) return } ctx := r.Context() user, err := api.getUser(ctx, userID) if err != nil { switch err { case store.ErrNotFound: api.notFoundError(w, r, err) return default: api.internalServerError(w, r, err) return } } if err := api.jsonResponse(w, http.StatusOK, user); err != nil { api.internalServerError(w, r, err) } } type updateUserPayload struct { FirstName *string `json:"first_name" validate:"omitempty,max=100"` LastName *string `json:"last_name" validate:"omitempty,max=100"` Username *string `json:"username" validate:"omitempty,max=100"` Email *string `json:"email" validate:"omitempty,email,max=255"` } // Update godoc // // @Summary Update a user Profile // @Description Update a user Profile by ID // @Tags users // @Accept json // @Produce json // @Param id path int true "User ID" // @Param payload body updateUserPayload true "User Profile" // @Success 200 {object} store.User // @Failure 400 {object} error // @Failure 404 {object} error // @Failure 500 {object} error // @Security ApiKeyAuth // @Router /users/{id} [patch] func (api *api) updateUserHandler(w http.ResponseWriter, r *http.Request) { user := getUserFromContext(r) log.Println(user) var payload updateUserPayload if err := readJSON(w, r, &payload); err != nil { api.badRequestError(w, r, err) return } user = validateUser(payload, user) if err := api.updateUser(r.Context(), user); err != nil { api.internalServerError(w, r, err) return } if err := api.jsonResponse(w, http.StatusOK, user); err != nil { api.internalServerError(w, r, err) } } func (api *api) updateUser(ctx context.Context, user *store.User) error { if err := api.store.Users.Update(ctx, user); err != nil { return err } api.cacheStore.Users.Delete(ctx, user.ID) return nil } // FollowUser godoc // // @Summary Follows a user // @Description Follows a user by ID // @Tags users // @Accept json // @Produce json // @Param id path int true "User ID" // @Success 204 {string} string "User followed" // @Failure 400 {object} error "User not found" // @Router /users/{id}/follow [put] func (api *api) followUserHandler(w http.ResponseWriter, r *http.Request) { followee := getUserFromContext(r) followedID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { api.badRequestError(w, r, err) return } if err := api.store.Followers.Follow(r.Context(), followee.ID, followedID); err != nil { switch { case errors.Is(err, store.ErrConflict): api.conflictError(w, r, err) return default: api.internalServerError(w, r, err) return } } if err := api.jsonResponse(w, http.StatusNoContent, nil); err != nil { api.internalServerError(w, r, err) } } // UnollowUser godoc // // @Summary Unfollows a user // @Description Unfollows a user by ID // @Tags users // @Accept json // @Produce json // @Param id path int true "User ID" // @Success 204 {string} string "User unfollowed" // @Failure 400 {object} error "User payload error" // @Failure 404 {object} error "User not found" // @Router /users/{id}/unfollow [put] func (api *api) unfollowUserHandler(w http.ResponseWriter, r *http.Request) { followee := getUserFromContext(r) followedUserID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { api.badRequestError(w, r, err) return } if err := api.store.Followers.Unfollow(r.Context(), followee.ID, followedUserID); err != nil { switch { case errors.Is(err, store.ErrNotFound): api.conflictError(w, r, err) return default: api.internalServerError(w, r, err) return } } w.WriteHeader(http.StatusNoContent) if err := api.jsonResponse(w, http.StatusNoContent, nil); err != nil { api.internalServerError(w, r, err) } } // ActivateUser godoc // // @Summary Activate/Register a user // @Description Activate/Register a user by invitation token // @Tags users // @Accept json // @Produce json // @Param token path string true "Invitation Token" // @Success 204 {string} string "User activated" // @Failure 404 {object} error "User not found" // @Failure 500 {object} error "Internal Server Error" // @Security ApiKeyAuth // @Router /users/activate/{token} [put] func (api *api) activateUserHandler(w http.ResponseWriter, r *http.Request) { token := chi.URLParam(r, "token") err := api.store.Users.Activate(r.Context(), token) if err != nil { switch err { case store.ErrNotFound: api.notFoundError(w, r, err) return default: api.internalServerError(w, r, err) return } } w.WriteHeader(http.StatusNoContent) if err := api.jsonResponse(w, http.StatusNoContent, ""); err != nil { api.internalServerError(w, r, err) } } type updateUserPasswordPayload struct { CurrentPassword string `json:"current_password" validate:"required"` NewPassword string `json:"new_password" validate:"required,password,nefield=CurrentPassword"` } // UpdatePassword godoc // // @Summary Update user password // @Description Update user password with verification // @Description Password must: // @Description - Be at least 8 characters long // @Description - Contain at least one uppercase letter // @Description - Contain at least one lowercase letter // @Description - Contain at least one number // @Description - Contain at least one special character // @Description - Be different from the current password // @Tags users // @Accept json // @Produce json // @Param id path int true "User ID" // @Param payload body updateUserPasswordPayload true "Password Update" // @Success 204 {string} string "Password updated successfully" // @Failure 400 {object} error "Invalid request" // @Failure 401 {object} error "Unauthorized" // @Failure 404 {object} error "User not found" // @Failure 500 {object} error "Internal server error" // @Security ApiKeyAuth // @Router /users/{id}/password [patch] func (api *api) updatePasswordHandler(w http.ResponseWriter, r *http.Request) { user := getUserFromContext(r) var payload updateUserPasswordPayload if err := readJSON(w, r, &payload); err != nil { api.badRequestError(w, r, err) return } if err := Validate.Struct(payload); err != nil { api.badRequestError(w, r, err) return } if err := user.Password.Compare(payload.CurrentPassword); err != nil { api.unauthorizedError(w, r, err) return } if err := user.Password.Set(payload.NewPassword); err != nil { api.internalServerError(w, r, err) return } if err := api.updateUser(r.Context(), user); err != nil { api.internalServerError(w, r, err) return } w.WriteHeader(http.StatusNoContent) if err := api.jsonResponse(w, http.StatusNoContent, ""); err != nil { api.internalServerError(w, r, err) } } func validateUser(payload updateUserPayload, usrToUpdate *store.User) *store.User { if payload.Email != nil { usrToUpdate.Email = *payload.Email } if payload.FirstName != nil { usrToUpdate.FirstName = *payload.FirstName } if payload.LastName != nil { usrToUpdate.LastName = *payload.LastName } if payload.Username != nil { usrToUpdate.Username = *payload.Username } return usrToUpdate } func getUserFromContext(r *http.Request) *store.User { user, _ := r.Context().Value(userContextKey).(*store.User) return user }