Compare commits
3 Commits
738243d945
...
0e71b28615
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e71b28615 | |||
| cd740da57a | |||
| abe4a3dc88 |
@@ -84,23 +84,15 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.LoginRequest](r)
|
||||||
Username string `json:"username"`
|
if !ok {
|
||||||
Password string `json:"password"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := security.SanitizeUsername(req.Username)
|
username := security.SanitizeUsername(req.Username)
|
||||||
password := strings.TrimSpace(req.Password)
|
password := strings.TrimSpace(req.Password)
|
||||||
|
|
||||||
if username == "" || password == "" {
|
|
||||||
SendErrorResponse(w, "Username and password are required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validation.ValidatePassword(password); err != nil {
|
if err := validation.ValidatePassword(password); err != nil {
|
||||||
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -126,13 +118,9 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.RegisterRequest](r)
|
||||||
Username string `json:"username"`
|
if !ok {
|
||||||
Email string `json:"email"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +128,6 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
|||||||
email := strings.TrimSpace(req.Email)
|
email := strings.TrimSpace(req.Email)
|
||||||
password := strings.TrimSpace(req.Password)
|
password := strings.TrimSpace(req.Password)
|
||||||
|
|
||||||
if username == "" || email == "" || password == "" {
|
|
||||||
SendErrorResponse(w, "Username, email, and password are required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
username = security.SanitizeUsername(username)
|
username = security.SanitizeUsername(username)
|
||||||
if err := validation.ValidateUsername(username); err != nil {
|
if err := validation.ValidateUsername(username); err != nil {
|
||||||
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
@@ -234,19 +217,13 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.ResendVerificationRequest](r)
|
||||||
Email string `json:"email"`
|
if !ok {
|
||||||
}
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
email := strings.TrimSpace(req.Email)
|
email := strings.TrimSpace(req.Email)
|
||||||
if email == "" {
|
|
||||||
SendErrorResponse(w, "Email address is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.authService.ResendVerificationEmail(email)
|
err := h.authService.ResendVerificationEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -308,19 +285,13 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.ForgotPasswordRequest](r)
|
||||||
UsernameOrEmail string `json:"username_or_email"`
|
if !ok {
|
||||||
}
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameOrEmail := strings.TrimSpace(req.UsernameOrEmail)
|
usernameOrEmail := strings.TrimSpace(req.UsernameOrEmail)
|
||||||
if usernameOrEmail == "" {
|
|
||||||
SendErrorResponse(w, "Username or email is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.authService.RequestPasswordReset(usernameOrEmail); err != nil {
|
if err := h.authService.RequestPasswordReset(usernameOrEmail); err != nil {
|
||||||
}
|
}
|
||||||
@@ -339,30 +310,17 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.ResetPasswordRequest](r)
|
||||||
Token string `json:"token"`
|
if !ok {
|
||||||
NewPassword string `json:"new_password"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := strings.TrimSpace(req.Token)
|
token := strings.TrimSpace(req.Token)
|
||||||
newPassword := strings.TrimSpace(req.NewPassword)
|
newPassword := strings.TrimSpace(req.NewPassword)
|
||||||
|
|
||||||
if token == "" {
|
if err := validation.ValidatePassword(newPassword); err != nil {
|
||||||
SendErrorResponse(w, "Reset token is required", http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPassword == "" {
|
|
||||||
SendErrorResponse(w, "New password is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(newPassword) < 8 {
|
|
||||||
SendErrorResponse(w, "Password must be at least 8 characters long", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,11 +359,9 @@ func (h *AuthHandler) UpdateEmail(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.UpdateEmailRequest](r)
|
||||||
Email string `json:"email"`
|
if !ok {
|
||||||
}
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,11 +411,9 @@ func (h *AuthHandler) UpdateUsername(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.UpdateUsernameRequest](r)
|
||||||
Username string `json:"username"`
|
if !ok {
|
||||||
}
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,23 +458,15 @@ func (h *AuthHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.UpdatePasswordRequest](r)
|
||||||
CurrentPassword string `json:"current_password"`
|
if !ok {
|
||||||
NewPassword string `json:"new_password"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPassword := strings.TrimSpace(req.CurrentPassword)
|
currentPassword := strings.TrimSpace(req.CurrentPassword)
|
||||||
newPassword := strings.TrimSpace(req.NewPassword)
|
newPassword := strings.TrimSpace(req.NewPassword)
|
||||||
|
|
||||||
if currentPassword == "" {
|
|
||||||
SendErrorResponse(w, "Current password is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validation.ValidatePassword(newPassword); err != nil {
|
if err := validation.ValidatePassword(newPassword); err != nil {
|
||||||
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -584,20 +530,13 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.ConfirmAccountDeletionRequest](r)
|
||||||
Token string `json:"token"`
|
if !ok {
|
||||||
DeletePosts bool `json:"delete_posts"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := strings.TrimSpace(req.Token)
|
token := strings.TrimSpace(req.Token)
|
||||||
if token == "" {
|
|
||||||
SendErrorResponse(w, "Deletion token is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.authService.ConfirmAccountDeletionWithPosts(token, req.DeletePosts); err != nil {
|
if err := h.authService.ConfirmAccountDeletionWithPosts(token, req.DeletePosts); err != nil {
|
||||||
switch {
|
switch {
|
||||||
@@ -646,14 +585,9 @@ 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) {
|
||||||
var req dto.RefreshTokenRequest
|
req, ok := GetValidatedDTO[dto.RefreshTokenRequest](r)
|
||||||
|
if !ok {
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(req.RefreshToken) == "" {
|
|
||||||
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,14 +612,9 @@ 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) {
|
||||||
var req dto.RevokeTokenRequest
|
req, ok := GetValidatedDTO[dto.RevokeTokenRequest](r)
|
||||||
|
if !ok {
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(req.RefreshToken) == "" {
|
|
||||||
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,28 +655,28 @@ func (h *AuthHandler) RevokeAllTokens(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
|
func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
|
||||||
if config.GeneralRateLimit != nil {
|
if config.GeneralRateLimit != nil {
|
||||||
rateLimited := config.GeneralRateLimit(r)
|
rateLimited := config.GeneralRateLimit(r)
|
||||||
rateLimited.Post("/auth/refresh", h.RefreshToken)
|
rateLimited.Post("/auth/refresh", WithValidation[dto.RefreshTokenRequest](config.ValidationMiddleware, h.RefreshToken))
|
||||||
rateLimited.Get("/auth/confirm", h.ConfirmEmail)
|
rateLimited.Get("/auth/confirm", h.ConfirmEmail)
|
||||||
rateLimited.Post("/auth/resend-verification", h.ResendVerificationEmail)
|
rateLimited.Post("/auth/resend-verification", WithValidation[dto.ResendVerificationRequest](config.ValidationMiddleware, h.ResendVerificationEmail))
|
||||||
} else {
|
} else {
|
||||||
r.Post("/auth/refresh", h.RefreshToken)
|
r.Post("/auth/refresh", WithValidation[dto.RefreshTokenRequest](config.ValidationMiddleware, h.RefreshToken))
|
||||||
r.Get("/auth/confirm", h.ConfirmEmail)
|
r.Get("/auth/confirm", h.ConfirmEmail)
|
||||||
r.Post("/auth/resend-verification", h.ResendVerificationEmail)
|
r.Post("/auth/resend-verification", WithValidation[dto.ResendVerificationRequest](config.ValidationMiddleware, h.ResendVerificationEmail))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AuthRateLimit != nil {
|
if config.AuthRateLimit != nil {
|
||||||
rateLimited := config.AuthRateLimit(r)
|
rateLimited := config.AuthRateLimit(r)
|
||||||
rateLimited.Post("/auth/register", h.Register)
|
rateLimited.Post("/auth/register", WithValidation[dto.RegisterRequest](config.ValidationMiddleware, h.Register))
|
||||||
rateLimited.Post("/auth/login", h.Login)
|
rateLimited.Post("/auth/login", WithValidation[dto.LoginRequest](config.ValidationMiddleware, h.Login))
|
||||||
rateLimited.Post("/auth/forgot-password", h.RequestPasswordReset)
|
rateLimited.Post("/auth/forgot-password", WithValidation[dto.ForgotPasswordRequest](config.ValidationMiddleware, h.RequestPasswordReset))
|
||||||
rateLimited.Post("/auth/reset-password", h.ResetPassword)
|
rateLimited.Post("/auth/reset-password", WithValidation[dto.ResetPasswordRequest](config.ValidationMiddleware, h.ResetPassword))
|
||||||
rateLimited.Post("/auth/account/confirm", h.ConfirmAccountDeletion)
|
rateLimited.Post("/auth/account/confirm", WithValidation[dto.ConfirmAccountDeletionRequest](config.ValidationMiddleware, h.ConfirmAccountDeletion))
|
||||||
} else {
|
} else {
|
||||||
r.Post("/auth/register", h.Register)
|
r.Post("/auth/register", WithValidation[dto.RegisterRequest](config.ValidationMiddleware, h.Register))
|
||||||
r.Post("/auth/login", h.Login)
|
r.Post("/auth/login", WithValidation[dto.LoginRequest](config.ValidationMiddleware, h.Login))
|
||||||
r.Post("/auth/forgot-password", h.RequestPasswordReset)
|
r.Post("/auth/forgot-password", WithValidation[dto.ForgotPasswordRequest](config.ValidationMiddleware, h.RequestPasswordReset))
|
||||||
r.Post("/auth/reset-password", h.ResetPassword)
|
r.Post("/auth/reset-password", WithValidation[dto.ResetPasswordRequest](config.ValidationMiddleware, h.ResetPassword))
|
||||||
r.Post("/auth/account/confirm", h.ConfirmAccountDeletion)
|
r.Post("/auth/account/confirm", WithValidation[dto.ConfirmAccountDeletionRequest](config.ValidationMiddleware, h.ConfirmAccountDeletion))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected := r
|
protected := r
|
||||||
@@ -760,10 +689,10 @@ func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
|
|||||||
|
|
||||||
protected.Get("/auth/me", h.Me)
|
protected.Get("/auth/me", h.Me)
|
||||||
protected.Post("/auth/logout", h.Logout)
|
protected.Post("/auth/logout", h.Logout)
|
||||||
protected.Post("/auth/revoke", h.RevokeToken)
|
protected.Post("/auth/revoke", WithValidation[dto.RevokeTokenRequest](config.ValidationMiddleware, h.RevokeToken))
|
||||||
protected.Post("/auth/revoke-all", h.RevokeAllTokens)
|
protected.Post("/auth/revoke-all", h.RevokeAllTokens)
|
||||||
protected.Put("/auth/email", h.UpdateEmail)
|
protected.Put("/auth/email", WithValidation[dto.UpdateEmailRequest](config.ValidationMiddleware, h.UpdateEmail))
|
||||||
protected.Put("/auth/username", h.UpdateUsername)
|
protected.Put("/auth/username", WithValidation[dto.UpdateUsernameRequest](config.ValidationMiddleware, h.UpdateUsername))
|
||||||
protected.Put("/auth/password", h.UpdatePassword)
|
protected.Put("/auth/password", WithValidation[dto.UpdatePasswordRequest](config.ValidationMiddleware, h.UpdatePassword))
|
||||||
protected.Delete("/auth/account", h.DeleteAccount)
|
protected.Delete("/auth/account", h.DeleteAccount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,32 +115,9 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.CreatePostRequest](r)
|
||||||
Title string `json:"title"`
|
if !ok {
|
||||||
URL string `json:"url"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
Content string `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Title = security.SanitizeInput(req.Title)
|
|
||||||
req.URL = security.SanitizeURL(req.URL)
|
|
||||||
req.Content = security.SanitizePostContent(req.Content)
|
|
||||||
|
|
||||||
if req.URL == "" {
|
|
||||||
SendErrorResponse(w, "URL is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Title) > 200 {
|
|
||||||
SendErrorResponse(w, "Title must be no more than 200 characters", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Content) > 10000 {
|
|
||||||
SendErrorResponse(w, "Content must be no more than 10,000 characters", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +126,15 @@ func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title := req.Title
|
title := security.SanitizeInput(req.Title)
|
||||||
|
url := security.SanitizeURL(req.URL)
|
||||||
|
content := security.SanitizePostContent(req.Content)
|
||||||
|
|
||||||
if title == "" && h.titleFetcher != nil {
|
if title == "" && h.titleFetcher != nil {
|
||||||
titleCtx, cancel := context.WithTimeout(r.Context(), 7*time.Second)
|
titleCtx, cancel := context.WithTimeout(r.Context(), 7*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
fetchedTitle, err := h.titleFetcher.FetchTitle(titleCtx, req.URL)
|
fetchedTitle, err := h.titleFetcher.FetchTitle(titleCtx, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, services.ErrUnsupportedScheme):
|
case errors.Is(err, services.ErrUnsupportedScheme):
|
||||||
@@ -183,8 +162,8 @@ func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
post := &database.Post{
|
post := &database.Post{
|
||||||
Title: title,
|
Title: title,
|
||||||
URL: req.URL,
|
URL: url,
|
||||||
Content: req.Content,
|
Content: content,
|
||||||
AuthorID: &userID,
|
AuthorID: &userID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,40 +260,27 @@ func (h *PostHandler) UpdatePost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.UpdatePostRequest](r)
|
||||||
Title string `json:"title"`
|
if !ok {
|
||||||
Content string `json:"content"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Title = security.SanitizeInput(req.Title)
|
title := security.SanitizeInput(req.Title)
|
||||||
req.Content = security.SanitizePostContent(req.Content)
|
content := security.SanitizePostContent(req.Content)
|
||||||
|
|
||||||
if len(req.Title) > 200 {
|
if err := validation.ValidateTitle(title); err != nil {
|
||||||
SendErrorResponse(w, "Title must be no more than 200 characters", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.Content) > 10000 {
|
|
||||||
SendErrorResponse(w, "Content must be no more than 10,000 characters", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validation.ValidateTitle(req.Title); err != nil {
|
|
||||||
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validation.ValidateContent(req.Content); err != nil {
|
if err := validation.ValidateContent(content); err != nil {
|
||||||
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
SendErrorResponse(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
post.Title = req.Title
|
post.Title = title
|
||||||
post.Content = req.Content
|
post.Content = content
|
||||||
|
|
||||||
if err := h.postRepo.Update(post); err != nil {
|
if err := h.postRepo.Update(post); err != nil {
|
||||||
SendErrorResponse(w, "Failed to update post", http.StatusInternalServerError)
|
SendErrorResponse(w, "Failed to update post", http.StatusInternalServerError)
|
||||||
@@ -453,7 +419,7 @@ func (h *PostHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
|
|||||||
if config.GeneralRateLimit != nil {
|
if config.GeneralRateLimit != nil {
|
||||||
protected = config.GeneralRateLimit(protected)
|
protected = config.GeneralRateLimit(protected)
|
||||||
}
|
}
|
||||||
protected.Post("/posts", h.CreatePost)
|
protected.Post("/posts", WithValidation[dto.CreatePostRequest](config.ValidationMiddleware, h.CreatePost))
|
||||||
protected.Put("/posts/{id}", h.UpdatePost)
|
protected.Put("/posts/{id}", WithValidation[dto.UpdatePostRequest](config.ValidationMiddleware, h.UpdatePost))
|
||||||
protected.Delete("/posts/{id}", h.DeletePost)
|
protected.Delete("/posts/{id}", h.DeletePost)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,13 +99,9 @@ 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) {
|
||||||
var req struct {
|
req, ok := GetValidatedDTO[dto.RegisterRequest](r)
|
||||||
Username string `json:"username"`
|
if !ok {
|
||||||
Email string `json:"email"`
|
SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if !DecodeJSONRequest(w, r, &req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +185,7 @@ func (h *UserHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected.Get("/users", h.GetUsers)
|
protected.Get("/users", h.GetUsers)
|
||||||
protected.Post("/users", h.CreateUser)
|
protected.Post("/users", WithValidation[dto.RegisterRequest](config.ValidationMiddleware, h.CreateUser))
|
||||||
protected.Get("/users/{id}", h.GetUser)
|
protected.Get("/users/{id}", h.GetUser)
|
||||||
protected.Get("/users/{id}/posts", h.GetUserPosts)
|
protected.Get("/users/{id}/posts", h.GetUserPosts)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user