[BE-API-018] be-api: Implement user block/unblock endpoints
- Added BlockUser and UnblockUser methods to SocialService - Added BlockUser and UnblockUser handlers in ProfileHandler - Added routes: POST /users/:id/block and DELETE /users/:id/block - Handlers use existing SocialService methods - Includes validation to prevent users from blocking themselves - Added IsBlocked helper method to check block status - Handlers use standard API response format Phase: PHASE-2 Priority: P2 Progress: 27/267 (10.1%)
This commit is contained in:
parent
d869aa987a
commit
78862c6ee1
4 changed files with 185 additions and 1 deletions
|
|
@ -1908,7 +1908,19 @@
|
|||
"description": "POST /api/v1/users/:id/block, DELETE /api/v1/users/:id/block",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 3,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"completion": {
|
||||
"completed_at": "2025-12-23T09:58:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": [],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/social_service.go",
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
"notes": "Added BlockUser and UnblockUser methods to SocialService. Added BlockUser and UnblockUser handlers in ProfileHandler. Added routes: POST /users/:id/block and DELETE /users/:id/block (protected). Handlers use existing SocialService methods. Includes validation to prevent users from blocking themselves. Added IsBlocked helper method to check block status. Handlers use standard API response format (RespondSuccess, RespondWithAppError).",
|
||||
"issues_encountered": []
|
||||
},
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -394,6 +394,10 @@ func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) {
|
|||
protected.POST("/:id/follow", profileHandler.FollowUser) // Follow user endpoint
|
||||
protected.DELETE("/:id/follow", profileHandler.UnfollowUser) // Unfollow user endpoint
|
||||
|
||||
// BE-API-018: User block/unblock endpoints
|
||||
protected.POST("/:id/block", profileHandler.BlockUser) // Block user endpoint
|
||||
protected.DELETE("/:id/block", profileHandler.UnblockUser) // Unblock user endpoint
|
||||
|
||||
// BE-API-007: User role assignment routes
|
||||
roleService := services.NewRoleService(r.db.GormDB)
|
||||
roleHandler := handlers.NewRoleHandler(roleService, r.logger)
|
||||
|
|
|
|||
|
|
@ -299,6 +299,100 @@ func (h *ProfileHandler) UnfollowUser(c *gin.Context) {
|
|||
RespondSuccess(c, http.StatusOK, gin.H{"message": "User unfollowed successfully"})
|
||||
}
|
||||
|
||||
// BlockUser gère le blocage d'un utilisateur
|
||||
// POST /api/v1/users/:id/block
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
func (h *ProfileHandler) BlockUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à bloquer depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
if err != nil {
|
||||
RespondWithAppError(c, apperrors.NewValidationError("invalid user id"))
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'ID de l'utilisateur authentifié
|
||||
blockerID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
return // Erreur déjà envoyée par GetUserIDUUID
|
||||
}
|
||||
|
||||
// Vérifier qu'on ne peut pas se bloquer soi-même
|
||||
if blockerID == userID {
|
||||
RespondWithAppError(c, apperrors.NewValidationError("cannot block yourself"))
|
||||
return
|
||||
}
|
||||
|
||||
// Vérifier que le service social est initialisé
|
||||
if h.socialService == nil {
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Social service not initialized", nil))
|
||||
return
|
||||
}
|
||||
|
||||
// Bloquer l'utilisateur
|
||||
err = h.socialService.BlockUser(blockerID, userID)
|
||||
if err != nil {
|
||||
if err.Error() == "cannot block yourself" {
|
||||
RespondWithAppError(c, apperrors.NewValidationError("cannot block yourself"))
|
||||
return
|
||||
}
|
||||
h.logger.Error("failed to block user",
|
||||
zap.Error(err),
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", userID.String()))
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to block user", err))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("user blocked",
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", userID.String()))
|
||||
|
||||
RespondSuccess(c, http.StatusOK, gin.H{"message": "User blocked successfully"})
|
||||
}
|
||||
|
||||
// UnblockUser gère le déblocage d'un utilisateur
|
||||
// DELETE /api/v1/users/:id/block
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
func (h *ProfileHandler) UnblockUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à débloquer depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
if err != nil {
|
||||
RespondWithAppError(c, apperrors.NewValidationError("invalid user id"))
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'ID de l'utilisateur authentifié
|
||||
blockerID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
return // Erreur déjà envoyée par GetUserIDUUID
|
||||
}
|
||||
|
||||
// Vérifier que le service social est initialisé
|
||||
if h.socialService == nil {
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Social service not initialized", nil))
|
||||
return
|
||||
}
|
||||
|
||||
// Débloquer l'utilisateur
|
||||
err = h.socialService.UnblockUser(blockerID, userID)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to unblock user",
|
||||
zap.Error(err),
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", userID.String()))
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to unblock user", err))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("user unblocked",
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", userID.String()))
|
||||
|
||||
RespondSuccess(c, http.StatusOK, gin.H{"message": "User unblocked successfully"})
|
||||
}
|
||||
|
||||
// UpdateProfileRequest represents the request body for updating a user profile
|
||||
type UpdateProfileRequest struct {
|
||||
FirstName string `json:"first_name" binding:"omitempty,max=100" validate:"omitempty,max=100"`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"veza-backend-api/internal/database"
|
||||
|
|
@ -242,3 +243,76 @@ func (ss *SocialService) IsTrackLiked(userID, trackID uuid.UUID) (bool, error) {
|
|||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// BlockUser creates a block relationship between users
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
func (ss *SocialService) BlockUser(blockerID, blockedID uuid.UUID) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Vérifier qu'on ne peut pas se bloquer soi-même
|
||||
if blockerID == blockedID {
|
||||
return fmt.Errorf("cannot block yourself")
|
||||
}
|
||||
|
||||
_, err := ss.db.ExecContext(ctx, `
|
||||
INSERT INTO user_blocks (blocker_id, blocked_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (blocker_id, blocked_id) DO NOTHING
|
||||
`, blockerID, blockedID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to block user: %w", err)
|
||||
}
|
||||
|
||||
ss.logger.Info("User blocked",
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", blockedID.String()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnblockUser removes a block relationship between users
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
func (ss *SocialService) UnblockUser(blockerID, blockedID uuid.UUID) error {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := ss.db.ExecContext(ctx, `
|
||||
DELETE FROM user_blocks
|
||||
WHERE blocker_id = $1 AND blocked_id = $2
|
||||
`, blockerID, blockedID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unblock user: %w", err)
|
||||
}
|
||||
|
||||
ss.logger.Info("User unblocked",
|
||||
zap.String("blocker_id", blockerID.String()),
|
||||
zap.String("blocked_id", blockedID.String()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBlocked checks if a user is blocked by another user
|
||||
// BE-API-018: Helper method to check block status
|
||||
func (ss *SocialService) IsBlocked(blockerID, blockedID uuid.UUID) (bool, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var exists bool
|
||||
err := ss.db.QueryRowContext(ctx, `
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM user_blocks
|
||||
WHERE blocker_id = $1 AND blocked_id = $2
|
||||
)
|
||||
`, blockerID, blockedID).Scan(&exists)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to check block status: %w", err)
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue