feat: initial commit
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user