Compare commits
8 Commits
5a530b7609
...
e2804ca07e
| Author | SHA1 | Date | |
|---|---|---|---|
| e2804ca07e | |||
| 6cdad79caa | |||
| 6227b64746 | |||
| 506e233347 | |||
| 8c06c916e1 | |||
| 29fcaab25d | |||
| 422ff2473e | |||
| dbe4879457 |
@@ -64,14 +64,13 @@ func NewAuthHandler(authService AuthServiceInterface, userRepo repositories.User
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/login [post]
|
// @Router /api/auth/login [post]
|
||||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.LoginRequest](r)
|
request, ok := GetValidatedDTO[dto.LoginRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := security.SanitizeUsername(req.Username)
|
username := security.SanitizeUsername(request.Username)
|
||||||
password := strings.TrimSpace(req.Password)
|
password := strings.TrimSpace(request.Password)
|
||||||
|
|
||||||
result, err := h.authService.Login(username, password)
|
result, err := h.authService.Login(username, password)
|
||||||
if !HandleServiceError(w, err, "Authentication failed", http.StatusInternalServerError) {
|
if !HandleServiceError(w, err, "Authentication failed", http.StatusInternalServerError) {
|
||||||
@@ -94,15 +93,14 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/register [post]
|
// @Router /api/auth/register [post]
|
||||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.RegisterRequest](r)
|
request, ok := GetValidatedDTO[dto.RegisterRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := strings.TrimSpace(req.Username)
|
username := strings.TrimSpace(request.Username)
|
||||||
email := strings.TrimSpace(req.Email)
|
email := strings.TrimSpace(request.Email)
|
||||||
password := strings.TrimSpace(req.Password)
|
password := strings.TrimSpace(request.Password)
|
||||||
|
|
||||||
username = security.SanitizeUsername(username)
|
username = security.SanitizeUsername(username)
|
||||||
|
|
||||||
@@ -163,13 +161,12 @@ func (h *AuthHandler) ConfirmEmail(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} AuthResponse
|
// @Failure 500 {object} AuthResponse
|
||||||
// @Router /api/auth/resend-verification [post]
|
// @Router /api/auth/resend-verification [post]
|
||||||
func (h *AuthHandler) ResendVerificationEmail(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) ResendVerificationEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.ResendVerificationRequest](r)
|
request, ok := GetValidatedDTO[dto.ResendVerificationRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
email := strings.TrimSpace(req.Email)
|
email := strings.TrimSpace(request.Email)
|
||||||
|
|
||||||
if email == "" {
|
if email == "" {
|
||||||
SendErrorResponse(w, "Email address is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Email address is required", http.StatusBadRequest)
|
||||||
@@ -237,13 +234,12 @@ func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 400 {object} AuthResponse "Invalid request data"
|
// @Failure 400 {object} AuthResponse "Invalid request data"
|
||||||
// @Router /api/auth/forgot-password [post]
|
// @Router /api/auth/forgot-password [post]
|
||||||
func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.ForgotPasswordRequest](r)
|
request, ok := GetValidatedDTO[dto.ForgotPasswordRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameOrEmail := strings.TrimSpace(req.UsernameOrEmail)
|
usernameOrEmail := strings.TrimSpace(request.UsernameOrEmail)
|
||||||
|
|
||||||
if usernameOrEmail == "" {
|
if usernameOrEmail == "" {
|
||||||
SendErrorResponse(w, "Username or email is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Username or email is required", http.StatusBadRequest)
|
||||||
@@ -267,14 +263,13 @@ func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Reques
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/reset-password [post]
|
// @Router /api/auth/reset-password [post]
|
||||||
func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.ResetPasswordRequest](r)
|
request, ok := GetValidatedDTO[dto.ResetPasswordRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := strings.TrimSpace(req.Token)
|
token := strings.TrimSpace(request.Token)
|
||||||
newPassword := strings.TrimSpace(req.NewPassword)
|
newPassword := strings.TrimSpace(request.NewPassword)
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
SendErrorResponse(w, "Token is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Token is required", http.StatusBadRequest)
|
||||||
@@ -316,13 +311,12 @@ func (h *AuthHandler) UpdateEmail(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, ok := GetValidatedDTO[dto.UpdateEmailRequest](r)
|
request, ok := GetValidatedDTO[dto.UpdateEmailRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
email := strings.TrimSpace(req.Email)
|
email := strings.TrimSpace(request.Email)
|
||||||
|
|
||||||
user, err := h.authService.UpdateEmail(userID, email)
|
user, err := h.authService.UpdateEmail(userID, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -362,13 +356,12 @@ func (h *AuthHandler) UpdateUsername(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, ok := GetValidatedDTO[dto.UpdateUsernameRequest](r)
|
request, ok := GetValidatedDTO[dto.UpdateUsernameRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := strings.TrimSpace(req.Username)
|
username := strings.TrimSpace(request.Username)
|
||||||
|
|
||||||
user, err := h.authService.UpdateUsername(userID, username)
|
user, err := h.authService.UpdateUsername(userID, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -403,14 +396,13 @@ func (h *AuthHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, ok := GetValidatedDTO[dto.UpdatePasswordRequest](r)
|
request, ok := GetValidatedDTO[dto.UpdatePasswordRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPassword := strings.TrimSpace(req.CurrentPassword)
|
currentPassword := strings.TrimSpace(request.CurrentPassword)
|
||||||
newPassword := strings.TrimSpace(req.NewPassword)
|
newPassword := strings.TrimSpace(request.NewPassword)
|
||||||
|
|
||||||
user, err := h.authService.UpdatePassword(userID, currentPassword, newPassword)
|
user, err := h.authService.UpdatePassword(userID, currentPassword, newPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -468,20 +460,19 @@ func (h *AuthHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/account/confirm [post]
|
// @Router /api/auth/account/confirm [post]
|
||||||
func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.ConfirmAccountDeletionRequest](r)
|
request, ok := GetValidatedDTO[dto.ConfirmAccountDeletionRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := strings.TrimSpace(req.Token)
|
token := strings.TrimSpace(request.Token)
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
SendErrorResponse(w, "Deletion token is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Deletion token is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.authService.ConfirmAccountDeletionWithPosts(token, req.DeletePosts); err != nil {
|
if err := h.authService.ConfirmAccountDeletionWithPosts(token, request.DeletePosts); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, services.ErrInvalidDeletionToken):
|
case errors.Is(err, services.ErrInvalidDeletionToken):
|
||||||
SendErrorResponse(w, "This deletion link is invalid or has expired.", http.StatusBadRequest)
|
SendErrorResponse(w, "This deletion link is invalid or has expired.", http.StatusBadRequest)
|
||||||
@@ -489,7 +480,7 @@ func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Requ
|
|||||||
SendErrorResponse(w, "Account deletion isn't available right now because email delivery is disabled.", http.StatusServiceUnavailable)
|
SendErrorResponse(w, "Account deletion isn't available right now because email delivery is disabled.", http.StatusServiceUnavailable)
|
||||||
case errors.Is(err, services.ErrDeletionEmailFailed):
|
case errors.Is(err, services.ErrDeletionEmailFailed):
|
||||||
responseDTO := dto.AccountDeletionResponseDTO{
|
responseDTO := dto.AccountDeletionResponseDTO{
|
||||||
PostsDeleted: req.DeletePosts,
|
PostsDeleted: request.DeletePosts,
|
||||||
}
|
}
|
||||||
SendSuccessResponse(w, "Your account has been deleted, but we couldn't send the confirmation email.", responseDTO)
|
SendSuccessResponse(w, "Your account has been deleted, but we couldn't send the confirmation email.", responseDTO)
|
||||||
default:
|
default:
|
||||||
@@ -499,7 +490,7 @@ func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
responseDTO := dto.AccountDeletionResponseDTO{
|
responseDTO := dto.AccountDeletionResponseDTO{
|
||||||
PostsDeleted: req.DeletePosts,
|
PostsDeleted: request.DeletePosts,
|
||||||
}
|
}
|
||||||
SendSuccessResponse(w, "Your account has been deleted.", responseDTO)
|
SendSuccessResponse(w, "Your account has been deleted.", responseDTO)
|
||||||
}
|
}
|
||||||
@@ -530,18 +521,17 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/refresh [post]
|
// @Router /api/auth/refresh [post]
|
||||||
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.RefreshTokenRequest](r)
|
request, ok := GetValidatedDTO[dto.RefreshTokenRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.RefreshToken == "" {
|
if request.RefreshToken == "" {
|
||||||
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.authService.RefreshAccessToken(req.RefreshToken)
|
result, err := h.authService.RefreshAccessToken(request.RefreshToken)
|
||||||
if !HandleServiceError(w, err, "Token refresh failed", http.StatusInternalServerError) {
|
if !HandleServiceError(w, err, "Token refresh failed", http.StatusInternalServerError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -563,18 +553,17 @@ func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} AuthResponse "Internal server error"
|
// @Failure 500 {object} AuthResponse "Internal server error"
|
||||||
// @Router /api/auth/revoke [post]
|
// @Router /api/auth/revoke [post]
|
||||||
func (h *AuthHandler) RevokeToken(w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) RevokeToken(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.RevokeTokenRequest](r)
|
request, ok := GetValidatedDTO[dto.RevokeTokenRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.RefreshToken == "" {
|
if request.RefreshToken == "" {
|
||||||
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.authService.RevokeRefreshToken(req.RefreshToken)
|
err := h.authService.RevokeRefreshToken(request.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SendErrorResponse(w, "Failed to revoke token", http.StatusInternalServerError)
|
SendErrorResponse(w, "Failed to revoke token", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -693,7 +693,7 @@ func TestAuthHandlerUpdateEmail(t *testing.T) {
|
|||||||
userID: 1,
|
userID: 1,
|
||||||
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Invalid request",
|
expectedError: "Invalid JSON",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty email",
|
name: "empty email",
|
||||||
@@ -876,7 +876,7 @@ func TestAuthHandlerUpdatePassword(t *testing.T) {
|
|||||||
userID: 1,
|
userID: 1,
|
||||||
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Current password is required",
|
expectedError: "CurrentPassword is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty new password",
|
name: "empty new password",
|
||||||
@@ -884,7 +884,7 @@ func TestAuthHandlerUpdatePassword(t *testing.T) {
|
|||||||
userID: 1,
|
userID: 1,
|
||||||
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Password is required",
|
expectedError: "NewPassword is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "short new password",
|
name: "short new password",
|
||||||
@@ -892,7 +892,7 @@ func TestAuthHandlerUpdatePassword(t *testing.T) {
|
|||||||
userID: 1,
|
userID: 1,
|
||||||
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
mockSetup: func(repo *testutils.UserRepositoryStub) {},
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Password must be at least 8 characters long",
|
expectedError: "NewPassword must be at least 8 characters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "incorrect current password",
|
name: "incorrect current password",
|
||||||
@@ -1042,13 +1042,13 @@ func TestAuthHandlerResendVerificationEmail(t *testing.T) {
|
|||||||
name: "invalid json",
|
name: "invalid json",
|
||||||
body: "not-json",
|
body: "not-json",
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Invalid request",
|
expectedError: "Invalid JSON",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing email",
|
name: "missing email",
|
||||||
body: `{}`,
|
body: `{}`,
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Email address is required",
|
expectedError: "Email is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "account not found",
|
name: "account not found",
|
||||||
@@ -1167,13 +1167,13 @@ func TestAuthHandlerConfirmAccountDeletion(t *testing.T) {
|
|||||||
name: "invalid json",
|
name: "invalid json",
|
||||||
body: "not-json",
|
body: "not-json",
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Invalid request",
|
expectedError: "Invalid JSON",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing token",
|
name: "missing token",
|
||||||
body: `{}`,
|
body: `{}`,
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Deletion token is required",
|
expectedError: "Token is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid token from service",
|
name: "invalid token from service",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"goyco/internal/dto"
|
"goyco/internal/dto"
|
||||||
"goyco/internal/middleware"
|
"goyco/internal/middleware"
|
||||||
"goyco/internal/services"
|
"goyco/internal/services"
|
||||||
|
"goyco/internal/validation"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -272,13 +273,51 @@ func HandleServiceError(w http.ResponseWriter, err error, defaultMsg string, def
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetValidatedDTO[T any](r *http.Request) (*T, bool) {
|
func GetValidatedDTO[T any](w http.ResponseWriter, r *http.Request) (*T, bool) {
|
||||||
dtoVal := middleware.GetValidatedDTOFromContext(r.Context())
|
dtoVal := middleware.GetValidatedDTOFromContext(r.Context())
|
||||||
if dtoVal == nil {
|
dtoTypeInContext := middleware.GetDTOTypeFromContext(r.Context())
|
||||||
|
|
||||||
|
var dto *T
|
||||||
|
needsValidation := false
|
||||||
|
|
||||||
|
if dtoVal != nil {
|
||||||
|
var ok bool
|
||||||
|
dto, ok = dtoVal.(*T)
|
||||||
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
dto, ok := dtoVal.(*T)
|
if dtoTypeInContext == nil {
|
||||||
return dto, ok
|
needsValidation = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var decoded T
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&decoded); err != nil {
|
||||||
|
SendErrorResponse(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
dto = &decoded
|
||||||
|
needsValidation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsValidation {
|
||||||
|
if err := validation.ValidateStruct(dto); err != nil {
|
||||||
|
var errorMessages []string
|
||||||
|
if structErr, ok := err.(*validation.StructValidationError); ok {
|
||||||
|
errorMessages = make([]string, len(structErr.Errors))
|
||||||
|
for i, fieldError := range structErr.Errors {
|
||||||
|
errorMessages[i] = fieldError.Message
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessages = []string{err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMsg := strings.Join(errorMessages, "; ")
|
||||||
|
SendErrorResponse(w, errorMsg, http.StatusBadRequest)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithValidation[T any](validationMiddleware func(http.Handler) http.Handler, handler http.HandlerFunc) http.HandlerFunc {
|
func WithValidation[T any](validationMiddleware func(http.Handler) http.Handler, handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
|||||||
@@ -109,9 +109,8 @@ func (h *PostHandler) GetPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} PostResponse "Internal server error"
|
// @Failure 500 {object} PostResponse "Internal server error"
|
||||||
// @Router /api/posts [post]
|
// @Router /api/posts [post]
|
||||||
func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
|
func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.CreatePostRequest](r)
|
request, ok := GetValidatedDTO[dto.CreatePostRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +119,9 @@ func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title := security.SanitizeInput(req.Title)
|
title := security.SanitizeInput(request.Title)
|
||||||
url := security.SanitizeURL(req.URL)
|
url := security.SanitizeURL(request.URL)
|
||||||
content := security.SanitizePostContent(req.Content)
|
content := security.SanitizePostContent(request.Content)
|
||||||
|
|
||||||
if url == "" {
|
if url == "" {
|
||||||
SendErrorResponse(w, "Invalid URL", http.StatusBadRequest)
|
SendErrorResponse(w, "Invalid URL", http.StatusBadRequest)
|
||||||
@@ -263,14 +262,13 @@ func (h *PostHandler) UpdatePost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, ok := GetValidatedDTO[dto.UpdatePostRequest](r)
|
request, ok := GetValidatedDTO[dto.UpdatePostRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title := security.SanitizeInput(req.Title)
|
title := security.SanitizeInput(request.Title)
|
||||||
content := security.SanitizePostContent(req.Content)
|
content := security.SanitizePostContent(request.Content)
|
||||||
|
|
||||||
post.Title = title
|
post.Title = title
|
||||||
post.Content = content
|
post.Content = content
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ func TestPostHandlerCreatePostSuccess(t *testing.T) {
|
|||||||
|
|
||||||
handler := NewPostHandler(repo, fetcher, nil)
|
handler := NewPostHandler(repo, fetcher, nil)
|
||||||
|
|
||||||
request := createCreatePostRequest(`{"title":" ","url":"https://example.com","content":"Go"}`)
|
request := createCreatePostRequest(`{"title":"","url":"https://example.com","content":"Go"}`)
|
||||||
ctx := context.WithValue(request.Context(), middleware.UserIDKey, uint(42))
|
ctx := context.WithValue(request.Context(), middleware.UserIDKey, uint(42))
|
||||||
request = request.WithContext(ctx)
|
request = request.WithContext(ctx)
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ func TestPostHandlerCreatePostValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recorder = httptest.NewRecorder()
|
recorder = httptest.NewRecorder()
|
||||||
request = createCreatePostRequest(`{"title":"ok","url":"https://example.com"}`)
|
request = createCreatePostRequest(`{"title":"okay","url":"https://example.com"}`)
|
||||||
handler.CreatePost(recorder, request)
|
handler.CreatePost(recorder, request)
|
||||||
testutils.AssertHTTPStatus(t, recorder, http.StatusUnauthorized)
|
testutils.AssertHTTPStatus(t, recorder, http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ func TestPostHandlerCreatePostTitleFetcherErrors(t *testing.T) {
|
|||||||
return "", tc.err
|
return "", tc.err
|
||||||
}}
|
}}
|
||||||
handler := NewPostHandler(repo, fetcher, nil)
|
handler := NewPostHandler(repo, fetcher, nil)
|
||||||
request := createCreatePostRequest(`{"title":" ","url":"https://example.com"}`)
|
request := createCreatePostRequest(`{"title":"","url":"https://example.com"}`)
|
||||||
request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
|
request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|||||||
@@ -93,13 +93,12 @@ func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Failure 500 {object} UserResponse "Internal server error"
|
// @Failure 500 {object} UserResponse "Internal server error"
|
||||||
// @Router /api/users [post]
|
// @Router /api/users [post]
|
||||||
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
req, ok := GetValidatedDTO[dto.RegisterRequest](r)
|
request, ok := GetValidatedDTO[dto.RegisterRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.authService.Register(req.Username, req.Email, req.Password)
|
result, err := h.authService.Register(request.Username, request.Email, request.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var validationErr *validation.ValidationError
|
var validationErr *validation.ValidationError
|
||||||
if errors.As(err, &validationErr) {
|
if errors.As(err, &validationErr) {
|
||||||
|
|||||||
@@ -78,14 +78,13 @@ func (h *VoteHandler) CastVote(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, ok := GetValidatedDTO[dto.CastVoteRequest](r)
|
request, ok := GetValidatedDTO[dto.CastVoteRequest](w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var voteType database.VoteType
|
var voteType database.VoteType
|
||||||
switch req.Type {
|
switch request.Type {
|
||||||
case "up":
|
case "up":
|
||||||
voteType = database.VoteUp
|
voteType = database.VoteUp
|
||||||
case "down":
|
case "down":
|
||||||
|
|||||||
Reference in New Issue
Block a user