# API Documentation — Spaces & Rooms > Tài liệu dành cho Frontend để tích hợp API Spaces và Rooms. --- ## Base URL ``` /api ``` ## Authentication Tất cả endpoints yêu cầu header: ```http Authorization: Bearer Content-Type: application/json ``` --- ## 1. Spaces API (`/spaces`) ### 1.1 Lấy danh sách spaces của user ```http GET /spaces ``` **Response:** ```json { "success": true, "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lớp Toán 12A", "description": "Không gian học tập lớp 12A", "icon_url": "https://...", "owner_id": "user-uuid", "is_private": true, "invite_code": "abc123", "created_at": "2026-05-01T10:00:00Z", "updated_at": "2026-05-01T10:00:00Z" } ] } ``` --- ### 1.2 Tạo space mới ```http POST /spaces ``` **Request Body:** ```json { "name": "Lớp Toán 12A", "description": "Không gian học tập", "icon": "https://...", "isPrivate": true } ``` | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `name` | string | ✅ | Min 2, Max 100 chars | | `description` | string | ❌ | Max 500 chars | | `icon` | string (URL) | ❌ | — | | `isPrivate` | boolean | ❌ | Default: `false` | **Response:** ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lớp Toán 12A", "description": "Không gian học tập", "icon_url": "https://...", "owner_id": "user-uuid", "is_private": true, "invite_code": "abc123", "created_at": "2026-05-01T10:00:00Z" } } ``` --- ### 1.3 Lấy chi tiết space ```http GET /spaces/:spaceId ``` **Params:** - `spaceId` (UUID) — ID của space **Response:** ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lớp Toán 12A", "description": "Không gian học tập", "icon_url": "https://...", "owner_id": "user-uuid", "is_private": true, "invite_code": "abc123", "created_at": "2026-05-01T10:00:00Z" } } ``` **Lỗi:** - `403 Forbidden` — User không phải member của space private - `404 Not Found` — Space không tồn tại --- ### 1.4 Cập nhật space ```http PATCH /spaces/:spaceId ``` **Request Body:** ```json { "name": "Lớp Toán 12A (Updated)", "description": "Mô tả mới", "icon": "https://...", "isPrivate": false } ``` > **Lưu ý:** Chỉ owner hoặc admin mới có quyền cập nhật. Tất cả fields optional. **Response:** ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lớp Toán 12A (Updated)", ... } } ``` --- ### 1.5 Xóa space ```http DELETE /spaces/:spaceId ``` > **Lưu ý:** Chỉ owner mới có quyền xóa. Trả về `204 No Content`. --- ### 1.6 Tìm kiếm spaces công khai ```http GET /spaces/search?q=toán ``` **Query Params:** - `q` (string) — Từ khóa tìm kiếm (tối đa 100 ký tự) **Response:** ```json { "success": true, "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lớp Toán 12A", "description": "...", "is_private": false } ] } ``` --- ### 1.7 Lấy danh sách rooms trong space ```http GET /spaces/:spaceId/rooms ``` **Response:** ```json { "success": true, "data": [ { "id": "room-uuid-1", "space_id": "space-uuid", "name": "Thông báo", "description": "Kênh thông báo chính", "type": "text", "is_private": false, "created_at": "2026-05-01T10:00:00Z" }, { "id": "room-uuid-2", "space_id": "space-uuid", "name": "Tài liệu", "description": "Chia sẻ tài liệu", "type": "text", "is_private": false, "created_at": "2026-05-01T10:00:00Z" } ] } ``` --- ### 1.8 Thêm member vào space ```http POST /spaces/:spaceId/members ``` **Request Body:** ```json { "userId": "550e8400-e29b-41d4-a716-446655440000", "role": "member" } ``` | Field | Type | Required | Values | |-------|------|----------|--------| | `userId` | UUID | ✅ | — | | `role` | string | ❌ | `"member"` (default), `"admin"` | > **Lưu ý:** Chỉ owner hoặc admin mới có quyền thêm member. **Response:** ```json { "success": true, "data": { "id": "member-id", "space_id": "space-uuid", "user_id": "user-uuid", "role": "member", "joined_at": "2026-05-01T10:00:00Z" } } ``` --- ### 1.9 Tạo mã mờispace ```http POST /spaces/:spaceId/invite ``` > **Lưu ý:** Chỉ owner hoặc admin mới có quyền tạo mã mờ. **Response:** ```json { "success": true, "data": { "inviteCode": "abc123" } } ``` --- ### 1.10 Tham gia space bằng mã mờ ```http POST /spaces/join/:code ``` **Params:** - `code` (string) — Mã mờ **Response:** ```json { "success": true, "data": { "id": "space-uuid", "name": "Lớp Toán 12A", ... } } ``` --- ### 1.11 Rờikhỏi space ```http POST /spaces/:spaceId/leave ``` > **Lưu ý:** Owner không thể rờikhỏi space. Trả về `204 No Content`. --- ## 2. Rooms API ### 2.1 Tạo room trong space ```http POST /spaces/:spaceId/rooms ``` **Request Body:** ```json { "name": "Thảo luận", "description": "Phòng thảo luận bài tập", "type": "text", "isPrivate": false } ``` | Field | Type | Required | Values | Default | |-------|------|----------|--------|---------| | `name` | string | ✅ | Min 2, Max 100 chars | — | | `description` | string | ❌ | Max 500 chars | — | | `type` | string | ❌ | `"text"`, `"voice"` | `"text"` | | `isPrivate` | boolean | ❌ | — | `false` | > **Lưu ý:** Chỉ space member mới có quyền tạo room. **Response:** ```json { "success": true, "data": { "id": "room-uuid", "space_id": "space-uuid", "name": "Thảo luận", "description": "Phòng thảo luận bài tập", "type": "text", "is_private": false, "created_at": "2026-05-01T10:00:00Z" } } ``` --- ### 2.2 Lấy chi tiết room ```http GET /rooms/:roomId ``` **Response:** ```json { "success": true, "data": { "id": "room-uuid", "space_id": "space-uuid", "name": "Thảo luận", "description": "Phòng thảo luận bài tập", "type": "text", "is_private": false, "created_at": "2026-05-01T10:00:00Z" } } ``` **Lỗi:** - `403 Forbidden` — User không phải member của room --- ### 2.3 Cập nhật room ```http PATCH /rooms/:roomId ``` **Request Body:** ```json { "name": "Thảo luận (Updated)", "description": "Mô tả mới", "type": "text", "isPrivate": true } ``` > **Lưu ý:** Chỉ room creator hoặc space admin mới có quyền cập nhật. Tất cả fields optional. **Response:** ```json { "success": true, "data": { "id": "room-uuid", "name": "Thảo luận (Updated)", ... } } ``` --- ### 2.4 Xóa room ```http DELETE /rooms/:roomId ``` > **Lưu ý:** Chỉ room creator hoặc space admin mới có quyền xóa. **Response:** ```json { "success": true, "message": "Room deleted successfully" } ``` --- ### 2.5 Lấy danh sách rooms trong space ```http GET /spaces/:spaceId/rooms ``` > Cùng chức năng với **1.7**, trả về danh sách rooms trong space. --- ### 2.6 Lấy danh sách members trong room ```http GET /rooms/:roomId/members ``` **Response:** ```json { "success": true, "data": [ "user-id-1", "user-id-2", "user-id-3" ] } ``` --- ### 2.7 Thêm member vào room ```http POST /rooms/:roomId/members ``` **Request Body:** ```json { "userId": "550e8400-e29b-41d4-a716-446655440000" } ``` | Field | Type | Required | |-------|------|----------| | `userId` | UUID | ✅ | > **Lưu ý:** > - User phải là member của space trước > - Chỉ room creator hoặc space admin mới có quyền thêm **Response:** ```json { "success": true, "message": "Member added successfully" } ``` --- ### 2.8 Xóa member khỏi room ```http DELETE /rooms/:roomId/members/:userId ``` > **Lưu ý:** Chỉ room creator hoặc space admin mới có quyền xóa. **Response:** ```json { "success": true, "message": "Member removed successfully" } ``` --- ### 2.9 Lấy thống kê room ```http GET /rooms/:roomId/stats ``` **Response:** ```json { "success": true, "data": { "memberCount": 25, "messageCount": 1200, "lastActivity": "2026-05-04T10:00:00Z" } } ``` --- ## 3. Members API (`/spaces/:spaceId/members`) ### 3.1 Lấy danh sách members trong space ```http GET /spaces/:spaceId/members ``` **Response:** ```json { "success": true, "data": [ { "id": "user-id-1", "email": "alice@example.com", "username": "alice", "displayName": "Alice", "avatar": "https://...", "status": "online", "role": "owner", "joinedAt": "2026-05-01T10:00:00Z" }, { "id": "user-id-2", "email": "bob@example.com", "username": "bob", "displayName": "Bob", "avatar": "https://...", "status": "offline", "role": "member", "joinedAt": "2026-05-02T10:00:00Z" } ] } ``` --- ### 3.2 Tìm kiếm members trong space ```http GET /spaces/:spaceId/members/search?q=alice ``` **Query Params:** - `q` (string) — Từ khóa tìm kiếm username/display_name/email **Response:** ```json { "success": true, "data": [ { "id": "user-id-1", "email": "alice@example.com", "username": "alice", "displayName": "Alice", "avatar": "https://...", "status": "online", "role": "owner", "joinedAt": "2026-05-01T10:00:00Z" } ] } ``` --- ### 3.3 Lấy role của member ```http GET /spaces/:spaceId/members/:userId/role ``` **Response:** ```json { "success": true, "data": { "role": "admin" } } ``` --- ### 3.4 Cập nhật role member ```http PATCH /spaces/:spaceId/members/:userId/role ``` **Request Body:** ```json { "role": "admin" } ``` | Field | Type | Required | Values | |-------|------|----------|--------| | `role` | string | ✅ | `"member"`, `"admin"` | > **Lưu ý:** > - Chỉ owner mới có quyền promote lên admin > - Không thể thay đổi role của owner **Response:** ```json { "success": true, "data": { "id": "member-id", "space_id": "space-uuid", "user_id": "user-uuid", "role": "admin", "joined_at": "2026-05-01T10:00:00Z" } } ``` --- ### 3.5 Lấy activity của member ```http GET /spaces/:spaceId/members/:userId/activity ``` **Response:** ```json { "success": true, "data": { "lastActive": "2026-05-04T10:00:00Z", "messageCount": 150, "reactionCount": 45 } } ``` --- ### 3.6 Xóa member khỏi space ```http DELETE /spaces/:spaceId/members/:userId ``` > **Lưu ý:** > - Chỉ owner hoặc admin mới có quyền xóa > - Admin không thể xóa owner > - Owner không thể bị xóa > - Member có thể tự xóa chính mình (leave) **Response:** ```json { "success": true, "message": "Member removed successfully" } ``` --- ## 4. Error Responses ### Format lỗi chuẩn ```json { "success": false, "message": "Error description", "error": "ERROR_CODE" } ``` ### Các mã lỗi phổ biến | Status | Code | Mô tả | |--------|------|-------| | `400` | `BAD_REQUEST` | Request không hợp lệ (validation error) | | `401` | `UNAUTHORIZED` | Token hết hạn hoặc không hợp lệ | | `403` | `FORBIDDEN` | Không có quyền thực hiện | | `404` | `NOT_FOUND` | Resource không tồn tại | | `409` | `CONFLICT` | Conflict (đã tồn tại, v.v.) | | `429` | `RATE_LIMIT` | Quá nhiều requests | ### Ví dụ lỗi 403 ```json { "statusCode": 403, "message": "You are not a member of this space", "error": "Forbidden" } ``` ### Ví dụ lỗi 400 (Validation) ```json { "statusCode": 400, "message": ["name must be longer than or equal to 2 characters"], "error": "Bad Request" } ``` --- ## 5. DTO Reference ### CreateSpaceDto ```typescript { name: string; // required, min 2, max 100 chars description?: string; // optional, max 500 chars icon?: string; // optional, URL isPrivate?: boolean; // optional, default: false } ``` ### UpdateSpaceDto ```typescript { name?: string; // optional, min 2, max 100 chars description?: string; // optional, max 500 chars icon?: string; // optional, URL isPrivate?: boolean; // optional } ``` ### CreateRoomDto ```typescript { name: string; // required, min 2, max 100 chars description?: string; // optional, max 500 chars type?: string; // optional, default: "text" ("text" | "voice") isPrivate?: boolean; // optional, default: false } ``` ### UpdateRoomDto ```typescript { name?: string; description?: string; type?: string; // "text" | "voice" isPrivate?: boolean; } ``` ### AddMemberDto ```typescript { userId: string; // required, UUID role?: string; // optional, default: "member" ("member" | "admin") } ``` ### AddRoomMemberDto ```typescript { userId: string; // required, UUID } ``` ### UpdateRoleDto ```typescript { role: string; // required, "member" | "admin" } ``` --- ## 6. Flow gợi ý cho Frontend ### Tạo space mới ```typescript async function createSpace(name: string, description?: string) { const response = await fetch('/api/spaces', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name, description, isPrivate: false }), }); return response.json(); } ``` ### Lấy danh sách spaces và rooms ```typescript async function loadSpacesAndRooms() { // 1. Lấy danh sách spaces const spacesRes = await fetch('/api/spaces', { headers: { 'Authorization': `Bearer ${token}` }, }); const { data: spaces } = await spacesRes.json(); // 2. Với mỗi space, lấy rooms for (const space of spaces) { const roomsRes = await fetch(`/api/spaces/${space.id}/rooms`, { headers: { 'Authorization': `Bearer ${token}` }, }); const { data: rooms } = await roomsRes.json(); space.rooms = rooms; } return spaces; } ``` ### Tham gia room qua WebSocket ```typescript import { io } from 'socket.io-client'; const socket = io('wss://api.example.com/chat', { auth: { token: accessToken }, }); // Join room socket.emit('joinRoom', { roomId: 'room-uuid' }); // Listen events socket.on('joinedRoom', (data) => { console.log('Joined room:', data); }); socket.on('newMessage', (message) => { console.log('New message:', message); }); ``` ### Thêm member vào space ```typescript async function addMember(spaceId: string, userId: string, role: string = 'member') { const response = await fetch(`/api/spaces/${spaceId}/members`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ userId, role }), }); return response.json(); } ``` --- ## 7. Permissions Summary | Action | Who can do it | |--------|---------------| | Create space | Any authenticated user | | Update space | Owner, Admin | | Delete space | Owner only | | Add member | Owner, Admin | | Remove member | Owner, Admin (except owner) | | Update member role | Owner only | | Create room | Space member | | Update room | Room creator, Space admin | | Delete room | Room creator, Space admin | | Add room member | Room creator, Space admin | | Remove room member | Room creator, Space admin | | Generate invite code | Owner, Admin | | Join by invite code | Any authenticated user | | Leave space | Member (not owner) | --- ## 8. Cache Notes | Endpoint | Cache | |----------|-------| | `GET /spaces/:spaceId/rooms` | `private, max-age=30` | | `GET /rooms/:roomId/members` | Redis cache 5 phút | | `GET /spaces/:spaceId/members` | Redis cache 5 phút | Frontend nên implement stale-while-revalidate cho danh sách spaces/rooms.