# Messaging Service API Documentation ## Base URL ``` Production: https://talentlink.io.vn/api/v1 Development: http://localhost:8888/api/v1 ``` ## Authentication Tất cả các endpoints đều yêu cầu authentication token trong header: ``` Authorization: Bearer ``` --- ## 1. Storage API - Upload Files ### Upload File to MinIO Upload ảnh/video/audio/file để sử dụng trong tin nhắn. **Endpoint:** `POST /storage/upload` **Request Headers:** ``` Authorization: Bearer Content-Type: multipart/form-data ``` **Request Body:** ``` file: (max 10MB) ``` **Allowed File Types:** - Images: image/jpeg, image/png, image/gif, image/webp - Videos: video/mp4, video/mpeg, video/quicktime - Audio: audio/mpeg, audio/wav, audio/ogg - Documents: application/pdf, application/msword, application/vnd.* **Response:** `201 Created` ```json { "url": "http://minio:9000/messaging/messages/1732546789123-photo.jpg", "filename": "photo.jpg", "mimetype": "image/jpeg", "size": 102400 } ``` **Example:** ```javascript const formData = new FormData(); formData.append('file', fileInput.files[0]); const response = await fetch('https://talentlink.io.vn/api/v1/storage/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const data = await response.json(); console.log('File URL:', data.url); ``` --- ## 2. Conversations API ### 2.1. Create Conversation Tạo cuộc trò chuyện mới giữa 2 người hoặc group chat. **Endpoint:** `POST /conversations` **Request Body:** ```json { "participantIds": ["user-uuid-1", "user-uuid-2"], "conversationType": "direct", // "direct" hoặc "group" "conversationName": "Team Discussion" // Chỉ bắt buộc cho group } ``` **Response:** `201 Created` ```json { "id": "conv-uuid-123", "conversationType": "direct", "conversationName": null, "createdBy": "current-user-uuid", "createdAt": "2025-11-25T15:30:00.000Z", "updatedAt": "2025-11-25T15:30:00.000Z", "participants": [ { "id": "participant-1-uuid", "userId": "user-uuid-1", "conversationId": "conv-uuid-123", "lastReadAt": "2025-11-25T15:30:00.000Z", "joinedAt": "2025-11-25T15:30:00.000Z" }, { "id": "participant-2-uuid", "userId": "user-uuid-2", "conversationId": "conv-uuid-123", "lastReadAt": null, "joinedAt": "2025-11-25T15:30:00.000Z" } ] } ``` ### 2.2. Get All Conversations Lấy danh sách tất cả conversations của user hiện tại. **Endpoint:** `GET /conversations` **Response:** `200 OK` ```json [ { "id": "conv-uuid-123", "conversationType": "direct", "conversationName": null, "createdBy": "user-uuid-1", "createdAt": "2025-11-25T15:00:00.000Z", "updatedAt": "2025-11-25T15:30:00.000Z", "participants": [...], "lastMessage": { "content": "Hello!", "createdAt": "2025-11-25T15:30:00.000Z", "senderId": "user-uuid-2" }, "unreadCount": 3 } ] ``` ### 2.3. Get Conversation by ID Lấy chi tiết một conversation. **Endpoint:** `GET /conversations/:id` **Response:** `200 OK` ```json { "id": "conv-uuid-123", "conversationType": "group", "conversationName": "Team Discussion", "createdBy": "user-uuid-1", "createdAt": "2025-11-25T15:00:00.000Z", "updatedAt": "2025-11-25T15:30:00.000Z", "participants": [...] } ``` ### 2.4. Update Conversation Cập nhật tên conversation (chỉ cho group chat). **Endpoint:** `PUT /conversations/:id` **Request Body:** ```json { "conversationName": "New Team Name" } ``` **Response:** `200 OK` ```json { "id": "conv-uuid-123", "conversationType": "group", "conversationName": "New Team Name", "updatedAt": "2025-11-25T15:35:00.000Z" } ``` ### 2.5. Add Participant Thêm người vào group chat. **Endpoint:** `POST /conversations/:id/participants` **Request Body:** ```json { "userId": "new-user-uuid" } ``` **Response:** `201 Created` ```json { "id": "participant-uuid", "userId": "new-user-uuid", "conversationId": "conv-uuid-123", "joinedAt": "2025-11-25T15:40:00.000Z" } ``` ### 2.6. Remove Participant Xóa người khỏi group chat. **Endpoint:** `DELETE /conversations/:id/participants/:participantId` **Response:** `200 OK` ```json { "message": "Participant removed successfully" } ``` ### 2.7. Delete Conversation Xóa conversation (chỉ creator mới được xóa). **Endpoint:** `DELETE /conversations/:id` **Response:** `200 OK` ```json { "message": "Conversation deleted successfully" } ``` --- ## 3. Messages API ### 3.1. Send Message Gửi tin nhắn mới trong conversation. **Endpoint:** `POST /messages` **Request Body:** ```json { "conversationId": "conv-uuid-123", "content": "Hello everyone!", "attachmentUrl": "http://minio:9000/messaging/messages/1732546789123-photo.jpg", // Optional "attachmentType": "image" // Optional: "image", "video", "audio", "file" } ``` **Response:** `201 Created` ```json { "id": "msg-uuid-456", "conversationId": "conv-uuid-123", "senderId": "current-user-uuid", "content": "Hello everyone!", "attachmentUrl": "http://minio:9000/messaging/messages/1732546789123-photo.jpg", "attachmentType": "image", "isEdited": false, "isDeleted": false, "createdAt": "2025-11-25T15:45:00.000Z", "updatedAt": "2025-11-25T15:45:00.000Z" } ``` **Flow gửi tin nhắn có ảnh:** ```javascript // Bước 1: Upload ảnh const formData = new FormData(); formData.append('file', imageFile); const uploadResponse = await fetch('/api/v1/storage/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const { url } = await uploadResponse.json(); // Bước 2: Gửi tin nhắn với URL ảnh const messageResponse = await fetch('/api/v1/messages', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ conversationId: 'conv-uuid-123', content: 'Check out this photo!', attachmentUrl: url, attachmentType: 'image' }) }); ``` ### 3.2. Get Messages from Conversation Lấy danh sách tin nhắn trong conversation (có phân trang). **Endpoint:** `GET /messages/conversation/:conversationId` **Query Parameters:** - `limit` (optional): Số lượng messages (default: 50) - `before` (optional): Lấy messages trước timestamp này (ISO 8601 format) **Example:** ``` GET /messages/conversation/conv-uuid-123?limit=20&before=2025-11-25T15:00:00.000Z ``` **Response:** `200 OK` ```json { "messages": [ { "id": "msg-uuid-456", "conversationId": "conv-uuid-123", "senderId": "user-uuid-1", "content": "Hello!", "attachmentUrl": null, "attachmentType": null, "isEdited": false, "isDeleted": false, "createdAt": "2025-11-25T15:45:00.000Z", "updatedAt": "2025-11-25T15:45:00.000Z", "readBy": [ { "userId": "user-uuid-2", "readAt": "2025-11-25T15:46:00.000Z" } ] } ], "hasMore": true, "nextBefore": "2025-11-25T15:40:00.000Z" } ``` **Pagination Example:** ```javascript // Load tin nhắn ban đầu let messages = []; let nextBefore = null; async function loadMessages(conversationId) { const url = nextBefore ? `/api/v1/messages/conversation/${conversationId}?limit=20&before=${nextBefore}` : `/api/v1/messages/conversation/${conversationId}?limit=20`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); messages = [...messages, ...data.messages]; nextBefore = data.nextBefore; return data.hasMore; // true nếu còn tin nhắn cũ hơn } // Load more khi scroll lên đầu if (await loadMessages('conv-uuid-123')) { console.log('Has more messages to load'); } ``` ### 3.3. Get Single Message Lấy chi tiết một tin nhắn. **Endpoint:** `GET /messages/:id` **Response:** `200 OK` ```json { "id": "msg-uuid-456", "conversationId": "conv-uuid-123", "senderId": "user-uuid-1", "content": "Hello!", "attachmentUrl": null, "attachmentType": null, "isEdited": false, "isDeleted": false, "createdAt": "2025-11-25T15:45:00.000Z", "updatedAt": "2025-11-25T15:45:00.000Z" } ``` ### 3.4. Update Message Chỉnh sửa tin nhắn (chỉ người gửi mới được sửa). **Endpoint:** `PUT /messages/:id` **Request Body:** ```json { "content": "Updated message content" } ``` **Response:** `200 OK` ```json { "id": "msg-uuid-456", "content": "Updated message content", "isEdited": true, "updatedAt": "2025-11-25T15:50:00.000Z" } ``` ### 3.5. Mark Message as Read Đánh dấu một tin nhắn đã đọc. **Endpoint:** `POST /messages/:id/read` **Response:** `200 OK` ```json { "message": "Message marked as read" } ``` ### 3.6. Mark All Messages in Conversation as Read Đánh dấu tất cả tin nhắn trong conversation đã đọc. **Endpoint:** `POST /messages/conversation/:conversationId/read` **Response:** `200 OK` ```json { "message": "All messages marked as read" } ``` ### 3.7. Get Unread Count Lấy số lượng tin nhắn chưa đọc trong conversation. **Endpoint:** `GET /messages/conversation/:conversationId/unread-count` **Response:** `200 OK` ```json { "conversationId": "conv-uuid-123", "unreadCount": 5 } ``` ### 3.8. Delete Message Xóa tin nhắn (chỉ người gửi mới được xóa). **Endpoint:** `DELETE /messages/:id` **Response:** `200 OK` ```json { "message": "Message deleted successfully" } ``` --- ## 4. WebSocket Events WebSocket được sử dụng cho real-time messaging. Kết nối qua Socket.IO. ### Connection ```javascript import io from 'socket.io-client'; const socket = io('https://talentlink.io.vn', { path: '/socket.io', auth: { token: 'your_jwt_token' } }); socket.on('connect', () => { console.log('Connected to messaging server'); }); ``` ### 4.1. Join Conversation Join vào conversation để nhận real-time updates. **Event:** `conversation:join` **Emit:** ```javascript socket.emit('conversation:join', { conversationId: 'conv-uuid-123' }); ``` **Response:** ```javascript socket.on('conversation:joined', (data) => { console.log('Joined conversation:', data.conversationId); }); ``` ### 4.2. Leave Conversation Rời khỏi conversation. **Event:** `conversation:leave` **Emit:** ```javascript socket.emit('conversation:leave', { conversationId: 'conv-uuid-123' }); ``` ### 4.3. Send Message (Real-time) Gửi tin nhắn qua WebSocket. **Event:** `message:send` **Emit:** ```javascript socket.emit('message:send', { conversationId: 'conv-uuid-123', content: 'Hello from WebSocket!', attachmentUrl: null, attachmentType: null }); ``` **Receive (all participants in conversation):** ```javascript socket.on('message:new', (message) => { console.log('New message:', message); // { // id: 'msg-uuid-456', // conversationId: 'conv-uuid-123', // senderId: 'user-uuid-1', // content: 'Hello from WebSocket!', // createdAt: '2025-11-25T15:55:00.000Z' // } }); ``` ### 4.4. Edit Message (Real-time) Chỉnh sửa tin nhắn qua WebSocket. **Event:** `message:edit` **Emit:** ```javascript socket.emit('message:edit', { messageId: 'msg-uuid-456', content: 'Updated content' }); ``` **Receive:** ```javascript socket.on('message:edited', (data) => { console.log('Message edited:', data); // { // messageId: 'msg-uuid-456', // content: 'Updated content', // isEdited: true, // updatedAt: '2025-11-25T16:00:00.000Z' // } }); ``` ### 4.5. Delete Message (Real-time) Xóa tin nhắn qua WebSocket. **Event:** `message:delete` **Emit:** ```javascript socket.emit('message:delete', { messageId: 'msg-uuid-456' }); ``` **Receive:** ```javascript socket.on('message:deleted', (data) => { console.log('Message deleted:', data.messageId); }); ``` ### 4.6. Mark as Read (Real-time) Đánh dấu tin nhắn đã đọc qua WebSocket. **Event:** `message:read` **Emit:** ```javascript socket.emit('message:read', { messageId: 'msg-uuid-456' }); ``` **Receive:** ```javascript socket.on('message:read', (data) => { console.log('Message read by:', data); // { // messageId: 'msg-uuid-456', // userId: 'user-uuid-2', // readAt: '2025-11-25T16:05:00.000Z' // } }); ``` ### 4.7. Typing Indicators Hiển thị khi ai đó đang typing. **Start Typing:** ```javascript socket.emit('typing:start', { conversationId: 'conv-uuid-123' }); ``` **Stop Typing:** ```javascript socket.emit('typing:stop', { conversationId: 'conv-uuid-123' }); ``` **Receive:** ```javascript socket.on('user:typing', (data) => { console.log(`${data.userId} is typing in ${data.conversationId}`); }); socket.on('user:stopped-typing', (data) => { console.log(`${data.userId} stopped typing`); }); ``` --- ## 5. Error Responses Tất cả errors đều có format sau: ```json { "error": "Error Type", "message": "Detailed error message", "statusCode": 400 } ``` ### Common Error Codes: - `400 Bad Request` - Invalid request body hoặc parameters - `401 Unauthorized` - Missing hoặc invalid authentication token - `403 Forbidden` - User không có quyền thực hiện action - `404 Not Found` - Resource không tồn tại - `500 Internal Server Error` - Server error ### Example Errors: **Missing Authentication:** ```json { "error": "Unauthorized", "message": "Authentication token is required", "statusCode": 401 } ``` **Invalid File Type:** ```json { "error": "Bad Request", "message": "Invalid file type. Only images, videos, audio, and documents are allowed", "statusCode": 400 } ``` **File Too Large:** ```json { "error": "Bad Request", "message": "File size exceeds 10MB", "statusCode": 400 } ``` --- ## 6. Complete Example: Chat Application Flow ### Initialize Connection ```javascript // 1. Setup Socket.IO connection const socket = io('https://talentlink.io.vn', { path: '/socket.io', auth: { token: authToken } }); // 2. Load conversations list const conversationsResponse = await fetch('/api/v1/conversations', { headers: { 'Authorization': `Bearer ${authToken}` } }); const conversations = await conversationsResponse.json(); // 3. Join a conversation socket.emit('conversation:join', { conversationId: conversations[0].id }); // 4. Load messages const messagesResponse = await fetch( `/api/v1/messages/conversation/${conversations[0].id}?limit=50`, { headers: { 'Authorization': `Bearer ${authToken}` }} ); const { messages } = await messagesResponse.json(); ``` ### Send Message with Image ```javascript async function sendImageMessage(conversationId, imageFile, text) { // Step 1: Upload image const formData = new FormData(); formData.append('file', imageFile); const uploadResponse = await fetch('/api/v1/storage/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}` }, body: formData }); const { url } = await uploadResponse.json(); // Step 2: Send message with image URL socket.emit('message:send', { conversationId, content: text, attachmentUrl: url, attachmentType: 'image' }); } ``` ### Listen for Real-time Updates ```javascript // New message received socket.on('message:new', (message) => { addMessageToUI(message); // Mark as read if conversation is active if (isConversationActive(message.conversationId)) { socket.emit('message:read', { messageId: message.id }); } }); // Message edited socket.on('message:edited', (data) => { updateMessageInUI(data.messageId, data.content); }); // Message deleted socket.on('message:deleted', (data) => { removeMessageFromUI(data.messageId); }); // Typing indicator socket.on('user:typing', (data) => { showTypingIndicator(data.userId, data.conversationId); }); socket.on('user:stopped-typing', (data) => { hideTypingIndicator(data.userId); }); ``` ### Handle Typing Indicator ```javascript let typingTimeout; function onUserTyping(conversationId) { socket.emit('typing:start', { conversationId }); clearTimeout(typingTimeout); typingTimeout = setTimeout(() => { socket.emit('typing:stop', { conversationId }); }, 3000); // Stop after 3s of inactivity } ``` --- ## 7. Best Practices ### 1. File Upload - Luôn validate file size ở client trước khi upload (max 10MB) - Show progress bar khi upload file lớn - Cache uploaded URLs để tránh upload lại cùng một file ### 2. Message Pagination - Load 20-50 messages ban đầu - Implement infinite scroll để load thêm messages cũ - Cache messages đã load để tránh refetch ### 3. Real-time Updates - Luôn join conversation trước khi hiển thị chat UI - Leave conversation khi user rời khỏi chat screen - Reconnect socket khi mất kết nối ### 4. Read Receipts - Mark messages as read ngay khi hiển thị trên màn hình - Batch multiple read receipts thành một request - Update UI immediately, không cần đợi server response ### 5. Error Handling - Implement retry logic cho failed uploads - Show user-friendly error messages - Cache failed messages để user có thể retry ### 6. Performance - Debounce typing indicators - Lazy load images và videos - Implement virtual scrolling cho long message lists --- ## 8. Testing ### Test với cURL **Upload file:** ```bash curl -X POST https://talentlink.io.vn/api/v1/storage/upload \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "file=@/path/to/image.jpg" ``` **Send message:** ```bash curl -X POST https://talentlink.io.vn/api/v1/messages \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "conv-uuid-123", "content": "Hello!", "attachmentUrl": null, "attachmentType": null }' ``` **Get messages:** ```bash curl https://talentlink.io.vn/api/v1/messages/conversation/conv-uuid-123?limit=20 \ -H "Authorization: Bearer YOUR_TOKEN" ``` --- ## 9. Swagger Documentation Interactive API documentation có sẵn tại: ``` http://localhost:8081/swagger ``` Swagger UI cho phép test tất cả endpoints directly từ browser.