Skip to content

Commit fb89ed3

Browse files
attempting to send and receive attachments (images and text) in the chat
1 parent 1e886d4 commit fb89ed3

File tree

8 files changed

+433
-31
lines changed

8 files changed

+433
-31
lines changed

apps/api/src/chat/chat.controller.ts

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ import {
99
NotFoundException,
1010
BadRequestException,
1111
UnauthorizedException,
12+
UseInterceptors,
13+
UploadedFile,
1214
} from '@nestjs/common';
1315
import { ChatService } from './chat.service';
1416
import { UsersService } from '../users/users.service';
1517
import { JwtAuthGuard } from '@/auth/jwt.auth.guard';
18+
import { FileInterceptor } from '@nestjs/platform-express';
19+
import { CloudinaryService } from '../cloudinary/cloudinary.service';
20+
import { ApiConsumes, ApiBody } from '@nestjs/swagger';
21+
import { SendMessageDto } from './dto/send-message.dto';
1622

1723
@Controller('chat')
1824
export class ChatController {
1925
constructor(
2026
private readonly chatService: ChatService,
21-
private readonly userService: UsersService
27+
private readonly userService: UsersService,
28+
private readonly cloudinaryService: CloudinaryService
2229
) {}
2330

2431
@Post(':receiverId/send')
@@ -27,10 +34,13 @@ export class ChatController {
2734
@Request() req,
2835
@Param('receiverId') receiverId: string,
2936
@Body('text') text: string,
30-
@Body('messageId') messageId: string
37+
@Body('messageId') messageId: string,
38+
@Body('fileUrl') fileUrl?: string,
39+
@Body('fileName') fileName?: string,
40+
@Body('fileType') fileType?: string
3141
) {
32-
if (!text.trim()) {
33-
throw new BadRequestException('Message cannot be empty');
42+
if (!text.trim() && !fileUrl) {
43+
throw new BadRequestException('Message cannot be empty and no file attached');
3444
}
3545

3646
console.log('Request user:', req.user); // Log the user object to debug
@@ -54,14 +64,77 @@ export class ChatController {
5464
sender,
5565
receiver,
5666
text,
57-
messageId
67+
messageId,
68+
fileUrl,
69+
fileName,
70+
fileType
5871
);
5972
} catch (error) {
6073
console.error('Error sending message:', error.message);
6174
throw error;
6275
}
6376
}
6477

78+
@Post(':receiverId/upload')
79+
@UseGuards(JwtAuthGuard)
80+
@UseInterceptors(FileInterceptor('file'))
81+
@ApiConsumes('multipart/form-data')
82+
@ApiBody({
83+
schema: {
84+
type: 'object',
85+
properties: {
86+
file: {
87+
type: 'string',
88+
format: 'binary',
89+
},
90+
text: {
91+
type: 'string',
92+
},
93+
messageId: {
94+
type: 'string',
95+
},
96+
},
97+
},
98+
})
99+
async uploadFile(
100+
@Request() req,
101+
@Param('receiverId') receiverId: string,
102+
@UploadedFile() file: Express.Multer.File,
103+
@Body('text') text: string = '',
104+
@Body('messageId') messageId: string
105+
) {
106+
try {
107+
// Upload file to Cloudinary
108+
const uploadResult = await this.cloudinaryService.uploadFile(file);
109+
console.log('File uploaded to Cloudinary:', uploadResult);
110+
111+
const sender = await this.userService.getAuthenticatedUser(req.user.id);
112+
const receiver = await this.userService.findById(receiverId);
113+
114+
if (!sender) {
115+
throw new NotFoundException(`Sender with ID ${req.user.id} not found`);
116+
}
117+
118+
if (!receiver) {
119+
throw new NotFoundException(`Receiver with ID ${receiverId} not found`);
120+
}
121+
122+
// Create message with file details
123+
return await this.chatService.sendMessageWithId(
124+
sender,
125+
receiver,
126+
text || `Sent a file: ${file.originalname}`, // Include full filename with extension in message
127+
messageId,
128+
uploadResult.secure_url,
129+
file.originalname, // Use full original filename with extension
130+
file.mimetype
131+
);
132+
} catch (error) {
133+
console.error('Error uploading file:', error);
134+
throw new BadRequestException(`File upload failed: ${error.message}`);
135+
}
136+
}
137+
65138
@UseGuards(JwtAuthGuard)
66139
@Get(':userId1/:userId2/messages')
67140
async getMessages(
@@ -99,12 +172,12 @@ export class ChatController {
99172

100173
// Endpoint to get unread counts
101174
@UseGuards(JwtAuthGuard)
102-
@Get('unread-counts')
103-
async getUnreadCounts(@Request() req) {
104-
const userId = req.user.sub;
105-
console.log(`API called by userId: ${userId}`);
106-
return await this.chatService.getUnreadCounts(userId);
107-
}
175+
@Get('unread-counts')
176+
async getUnreadCounts(@Request() req) {
177+
const userId = req.user.sub;
178+
console.log(`API called by userId: ${userId}`);
179+
return await this.chatService.getUnreadCounts(userId);
180+
}
108181

109182
@UseGuards(JwtAuthGuard)
110183
@Get('active-chats')

apps/api/src/chat/chat.gateway.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
113113
@SubscribeMessage('sendMessage')
114114
async handleSendMessage(
115115
@MessageBody()
116-
message: { id: string; senderId: string; receiverId: string; text: string },
116+
message: {
117+
id: string;
118+
senderId: string;
119+
receiverId: string;
120+
text: string;
121+
fileUrl?: string;
122+
fileName?: string;
123+
fileType?: string;
124+
},
117125
@ConnectedSocket() client: Socket
118126
) {
119127
try {
@@ -141,7 +149,10 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
141149
sender,
142150
receiver,
143151
message.text,
144-
message.id
152+
message.id,
153+
message.fileUrl,
154+
message.fileName,
155+
message.fileType
145156
);
146157

147158
this.server
@@ -153,6 +164,9 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
153164
text: savedMessage.message,
154165
timestamp: savedMessage.createdAt,
155166
isRead: savedMessage.isRead,
167+
fileUrl: savedMessage.fileUrl,
168+
fileName: savedMessage.fileName,
169+
fileType: savedMessage.fileType
156170
});
157171

158172
this.server.to(message.senderId).emit('messageDelivered', {

apps/api/src/chat/chat.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
1010
import { Room } from './entities/room.entity';
1111
import { ChatMessage } from './entities/chat-message.entity';
1212
import { RoomsModule } from './rooms.module';
13+
import { CloudinaryModule } from '@/cloudinary/cloudinary.module';
14+
import { MulterModule } from '@nestjs/platform-express';
1315

1416
@Module({
1517
imports: [
1618
TypeOrmModule.forFeature([ChatMessage, Room]), // Include Room entity here
1719
UsersModule,
1820
AuthModule,
1921
RoomsModule,
22+
CloudinaryModule, // Add CloudinaryModule here
23+
MulterModule.register(), // Register MulterModule for file uploads
2024
JwtModule.registerAsync({
2125
imports: [ConfigModule],
2226
inject: [ConfigService],

apps/api/src/chat/chat.service.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,18 @@ export class ChatService {
3333
async sendMessage(
3434
sender: User,
3535
receiver: User,
36-
message: string
36+
message: string,
37+
fileUrl?: string,
38+
fileName?: string,
39+
fileType?: string
3740
): Promise<ChatMessage> {
3841
const chatMessage = this.chatRepository.create({
3942
sender,
4043
receiver,
4144
message,
45+
fileUrl,
46+
fileName,
47+
fileType,
4248
});
4349
const savedMessage = await this.chatRepository.save(chatMessage);
4450
console.log('Saved message with createdAt:', savedMessage.createdAt);
@@ -54,13 +60,19 @@ export class ChatService {
5460
sender: User,
5561
receiver: User,
5662
message: string,
57-
id: string
63+
id: string,
64+
fileUrl?: string,
65+
fileName?: string,
66+
fileType?: string
5867
): Promise<ChatMessage> {
5968
const chatMessage = this.chatRepository.create({
6069
id, // Set the unique ID for the message
6170
sender,
6271
receiver,
6372
message,
73+
fileUrl,
74+
fileName,
75+
fileType,
6476
});
6577
const savedMessage = await this.chatRepository.save(chatMessage);
6678
console.log('Saved message with createdAt:', savedMessage.createdAt);
@@ -104,10 +116,19 @@ export class ChatService {
104116
return Array.from(chatPartnersMap.values());
105117
}
106118

107-
async sendMessageToRoom(room: Room, message: string): Promise<ChatMessage> {
119+
async sendMessageToRoom(
120+
room: Room,
121+
message: string,
122+
fileUrl?: string,
123+
fileName?: string,
124+
fileType?: string
125+
): Promise<ChatMessage> {
108126
const chatMessage = this.chatRepository.create({
109127
message,
110128
room,
129+
fileUrl,
130+
fileName,
131+
fileType,
111132
});
112133
return this.chatRepository.save(chatMessage);
113134
}
Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,51 @@
1-
import { IsString, IsNotEmpty } from 'class-validator';
1+
import { IsString, IsNotEmpty, IsOptional, IsUrl, ValidateIf } from 'class-validator';
2+
import { ApiProperty } from '@nestjs/swagger';
23

34
export class SendMessageDto {
5+
@ApiProperty({
6+
description: 'The message content',
7+
example: 'Hello, how are you?',
8+
})
49
@IsString()
510
@IsNotEmpty()
611
message: string;
12+
13+
@ApiProperty({
14+
description: 'The unique ID for the message',
15+
example: 'uuid-1234',
16+
required: true,
17+
})
18+
@IsString()
19+
@IsNotEmpty()
20+
messageId: string;
21+
22+
@ApiProperty({
23+
description: 'The URL of the attached file (if any)',
24+
example: 'https://res.cloudinary.com/example/image.jpg',
25+
required: false,
26+
})
27+
@IsOptional()
28+
@IsUrl()
29+
@ValidateIf(o => o.fileUrl !== undefined)
30+
fileUrl?: string;
31+
32+
@ApiProperty({
33+
description: 'The original name of the attached file',
34+
example: 'document.pdf',
35+
required: false,
36+
})
37+
@IsOptional()
38+
@IsString()
39+
@ValidateIf(o => o.fileName !== undefined)
40+
fileName?: string;
41+
42+
@ApiProperty({
43+
description: 'The MIME type of the attached file',
44+
example: 'application/pdf',
45+
required: false,
46+
})
47+
@IsOptional()
48+
@IsString()
49+
@ValidateIf(o => o.fileType !== undefined)
50+
fileType?: string;
751
}

apps/api/src/chat/entities/chat-message.entity.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ export class ChatMessage {
2121
@Column({ type: 'text' })
2222
message!: string;
2323

24+
@ApiProperty({
25+
example: 'https://res.cloudinary.com/example/image.jpg',
26+
description: 'The URL of the attached file',
27+
required: false,
28+
})
29+
@Column({ nullable: true })
30+
fileUrl?: string;
31+
32+
@ApiProperty({
33+
example: 'image.jpg',
34+
description: 'The original name of the attached file',
35+
required: false,
36+
})
37+
@Column({ nullable: true })
38+
fileName?: string;
39+
40+
@ApiProperty({
41+
example: 'image/jpeg',
42+
description: 'The MIME type of the attached file',
43+
required: false,
44+
})
45+
@Column({ nullable: true })
46+
fileType?: string;
47+
2448
@ApiProperty({
2549
example: 'uuid-1234',
2650
description: 'The ID of the user who sent the message',

0 commit comments

Comments
 (0)