Files
backend/src/messages/messages.controller.ts

308 lines
9.0 KiB
TypeScript

import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
UseGuards,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiQuery,
ApiParam,
} from '@nestjs/swagger';
import { MessagesService } from './messages.service';
import { MessagesGateway } from './messages.gateway';
import { CreateMessageDto, StartConversationDto } from './dto';
import { JwtAuthGuard } from '../auth/guards';
import { CurrentUser } from '../auth/decorators';
import { UserRole } from '@prisma/client';
@ApiTags('Messages')
@Controller('messages')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
export class MessagesController {
constructor(
private readonly messagesService: MessagesService,
private readonly messagesGateway: MessagesGateway,
) {}
// ==========================================
// GET CONVERSATIONS LIST
// ==========================================
@Get('conversations')
@ApiOperation({ summary: 'Get all conversations for the current user' })
@ApiResponse({
status: 200,
description: 'List of conversations',
schema: {
example: [
{
id: 'conv-uuid',
lastMessageAt: '2024-01-01T00:00:00.000Z',
lastMessageText: 'Hello!',
unreadCount: 2,
otherParty: {
id: 'agent-uuid',
name: 'John Doe',
avatar: 'https://...',
headline: 'Real Estate Agent',
isOnline: true,
},
},
],
},
})
async getConversations(
@CurrentUser('id') userId: string,
@CurrentUser('role') role: UserRole,
) {
return this.messagesService.getConversations(userId, role);
}
// ==========================================
// START OR GET CONVERSATION
// ==========================================
@Post('conversations')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Start or get existing conversation with a user or agent' })
@ApiResponse({
status: 200,
description: 'Conversation created or retrieved',
})
@ApiResponse({
status: 403,
description: 'Connection not accepted',
})
async startConversation(
@CurrentUser('id') callerUserId: string,
@CurrentUser('role') role: UserRole,
@Body() dto: StartConversationDto,
) {
return this.messagesService.getOrCreateConversation(callerUserId, role, dto);
}
// ==========================================
// GET CONVERSATION DETAILS
// ==========================================
@Get('conversations/:id')
@ApiOperation({ summary: 'Get a specific conversation' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiResponse({
status: 200,
description: 'Conversation details',
})
@ApiResponse({
status: 404,
description: 'Conversation not found',
})
async getConversation(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
) {
return this.messagesService.getConversationById(conversationId, userId);
}
// ==========================================
// GET MESSAGES FOR CONVERSATION
// ==========================================
@Get('conversations/:id/messages')
@ApiOperation({ summary: 'Get messages for a conversation' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiResponse({
status: 200,
description: 'List of messages with pagination',
schema: {
example: {
messages: [
{
id: 'msg-uuid',
content: 'Hello!',
messageType: 'TEXT',
status: 'READ',
createdAt: '2024-01-01T00:00:00.000Z',
sender: {
id: 'user-uuid',
role: 'USER',
userProfile: {
firstName: 'Jane',
lastName: 'Doe',
},
},
},
],
pagination: {
page: 1,
limit: 50,
total: 100,
pages: 2,
},
},
},
})
async getMessages(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
return this.messagesService.getMessages(
conversationId,
userId,
page ? parseInt(page, 10) : 1,
limit ? parseInt(limit, 10) : 50,
);
}
// ==========================================
// SEND MESSAGE (REST FALLBACK)
// ==========================================
@Post('conversations/:id/messages')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Send a message (REST fallback - prefer WebSocket)' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiResponse({
status: 201,
description: 'Message created',
})
async sendMessage(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
@Body() dto: CreateMessageDto,
) {
const message = await this.messagesService.createMessage(conversationId, userId, dto);
// Broadcast via WebSocket so the other party gets real-time delivery
try {
const participants = await this.messagesService.getConversationParticipants(conversationId);
if (participants) {
const otherUserId = userId === participants.userId
? participants.agentUserId
: participants.userId;
// Send to the other participant's socket(s)
this.messagesGateway.sendToUser(otherUserId, 'new_message', message);
}
} catch {
// Socket broadcast failure shouldn't fail the REST response
}
return message;
}
// ==========================================
// MARK MESSAGES AS READ
// ==========================================
@Patch('conversations/:id/read')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Mark all messages in conversation as read' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiResponse({
status: 200,
description: 'Messages marked as read',
})
async markAsRead(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
) {
return this.messagesService.markMessagesAsRead(conversationId, userId);
}
// ==========================================
// TOGGLE MUTE
// ==========================================
@Patch('conversations/:id/mute')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Mute or unmute conversation notifications' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
async toggleMute(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
@Body() dto: { muted: boolean },
) {
return this.messagesService.toggleMute(conversationId, userId, dto.muted);
}
// ==========================================
// TOGGLE FAVORITE
// ==========================================
@Patch('conversations/:id/favorite')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Star or unstar a conversation' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
async toggleFavorite(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
@Body() dto: { favorited: boolean },
) {
return this.messagesService.toggleFavorite(conversationId, userId, dto.favorited);
}
// ==========================================
// CLEAR CHAT (delete messages, keep conversation)
// ==========================================
@Delete('conversations/:id/messages')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Clear all messages in a conversation' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiResponse({ status: 200, description: 'Messages cleared' })
async clearChat(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
) {
return this.messagesService.clearChat(conversationId, userId);
}
// ==========================================
// DELETE CONVERSATION
// ==========================================
@Delete('conversations/:id')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Delete a conversation and all its messages' })
@ApiParam({ name: 'id', description: 'Conversation ID' })
@ApiResponse({
status: 200,
description: 'Conversation deleted',
})
@ApiResponse({
status: 404,
description: 'Conversation not found',
})
async deleteConversation(
@Param('id') conversationId: string,
@CurrentUser('id') userId: string,
) {
return this.messagesService.deleteConversation(conversationId, userId);
}
// ==========================================
// GET UNREAD COUNT
// ==========================================
@Get('unread-count')
@ApiOperation({ summary: 'Get total unread message count' })
@ApiResponse({
status: 200,
description: 'Unread count',
schema: {
example: { unreadCount: 5 },
},
})
async getUnreadCount(
@CurrentUser('id') userId: string,
@CurrentUser('role') role: UserRole,
) {
const unreadCount = await this.messagesService.getTotalUnreadCount(userId, role);
return { unreadCount };
}
}