Files
LK_API_Temp/cmd/api/users.go
T

339 lines
8.8 KiB
Go

package main
import (
"context"
"errors"
"net/http"
"strconv"
"github.com/FernandoVideira/LK_API_Temp/internal/store"
"github.com/go-chi/chi/v5"
)
type userKey string
type roleKey string
const userContextKey userKey = "user"
const roleContextKey roleKey = "role"
// GetUser godoc
//
// @Summary Fetches all users
// @Description Fetches all users
// @Tags admin
// @Accept json
// @Produce json
// @Success 200 {object} store.User
// @Failure 400 {object} error
// @Failure 404 {object} error
// @Failure 500 {object} error
// @Security ApiKeyAuth
// @Router /admin/users [get]
func (api *api) getUsersHandler(w http.ResponseWriter, r *http.Request) {
role := getRoleFromContext(r)
isAdmin := false
if role.Level == 3 {
isAdmin = true
}
users, err := api.store.Users.GetAll(r.Context(), isAdmin)
if err != nil {
api.internalServerError(w, r, err)
return
}
api.jsonResponse(w, http.StatusOK, users)
}
// 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)
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
}
if api.config.redisCfg.enabled {
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
}
func getRoleFromContext(r *http.Request) *store.Role {
role, _ := r.Context().Value(roleContextKey).(*store.Role)
return role
}