veza/veza-backend-api/internal/handlers/room_handler.go
senke a1000ce7fb style(backend): gofmt -w on 85 files (whitespace only)
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.

The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
2026-04-14 12:22:14 +02:00

673 lines
23 KiB
Go

package handlers
import (
"context"
"errors"
"net/http"
"strconv"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// RoomServiceInterface defines the interface for room service operations
type RoomServiceInterface interface {
CreateRoom(ctx context.Context, userID uuid.UUID, req services.CreateRoomRequest) (*services.RoomResponse, error)
GetUserRooms(ctx context.Context, userID uuid.UUID) ([]*services.RoomResponse, error)
GetRoom(ctx context.Context, roomID uuid.UUID) (*services.RoomResponse, error)
IsRoomMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) (bool, error)
UpdateRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID, req services.UpdateRoomRequest) (*services.RoomResponse, error) // BE-API-012: Update room method
AddMember(ctx context.Context, roomID, userID uuid.UUID) error
RemoveMember(ctx context.Context, roomID, userID uuid.UUID) error // BE-API-011: Remove member method
GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error)
GetRoomHistoryWithCursor(ctx context.Context, roomID uuid.UUID, limit int, cursor string) (*services.RoomHistoryWithCursorResult, error) // v0.931: cursor pagination
DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error // BE-API-010: Delete room method
GetRoomMembers(ctx context.Context, roomID uuid.UUID, requestUserID uuid.UUID) (*services.RoomMembersResponse, error) // v0.9.6 @mention, v0.9.7 roles
CreateInvitation(ctx context.Context, roomID uuid.UUID, inviterID uuid.UUID) (*services.RoomInvitationResponse, error) // v0.9.7
JoinByToken(ctx context.Context, token uuid.UUID, userID uuid.UUID) (uuid.UUID, error) // v0.9.7
KickMember(ctx context.Context, roomID uuid.UUID, targetUserID uuid.UUID, requestUserID uuid.UUID) error // v0.9.7
UpdateMemberRole(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID, newRole string) error // v0.9.7
}
// RoomHandler gère les opérations sur les rooms (conversations)
type RoomHandler struct {
roomService RoomServiceInterface
logger *zap.Logger
commonHandler *CommonHandler
}
// NewRoomHandler crée une nouvelle instance de RoomHandler
func NewRoomHandler(roomService RoomServiceInterface, logger *zap.Logger) *RoomHandler {
return &RoomHandler{
roomService: roomService,
logger: logger,
commonHandler: NewCommonHandler(logger),
}
}
// CreateRoom gère la création d'une nouvelle room
// POST /api/v1/conversations
func (h *RoomHandler) CreateRoom(c *gin.Context) {
// Récupérer l'ID utilisateur du contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
return
}
// Convertir userID en uuid.UUID
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type in context"))
return
}
// Parser la requête
var req services.CreateRoomRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// Valider le type de room si non spécifié
if req.Type == "" {
req.Type = "public"
}
// Créer la room
room, err := h.roomService.CreateRoom(c.Request.Context(), userID, req)
if err != nil {
h.logger.Error("failed to create room",
zap.Error(err),
zap.String("user_id", userID.String()),
zap.String("room_name", req.Name))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create conversation", err))
return
}
h.logger.Info("room created successfully",
zap.String("room_id", room.ID.String()),
zap.String("user_id", userID.String()),
zap.String("room_name", req.Name))
RespondSuccess(c, http.StatusCreated, room)
}
// GetUserRooms récupère toutes les rooms d'un utilisateur
// GET /api/v1/conversations
func (h *RoomHandler) GetUserRooms(c *gin.Context) {
// Récupérer l'ID utilisateur du contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
return
}
// Convertir userID en uuid.UUID
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type in context"))
return
}
// Récupérer les rooms
rooms, err := h.roomService.GetUserRooms(c.Request.Context(), userID)
if err != nil {
h.logger.Error("failed to get user rooms",
zap.Error(err),
zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch conversations", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{
"conversations": rooms,
"total": len(rooms),
})
}
// GetRoom récupère une room par son ID
// GET /api/v1/conversations/:id
// SECURITY(CRIT-001): Verify membership before returning room data
func (h *RoomHandler) GetRoom(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
return
}
// SECURITY(CRIT-001): Verify the requesting user is a member of this room
userID, ok := GetUserIDUUID(c)
if !ok {
RespondWithAppError(c, apperrors.NewUnauthorizedError("authentication required"))
return
}
isMember, err := h.roomService.IsRoomMember(c.Request.Context(), roomID, userID)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to check room membership",
zap.Error(err),
zap.String("room_id", roomID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation", err))
return
}
if !isMember {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
// Récupérer la room
room, err := h.roomService.GetRoom(c.Request.Context(), roomID)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to get room",
zap.Error(err),
zap.String("room_id", roomID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation", err))
return
}
RespondSuccess(c, http.StatusOK, room)
}
// UpdateRoom met à jour une room (conversation)
// PUT /api/v1/conversations/:id
// BE-API-012: Implement conversation update endpoint
func (h *RoomHandler) UpdateRoom(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid room id"))
return
}
// Récupérer l'ID utilisateur du contexte
userID, ok := GetUserIDUUID(c)
if !ok {
return // Erreur déjà envoyée par GetUserIDUUID
}
// Parser la requête
var req services.UpdateRoomRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// Mettre à jour la room
room, err := h.roomService.UpdateRoom(c.Request.Context(), roomID, userID, req)
if err != nil {
if err.Error() == "room not found" || errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("conversation"))
return
}
if err.Error() == "forbidden: only room creator can update the room" {
RespondWithAppError(c, apperrors.NewForbiddenError("only room creator can update the room"))
return
}
h.logger.Error("failed to update room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update conversation", err))
return
}
h.logger.Info("room updated successfully",
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondSuccess(c, http.StatusOK, room)
}
// AddMemberRequest représente une requête pour ajouter un membre à une room
// MOD-P1-001: Ajout tags validate pour validation systématique
type AddMemberRequest struct {
UserID uuid.UUID `json:"user_id" binding:"required" validate:"required"` // Changed to UUID
}
// AddMember ajoute un membre à une room
// POST /api/v1/conversations/:id/members
func (h *RoomHandler) AddMember(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
return
}
// Parser la requête
var req AddMemberRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// Ajouter le membre
if err := h.roomService.AddMember(c.Request.Context(), roomID, req.UserID); err != nil {
h.logger.Error("failed to add member to room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", req.UserID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to add member", err))
return
}
h.logger.Info("member added to room",
zap.String("room_id", roomID.String()),
zap.String("user_id", req.UserID.String()))
RespondSuccess(c, http.StatusOK, gin.H{"message": "Member added successfully"})
}
// GetRoomHistory récupère l'historique des messages d'une room
// GET /api/v1/conversations/:id/history
// v0.931: Supports cursor-based pagination via ?cursor=xxx&limit=20. Falls back to offset when cursor not provided.
// SECURITY(CRIT-001): Verify membership before returning history
func (h *RoomHandler) GetRoomHistory(c *gin.Context) {
conversationIDStr := c.Param("id")
conversationID, err := uuid.Parse(conversationIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID"))
return
}
// SECURITY(CRIT-001): Verify the requesting user is a member of this room
userID, ok := GetUserIDUUID(c)
if !ok {
RespondWithAppError(c, apperrors.NewUnauthorizedError("authentication required"))
return
}
isMember, err := h.roomService.IsRoomMember(c.Request.Context(), conversationID, userID)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to check room membership",
zap.Error(err),
zap.String("conversation_id", conversationID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation history", err))
return
}
if !isMember {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
limit := c.DefaultQuery("limit", "50")
limitInt, err := strconv.Atoi(limit)
if err != nil || limitInt <= 0 {
limitInt = 50
}
if limitInt > 100 {
limitInt = 100
}
cursor := c.Query("cursor")
if cursor != "" {
result, err := h.roomService.GetRoomHistoryWithCursor(c.Request.Context(), conversationID, limitInt, cursor)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to get room history",
zap.Error(err),
zap.String("conversation_id", conversationID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation history", err))
return
}
resp := gin.H{"messages": result.Messages}
if result.NextCursor != "" {
resp["next_cursor"] = result.NextCursor
}
RespondSuccess(c, http.StatusOK, resp)
return
}
offset := c.DefaultQuery("offset", "0")
offsetInt, err := strconv.Atoi(offset)
if err != nil || offsetInt < 0 {
offsetInt = 0
}
messages, err := h.roomService.GetRoomHistory(c.Request.Context(), conversationID, limitInt, offsetInt)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to get room history",
zap.Error(err),
zap.String("conversation_id", conversationID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation history", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"messages": messages})
}
// DeleteRoom supprime une room (conversation)
// DELETE /api/v1/conversations/:id
// BE-API-010: Implement conversation delete endpoint
func (h *RoomHandler) DeleteRoom(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid room id"))
return
}
// Récupérer l'ID utilisateur du contexte
userID, ok := GetUserIDUUID(c)
if !ok {
return // Erreur déjà envoyée par GetUserIDUUID
}
// Supprimer la room
if err := h.roomService.DeleteRoom(c.Request.Context(), roomID, userID); err != nil {
if err.Error() == "room not found" || errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("conversation"))
return
}
if err.Error() == "forbidden: only room creator can delete the room" {
RespondWithAppError(c, apperrors.NewForbiddenError("only room creator can delete the room"))
return
}
h.logger.Error("failed to delete room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to delete conversation", err))
return
}
h.logger.Info("room deleted successfully",
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondSuccess(c, http.StatusOK, gin.H{"message": "Conversation deleted successfully"})
}
// AddParticipant ajoute un participant à une conversation
// POST /api/v1/conversations/:id/participants
// BE-API-011: Implement conversation participants endpoints
func (h *RoomHandler) AddParticipant(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid room id"))
return
}
// Parser la requête
var req AddMemberRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// Ajouter le participant (utilise la même logique que AddMember)
if err := h.roomService.AddMember(c.Request.Context(), roomID, req.UserID); err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("conversation"))
return
}
h.logger.Error("failed to add participant to room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", req.UserID.String()))
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to add participant", err))
return
}
h.logger.Info("participant added to room",
zap.String("room_id", roomID.String()),
zap.String("user_id", req.UserID.String()))
RespondSuccess(c, http.StatusOK, gin.H{"message": "Participant added successfully"})
}
// RemoveParticipant retire un participant d'une conversation
// DELETE /api/v1/conversations/:id/participants/:userId
// BE-API-011: Implement conversation participants endpoints
func (h *RoomHandler) RemoveParticipant(c *gin.Context) {
// Récupérer l'ID de la room depuis l'URL
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid room id"))
return
}
// Récupérer l'ID de l'utilisateur depuis l'URL
userIDStr := c.Param("userId")
userID, err := uuid.Parse(userIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid user id"))
return
}
// Retirer le participant
if err := h.roomService.RemoveMember(c.Request.Context(), roomID, userID); err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("conversation"))
return
}
h.logger.Error("failed to remove participant from room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to remove participant", err))
return
}
h.logger.Info("participant removed from room",
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondSuccess(c, http.StatusOK, gin.H{"message": "Participant removed successfully"})
}
// CreateInvitation creates a room invitation (v0.9.7)
// POST /api/v1/conversations/:id/invitations
func (h *RoomHandler) CreateInvitation(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
roomID, err := uuid.Parse(c.Param("id"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
return
}
resp, err := h.roomService.CreateInvitation(c.Request.Context(), roomID, userID)
if err != nil {
if err.Error() == "forbidden: only owner or admin can create invitations" {
RespondWithAppError(c, apperrors.NewForbiddenError(err.Error()))
return
}
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to create invitation", zap.Error(err), zap.String("room_id", roomID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create invitation", err))
return
}
RespondSuccess(c, http.StatusCreated, resp)
}
// JoinByToken joins a room via invitation token (v0.9.7)
// GET /api/v1/conversations/join/:token
func (h *RoomHandler) JoinByToken(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
token, err := uuid.Parse(c.Param("token"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid token"))
return
}
roomID, err := h.roomService.JoinByToken(c.Request.Context(), token, userID)
if err != nil {
if err.Error() == "invitation not found or expired" {
RespondWithAppError(c, apperrors.NewNotFoundError(err.Error()))
return
}
h.logger.Error("failed to join via token", zap.Error(err), zap.String("token", token.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to join", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"room_id": roomID})
}
// KickMember removes a member (owner/admin only) (v0.9.7)
// DELETE /api/v1/conversations/:id/members/:userId
func (h *RoomHandler) KickMember(c *gin.Context) {
requestUserID, ok := GetUserIDUUID(c)
if !ok {
return
}
roomID, err := uuid.Parse(c.Param("id"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
return
}
targetUserID, err := uuid.Parse(c.Param("userId"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID"))
return
}
if err := h.roomService.KickMember(c.Request.Context(), roomID, targetUserID, requestUserID); err != nil {
if err.Error() == "forbidden: only owner or admin can remove members" {
RespondWithAppError(c, apperrors.NewForbiddenError(err.Error()))
return
}
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to kick member", zap.Error(err), zap.String("room_id", roomID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to remove member", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "Member removed successfully"})
}
// LeaveRoom allows a user to leave a conversation (self-removal) (v0.9.7)
// POST /api/v1/conversations/:id/leave
func (h *RoomHandler) LeaveRoom(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
roomID, err := uuid.Parse(c.Param("id"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID"))
return
}
if err := h.roomService.RemoveMember(c.Request.Context(), roomID, userID); err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to leave room",
zap.Error(err),
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to leave conversation", err))
return
}
h.logger.Info("user left room",
zap.String("room_id", roomID.String()),
zap.String("user_id", userID.String()))
RespondSuccess(c, http.StatusOK, gin.H{"message": "Left conversation successfully"})
}
// UpdateMemberRole updates a member's role (v0.9.7). Owner only.
// PATCH /api/v1/conversations/:id/members/:userId
func (h *RoomHandler) UpdateMemberRole(c *gin.Context) {
requestUserID, ok := GetUserIDUUID(c)
if !ok {
return
}
roomID, err := uuid.Parse(c.Param("id"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID"))
return
}
targetUserID, err := uuid.Parse(c.Param("userId"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID"))
return
}
var req services.UpdateMemberRoleRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
if err := h.roomService.UpdateMemberRole(c.Request.Context(), roomID, targetUserID, requestUserID, req.Role); err != nil {
if err.Error() == "forbidden: only owner can change member roles" ||
err.Error() == "forbidden: cannot change owner role" ||
err.Error() == "forbidden: owner cannot change their own role" ||
err.Error() == "forbidden: cannot promote to owner via this endpoint" {
RespondWithAppError(c, apperrors.NewForbiddenError(err.Error()))
return
}
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation or member"))
return
}
h.logger.Error("failed to update member role", zap.Error(err))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update member role", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "Member role updated successfully"})
}
// GetMembers returns room members for @mention autocomplete (v0.9.6)
// GET /api/v1/conversations/:id/members
func (h *RoomHandler) GetMembers(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
roomIDStr := c.Param("id")
roomID, err := uuid.Parse(roomIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
return
}
resp, err := h.roomService.GetRoomMembers(c.Request.Context(), roomID, userID)
if err != nil {
if errors.Is(err, services.ErrRoomNotFound) {
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
return
}
h.logger.Error("failed to get room members", zap.Error(err), zap.String("room_id", roomID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get members", err))
return
}
RespondSuccess(c, http.StatusOK, resp)
}