[BE-API-013] be-api: Implement track comments endpoints
- Added GET /tracks/:id/comments route (public) - Added POST /tracks/:id/comments route (protected) - Added DELETE /comments/:id route (protected) - Initialized CommentService and CommentHandler in setupTrackRoutes - Standardized API responses in comment handlers - Handlers use RespondSuccess and RespondWithAppError Phase: PHASE-2 Priority: P1 Progress: 22/267 (8.2%)
This commit is contained in:
parent
931b94ca28
commit
f90ddb0b0c
4 changed files with 70 additions and 20 deletions
|
|
@ -1691,7 +1691,18 @@
|
|||
"description": "GET /api/v1/tracks/:id/comments, POST /api/v1/tracks/:id/comments, DELETE /api/v1/comments/:id",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 5,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"completion": {
|
||||
"completed_at": "2025-12-23T09:52:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": [],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/comment_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
"notes": "Added comment routes: GET /tracks/:id/comments (public), POST /tracks/:id/comments (protected), DELETE /comments/:id (protected). Initialized CommentService and CommentHandler in setupTrackRoutes. Standardized API responses in comment handlers to use RespondSuccess and RespondWithAppError. Handlers already existed, only routes and response standardization were needed.",
|
||||
"issues_encountered": []
|
||||
},
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -109,3 +109,4 @@ export default globalSetup;
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -525,6 +525,37 @@ func (r *APIRouter) setupTrackRoutes(router *gin.RouterGroup) {
|
|||
}
|
||||
}
|
||||
|
||||
// BE-API-013: Setup comment routes
|
||||
commentService := services.NewCommentService(r.db.GormDB, r.logger)
|
||||
commentHandler := handlers.NewCommentHandler(commentService, r.logger)
|
||||
|
||||
// Comments routes - public GET, protected POST/DELETE
|
||||
comments := router.Group("/tracks")
|
||||
{
|
||||
// Public: Get comments for a track
|
||||
comments.GET("/:id/comments", commentHandler.GetComments) // BE-API-013: GET /api/v1/tracks/:id/comments
|
||||
|
||||
// Protected: Create and delete comments
|
||||
if r.config.AuthMiddleware != nil {
|
||||
protected := comments.Group("")
|
||||
protected.Use(r.config.AuthMiddleware.RequireAuth())
|
||||
{
|
||||
protected.POST("/:id/comments", commentHandler.CreateComment) // BE-API-013: POST /api/v1/tracks/:id/comments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comments routes - protected DELETE
|
||||
commentsProtected := router.Group("/comments")
|
||||
{
|
||||
if r.config.AuthMiddleware != nil {
|
||||
commentsProtected.Use(r.config.AuthMiddleware.RequireAuth())
|
||||
{
|
||||
commentsProtected.DELETE("/:id", commentHandler.DeleteComment) // BE-API-013: DELETE /api/v1/comments/:id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Internal routes are now set up in setupInternalRoutes() to avoid
|
||||
// path prefix issues when setupTrackRoutes is called with a RouterGroup
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
apperrors "veza-backend-api/internal/errors"
|
||||
"veza-backend-api/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -71,35 +72,36 @@ func (h *CommentHandler) CreateComment(c *gin.Context) {
|
|||
comment, err := h.commentService.CreateComment(c.Request.Context(), trackID, userID, req.Content, 0.0, req.ParentID) // req.ParentID is already *uuid.UUID
|
||||
if err != nil {
|
||||
if errors.Is(err, services.ErrTrackNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
|
||||
RespondWithAppError(c, apperrors.NewNotFoundError("track"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, services.ErrParentCommentNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "parent comment not found"})
|
||||
RespondWithAppError(c, apperrors.NewNotFoundError("parent comment"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, services.ErrParentTrackMismatch) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "parent comment does not belong to the same track"})
|
||||
RespondWithAppError(c, apperrors.NewValidationError("parent comment does not belong to the same track"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
h.commonHandler.logger.Error("failed to create comment", zap.Error(err))
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create comment", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"comment": comment})
|
||||
RespondSuccess(c, http.StatusCreated, comment)
|
||||
}
|
||||
|
||||
// GetComments gère la récupération des commentaires d'un track
|
||||
func (h *CommentHandler) GetComments(c *gin.Context) {
|
||||
trackIDStr := c.Param("id")
|
||||
if trackIDStr == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "track id is required"})
|
||||
RespondWithAppError(c, apperrors.NewValidationError("track id is required"))
|
||||
return
|
||||
}
|
||||
|
||||
trackID, err := uuid.Parse(trackIDStr) // Changed to uuid.Parse
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
|
||||
RespondWithAppError(c, apperrors.NewValidationError("invalid track id"))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -118,15 +120,19 @@ func (h *CommentHandler) GetComments(c *gin.Context) {
|
|||
|
||||
comments, total, err := h.commentService.GetComments(c.Request.Context(), trackID, page, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
h.commonHandler.logger.Error("failed to get comments", zap.Error(err))
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get comments", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
RespondSuccess(c, http.StatusOK, gin.H{
|
||||
"comments": comments,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"pagination": gin.H{
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total_pages": (int(total) + limit - 1) / limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -183,37 +189,38 @@ func (h *CommentHandler) DeleteComment(c *gin.Context) {
|
|||
return // Erreur déjà envoyée par GetUserIDUUID
|
||||
}
|
||||
if userID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
commentIDStr := c.Param("id")
|
||||
if commentIDStr == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "comment id is required"})
|
||||
RespondWithAppError(c, apperrors.NewValidationError("comment id is required"))
|
||||
return
|
||||
}
|
||||
|
||||
commentID, err := uuid.Parse(commentIDStr) // Changed to uuid.Parse
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid comment id"})
|
||||
RespondWithAppError(c, apperrors.NewValidationError("invalid comment id"))
|
||||
return
|
||||
}
|
||||
|
||||
err = h.commentService.DeleteComment(c.Request.Context(), commentID, userID, false) // Added false for isAdmin
|
||||
if err != nil {
|
||||
if errors.Is(err, services.ErrCommentNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "comment not found"})
|
||||
RespondWithAppError(c, apperrors.NewNotFoundError("comment"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, services.ErrForbidden) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "unauthorized: you can only delete your own comments"})
|
||||
RespondWithAppError(c, apperrors.NewForbiddenError("you can only delete your own comments"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
h.commonHandler.logger.Error("failed to delete comment", zap.Error(err))
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to delete comment", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "comment deleted successfully"})
|
||||
RespondSuccess(c, http.StatusOK, gin.H{"message": "Comment deleted successfully"})
|
||||
}
|
||||
|
||||
// GetReplies gère la récupération des réponses d'un commentaire
|
||||
|
|
|
|||
Loading…
Reference in a new issue