308 lines
9.0 KiB
TypeScript
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 };
|
|
}
|
|
}
|