Spaces:
Sleeping
Sleeping
File size: 7,368 Bytes
a12cb0a 5c20486 a12cb0a 5c20486 a12cb0a 5c20486 a12cb0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
Req,
UseGuards,
ParseIntPipe,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiParam,
ApiBody,
ApiResponse,
ApiBearerAuth,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../../auth/guards/roles.guard';
import { MessagingService } from '../services/messaging.service';
import {
StartConversationDto,
SendMessageDto,
MessageQueryDto,
SearchMessagesDto,
EditMessageDto,
SearchUsersDto,
} from '../dto';
@ApiTags('💬 Messaging')
@ApiBearerAuth('JWT-auth')
@Controller('api/messages')
@UseGuards(JwtAuthGuard, RolesGuard)
export class MessagingController {
constructor(private readonly messagingService: MessagingService) {}
@Get('users/search')
@ApiOperation({
summary: 'Search users',
description: 'Search for users by name or email. Available to all authenticated users. Useful for starting new conversations.',
})
@ApiResponse({ status: 200, description: 'Matching users' })
async searchUsers(@Query() dto: SearchUsersDto) {
return this.messagingService.searchUsers(dto.query, dto.limit);
}
@Get('conversations')
@ApiOperation({
summary: 'List conversations',
description: 'List all conversations for the current user. Returns last message, unread count, and participants for each conversation.',
})
@ApiResponse({ status: 200, description: 'Conversations retrieved' })
async listConversations(@Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.listConversations(userId);
}
@Post('conversations')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Start new conversation',
description: 'Start a new direct or group conversation. For direct messages, if a conversation already exists with the same user, the message is sent there instead.',
})
@ApiBody({ type: StartConversationDto })
@ApiResponse({ status: 201, description: 'Conversation created or message sent to existing' })
async startConversation(@Body() dto: StartConversationDto, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.startConversation(userId, dto);
}
@Get('conversations/:id')
@ApiOperation({
summary: 'Get conversation messages',
description: 'Get paginated messages in a conversation. Messages deleted for the current user are excluded. Deleted-for-everyone messages show "This message was deleted".',
})
@ApiParam({ name: 'id', description: 'Conversation (root message) ID', example: 1 })
@ApiResponse({ status: 200, description: 'Messages retrieved' })
async getConversationMessages(
@Param('id', ParseIntPipe) id: number,
@Query() query: MessageQueryDto,
@Req() req: any,
) {
const userId = req.user.userId || req.user.id;
return this.messagingService.getConversationMessages(id, userId, query);
}
@Post('conversations/:id')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Send message in conversation',
description: 'Send a message in an existing conversation. Supports text and optional file attachment. Message is delivered in real-time via WebSocket to all participants.',
})
@ApiParam({ name: 'id', description: 'Conversation ID', example: 1 })
@ApiBody({ type: SendMessageDto })
@ApiResponse({ status: 201, description: 'Message sent' })
async sendMessage(
@Param('id', ParseIntPipe) id: number,
@Body() dto: SendMessageDto,
@Req() req: any,
) {
const userId = req.user.userId || req.user.id;
return this.messagingService.sendMessage(id, userId, dto);
}
@Patch(':id')
@ApiOperation({
summary: 'Edit message',
description: 'Edit the text of a message. Only the original sender can edit. Updates the editedAt timestamp.',
})
@ApiParam({ name: 'id', description: 'Message ID', example: 1 })
@ApiBody({ type: EditMessageDto })
@ApiResponse({ status: 200, description: 'Message edited' })
@ApiResponse({ status: 403, description: 'Only the sender can edit' })
async editMessage(
@Param('id', ParseIntPipe) id: number,
@Body() dto: EditMessageDto,
@Req() req: any,
) {
const userId = req.user.userId || req.user.id;
return this.messagingService.editMessage(id, userId, dto.text);
}
@Patch(':id/read')
@ApiOperation({
summary: 'Mark message as read',
description: 'Mark a single message as read. Updates the read receipt timestamp.',
})
@ApiParam({ name: 'id', description: 'Message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Message marked as read' })
async markAsRead(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.markAsRead(id, userId);
}
@Delete(':id')
@ApiOperation({
summary: 'Delete message for me',
description: 'Soft-delete a message for the current user only. Other participants can still see it.',
})
@ApiParam({ name: 'id', description: 'Message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Message deleted for you' })
async deleteForMe(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.deleteForMe(id, userId);
}
@Delete(':id/everyone')
@ApiOperation({
summary: 'Delete message for everyone',
description: 'Delete a message for all participants (WhatsApp-style). Only the original sender can do this. Shows "This message was deleted" for all.',
})
@ApiParam({ name: 'id', description: 'Message ID', example: 1 })
@ApiResponse({ status: 200, description: 'Message deleted for everyone' })
@ApiResponse({ status: 403, description: 'Only the sender can delete for everyone' })
async deleteForEveryone(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.deleteForEveryone(id, userId);
}
@Get('unread-count')
@ApiOperation({
summary: 'Get unread message count',
description: 'Get total count of unread messages across all conversations.',
})
@ApiResponse({ status: 200, description: 'Unread count', schema: { example: { count: 5 } } })
async getUnreadCount(@Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.getUnreadCount(userId);
}
@Get('search')
@ApiOperation({
summary: 'Search messages',
description: 'Search through all messages the user has access to. Returns matching messages with conversation context.',
})
@ApiResponse({ status: 200, description: 'Search results' })
async searchMessages(@Query() dto: SearchMessagesDto, @Req() req: any) {
const userId = req.user.userId || req.user.id;
return this.messagingService.searchMessages(userId, dto);
}
@Get('online-users')
@ApiOperation({
summary: 'Get online users',
description: 'Get list of currently online user IDs (connected via WebSocket).',
})
@ApiResponse({ status: 200, description: 'Online user IDs', schema: { example: { onlineUserIds: [57, 58] } } })
async getOnlineUsers() {
return { onlineUserIds: this.messagingService.getOnlineUserIds() };
}
}
|