diff --git a/README.md b/README.md index 0f8dca8..7c618fe 100644 --- a/README.md +++ b/README.md @@ -174,25 +174,43 @@ npm start
## ๐Ÿ“ ์ฃผ์š” API ๋ชฉ๋ก + +### ๐Ÿ” OAuth Domain +| ๊ธฐ๋Šฅ๋ช… | Method | Endpoint | ์„ค๋ช… | +| :--- | :---: | :--- | :--- | +| ์นด์นด์˜ค ๋กœ๊ทธ์ธ | GET | `/api/v1/auth/kakao` | ์นด์นด์˜ค OAuth ๋กœ๊ทธ์ธ ์š”์ฒญ | +| Access Token ์žฌ๋ฐœ๊ธ‰ | POST | `/api/v1/auth/refresh` | Refresh Token ๊ธฐ๋ฐ˜ Access Token ์žฌ๋ฐœ๊ธ‰ | + +### ๐Ÿ“‹ Task Domain +| ๊ธฐ๋Šฅ๋ช… | Method | Endpoint | ์„ค๋ช… | +| :--- | :---: | :--- | :--- | +| ๊ณผ์ œ ์ƒ์„ฑ | POST | `/api/v1/task` | ๊ฐœ์ธ/ํŒ€ ๊ณผ์ œ ์ƒ์„ฑ | +| ๊ณผ์ œ ๋ชฉ๋ก ์กฐํšŒ | GET | `/api/v1/task` | ํ•„ํ„ฐ๋ง(๊ฐœ์ธ/ํŒ€/๋งˆ๊ฐ์ผ/์ง„์ฒ™๋„ ๋“ฑ) ๊ธฐ๋ฐ˜ ๊ณผ์ œ ์กฐํšŒ | +| ๊ณผ์ œ ์ƒ์„ธ์กฐํšŒ | GET | `/api/v1/task/{taskId}` | ๊ณผ์ œ + ์„ธ๋ถ€๊ณผ์ œ + ์ž๋ฃŒ + ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ํ†ตํ•ฉ ์กฐํšŒ | +| ๊ณผ์ œ ์ˆ˜์ • | PATCH | `/api/v1/task/{taskId}` | ๊ณผ์ œ ์ •๋ณด ์ˆ˜์ • | +| ๊ณผ์ œ ์‚ญ์ œ | DELETE | `/api/v1/task/{taskId}` | ๊ณผ์ œ ์‚ญ์ œ | +| ์„ธ๋ถ€ ๊ณผ์ œ ์ƒํƒœ ๋ณ€๊ฒฝ | PATCH | `/api/v1/task/subtask/{subTaskId}/status` | ์„ธ๋ถ€๊ณผ์ œ ์™„๋ฃŒ/์ง„ํ–‰ ์ƒํƒœ ๋ณ€๊ฒฝ | +| ํŒ€์› ์ดˆ๋Œ€ ๋งํฌ ์ƒ์„ฑ | POST | `/api/v1/task/{taskId}/invitation` | ํŒ€ ๊ณผ์ œ ์ดˆ๋Œ€ URL ์ƒ์„ฑ | + +### ๐Ÿ”” Alarm Domain +| ๊ธฐ๋Šฅ๋ช… | Method | Endpoint | ์„ค๋ช… | +| :--- | :---: | :--- | :--- | +| ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ | GET | `/api/v1/alarm` | ํ˜„์žฌ ์‚ฌ์šฉ์ž ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ | +| ์•Œ๋ฆผ ์ฝ๊ธฐ ์ฒ˜๋ฆฌ | PATCH | `/api/v1/alarm/{alarmId}` | ํŠน์ • ์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ | +| ์•Œ๋ฆผ ์ „์ฒด ์‚ญ์ œ | DELETE | `/api/v1/alarm/all` | ๋ชจ๋“  ์•Œ๋ฆผ ์‚ญ์ œ | +| ์•Œ๋ฆผ ์„ค์ • ๋ณ€๊ฒฝ | PATCH | `/api/v1/alarm/settings/task` | ์‚ฌ์šฉ์ž ์•Œ๋ฆผ ์„ค์ • ๋ณ€๊ฒฝ | + +### ๐Ÿ‘ค User Domain +| ๊ธฐ๋Šฅ๋ช… | Method | Endpoint | ์„ค๋ช… | +| :--- | :---: | :--- | :--- | +| ๋‚ด ์ •๋ณด ์กฐํšŒ | GET | `/api/v1/user/me` | ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ | +| ํ”„๋กœํ•„ ์ˆ˜์ • | PATCH | `/api/v1/user/profile` | ๋‹‰๋„ค์ž„ ๋“ฑ ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • | +| ํด๋” ์ƒ์„ฑ | POST | `/api/v1/user/folder` | ์‚ฌ์šฉ์ž ํด๋” ์ƒ์„ฑ | +| ํด๋” ์กฐํšŒ | GET | `/api/v1/user/folder` | ์‚ฌ์šฉ์ž ํด๋” ๋ชฉ๋ก ์กฐํšŒ | + +### ๐Ÿ“‚ Reference Domain | ๊ธฐ๋Šฅ๋ช… | Method | Endpoint | ์„ค๋ช… | -| --- | --- | --- | --- | -| ์นด์นด์˜ค ๋กœ๊ทธ์ธ | GET | /api/v1/auth/kakao | ์นด์นด์˜ค OAuth ๋กœ๊ทธ์ธ ์š”์ฒญ | -| Access Token ์žฌ๋ฐœ๊ธ‰ | POST | /api/v1/auth/refresh | Refresh Token ๊ธฐ๋ฐ˜ Access Token ์žฌ๋ฐœ๊ธ‰ | -| ๊ณผ์ œ ์ƒ์„ฑ | POST | /api/v1/task | ๊ฐœ์ธ/ํŒ€ ๊ณผ์ œ ์ƒ์„ฑ | -| ๊ณผ์ œ ๋ชฉ๋ก ์กฐํšŒ | GET | /api/v1/task | ํ•„ํ„ฐ๋ง(๊ฐœ์ธ/ํŒ€/๋งˆ๊ฐ์ผ/์ง„์ฒ™๋„ ๋“ฑ) ๊ธฐ๋ฐ˜ ๊ณผ์ œ ์กฐํšŒ | -| ๊ณผ์ œ ์ƒ์„ธ์กฐํšŒ | GET | /api/v1/task/{taskId} | ๊ณผ์ œ + ์„ธ๋ถ€๊ณผ์ œ + ์ž๋ฃŒ + ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ํ†ตํ•ฉ ์กฐํšŒ | -| ๊ณผ์ œ ์ˆ˜์ • | PATCH | /api/v1/task/{taskId} | ๊ณผ์ œ ์ •๋ณด ์ˆ˜์ • | -| ๊ณผ์ œ ์‚ญ์ œ | DELETE | /api/v1/task/{taskId} | ๊ณผ์ œ ์‚ญ์ œ | -| ์„ธ๋ถ€ ๊ณผ์ œ ์ƒํƒœ ๋ณ€๊ฒฝ | PATCH | /api/v1/task/subtask/{subTaskId}/status | ์„ธ๋ถ€๊ณผ์ œ ์™„๋ฃŒ/์ง„ํ–‰ ์ƒํƒœ ๋ณ€๊ฒฝ | -| ํŒ€์› ์ดˆ๋Œ€ ๋งํฌ ์ƒ์„ฑ | POST | /api/v1/task/{taskId}/invitation | ํŒ€ ๊ณผ์ œ ์ดˆ๋Œ€ URL ์ƒ์„ฑ | -| ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ | GET | /api/v1/alarm | ํ˜„์žฌ ์‚ฌ์šฉ์ž ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ | -| ์•Œ๋ฆผ ์ฝ๊ธฐ ์ฒ˜๋ฆฌ | PATCH | /api/v1/alarm/{alarmId} | ํŠน์ • ์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ | -| ์•Œ๋ฆผ ์ „์ฒด ์‚ญ์ œ | DELETE | /api/v1/alarm/all | ๋ชจ๋“  ์•Œ๋ฆผ ์‚ญ์ œ | -| ์•Œ๋ฆผ ์„ค์ • ๋ณ€๊ฒฝ | PATCH | /api/v1/alarm/settings/task | ์‚ฌ์šฉ์ž ์•Œ๋ฆผ ์„ค์ • ๋ณ€๊ฒฝ | -| ๋‚ด ์ •๋ณด ์กฐํšŒ | GET | /api/v1/user/me | ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ | -| ํ”„๋กœํ•„ ์ˆ˜์ • | PATCH | /api/v1/user/profile | ๋‹‰๋„ค์ž„ ๋“ฑ ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • | -| ํด๋” ์ƒ์„ฑ | POST | /api/v1/user/folder | ์‚ฌ์šฉ์ž ํด๋” ์ƒ์„ฑ | -| ํด๋” ์กฐํšŒ | GET | /api/v1/user/folder | ์‚ฌ์šฉ์ž ํด๋” ๋ชฉ๋ก ์กฐํšŒ | -| ์ž๋ฃŒ ์ถ”๊ฐ€ | POST | /api/v1/reference/data/{taskId} | URL ๋˜๋Š” ํŒŒ์ผ ์ž๋ฃŒ ์—…๋กœ๋“œ | -| ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ์ƒ์„ฑ | POST | /api/v1/reference/communication/{taskId} | ๊ณผ์ œ๋ณ„ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ์ƒ์„ฑ | -| ํšŒ์˜๋ก ์ƒ์„ฑ | POST | /api/v1/reference/log/{taskId} | ๊ณผ์ œ ํšŒ์˜๋ก ์ž‘์„ฑ | +| :--- | :---: | :--- | :--- | +| ์ž๋ฃŒ ์ถ”๊ฐ€ | POST | `/api/v1/reference/data/{taskId}` | URL ๋˜๋Š” ํŒŒ์ผ ์ž๋ฃŒ ์—…๋กœ๋“œ | +| ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ์ƒ์„ฑ | POST | `/api/v1/reference/communication/{taskId}` | ๊ณผ์ œ๋ณ„ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ์ƒ์„ฑ | +| ํšŒ์˜๋ก ์ƒ์„ฑ | POST | `/api/v1/reference/log/{taskId}` | ๊ณผ์ œ ํšŒ์˜๋ก ์ž‘์„ฑ | diff --git a/src/controllers/task.controller.js b/src/controllers/task.controller.js index d4dee2c..671c487 100644 --- a/src/controllers/task.controller.js +++ b/src/controllers/task.controller.js @@ -1,4 +1,5 @@ import taskService from "../services/task.service.js"; +import { uploadToS3 } from '../middlewares/upload.middleware.js'; import { TaskRequestDTO, TaskResponseDTO } from "../dtos/task.dto.js"; import { BadRequestError } from "../errors/custom.error.js"; @@ -40,21 +41,46 @@ class TaskController { // ๊ณผ์ œ ์ˆ˜์ • async updateTask(req, res, next) { - try { - const { taskId } = req.params; - const taskRequest = TaskRequestDTO.toUpdate(req.body); - - const result = await taskService.modifyTask(parseInt(taskId), taskRequest); + try { + const { taskId } = req.params; + + let customFileNames = []; + if (req.body.fileNames) { + const rawNames = req.body.fileNames; + if (typeof rawNames === 'string' && rawNames.startsWith('[')) { + customFileNames = JSON.parse(rawNames); + } else if (typeof rawNames === 'string') { + customFileNames = rawNames.split(',').map(name => name.trim()); + } else { + customFileNames = rawNames; + } + } - res.status(200).json({ - resultType: "SUCCESS", - message: "์š”์ฒญ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - data: result - }); - } catch (error) { - next(error); + let fileReferences = []; + if (req.files && req.files.length > 0) { + for (let i = 0; i < req.files.length; i++) { + const file = req.files[i]; + const fileUrl = await uploadToS3(file); + + fileReferences.push({ + name: (customFileNames && customFileNames[i]) ? customFileNames[i] : file.originalname, + fileUrl: fileUrl + }); + } } + + const taskRequest = TaskRequestDTO.toUpdate(req.body, fileReferences); + const result = await taskService.modifyTask(parseInt(taskId), taskRequest); + + res.status(200).json({ + resultType: "SUCCESS", + message: "๊ณผ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + data: result + }); + } catch (error) { + next(error); } +} // ๊ณผ์ œ ์‚ญ์ œ async deleteTask(req, res, next) { @@ -163,7 +189,8 @@ class TaskController { // ํŒ€์› ์ •๋ณด ์ˆ˜์ • (์—ญํ•  ๋ณ€๊ฒฝ) async updateTeamMember(req, res, next) { try { - const { taskId, memberId } = req.params; + const taskId = req.body.taskId || req.params.taskId; + const memberId = req.body.memberId || req.params.memberId; const { role } = req.body; const result = await taskService.modifyMemberRole( diff --git a/src/dtos/task.dto.js b/src/dtos/task.dto.js index 208a366..d5f774d 100644 --- a/src/dtos/task.dto.js +++ b/src/dtos/task.dto.js @@ -59,18 +59,34 @@ export class TaskRequestDTO { } // ๊ณผ์ œ ์ˆ˜์ • - static toUpdate(data) { + static toUpdate(data, uploadedFiles = []) { + const subTasks = typeof data.subTasks === 'string' ? JSON.parse(data.subTasks) : (data.subTasks || []); + const existingRefs = typeof data.references === 'string' ? JSON.parse(data.references) : (data.references || []); + return { title: data.title, - folderId: data.folderId, - deadline: data.deadline ? new Date(data.deadline) : undefined, - type: data.type === "TEAM" ? "TEAM" : (data.type === "PERSONAL" ? "PERSONAL" : undefined), - subTasks: (data.subTasks || []).map(st => ({ - title: st.title, - endDate: st.endDate ? new Date(st.endDate) : new Date(), - status: st.status || "PENDING" - })), - references: data.references || [] + folderId: data.folderId ? Number(data.folderId) : undefined, + deadline: data.deadline, + type: (data.type === "TEAM" || data.type === "ํŒ€") ? "TEAM" : "PERSONAL", + subTasks: subTasks + .filter(st => st !== null && st !== undefined) // ๋นˆ ๊ฐ์ฒด ์ œ๊ฑฐ + .map(st => ({ + title: st.title || "์ œ๋ชฉ ์—†์Œ", + endDate: st.endDate ? new Date(st.endDate) : new Date(), + status: st.status || "PENDING" + })), + references: [ + ...existingRefs.map(ref => ({ + name: ref.name, + url: ref.url || null, + fileUrl: ref.fileUrl || ref.file_url || null + })), + ...uploadedFiles.map(file => ({ + name: file.name, + url: null, + fileUrl: file.fileUrl || file.file_url + })) + ] }; } } diff --git a/src/routes/task.route.js b/src/routes/task.route.js index 8df3e53..ac87a9f 100644 --- a/src/routes/task.route.js +++ b/src/routes/task.route.js @@ -1,7 +1,12 @@ import express from "express"; +import multer from "multer"; + import taskController from "../controllers/task.controller.js"; import authenticate from "../middlewares/authenticate.middleware.js"; +const upload = multer({ storage: multer.memoryStorage() }); + + const router = express.Router(); // ์™„๋ฃŒ๋œ ๊ณผ์ œ @@ -14,7 +19,7 @@ router.post("/", authenticate, taskController.createTask); router.patch("/priority", authenticate, taskController.updateTaskPriorities); // PATCH /api/v1/task/:taskId -- ๊ณผ์ œ ์ˆ˜์ • -router.patch("/:taskId", authenticate, taskController.updateTask); +router.patch("/:taskId", authenticate, upload.array('files'), taskController.updateTask); // DELETE /api/v1/task/:taskId -- ๊ณผ์ œ ์‚ญ์ œ router.delete("/:taskId", authenticate, taskController.deleteTask); diff --git a/src/services/folder.service.js b/src/services/folder.service.js index 118b2e0..cf8b041 100644 --- a/src/services/folder.service.js +++ b/src/services/folder.service.js @@ -29,6 +29,10 @@ class FolderService { if (!folder) throw new NotFoundError("FOLDER_NOT_FOUND", "ํ•ด๋‹น ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); if (folder.userId !== userId) throw new ForbiddenError("FORBIDDEN", "์ˆ˜์ • ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค."); + if (folder.folderTitle === "ํŒ€") { + throw new BadRequestError("PROTECTED_FOLDER", "'ํŒ€' ํด๋”๋Š” ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + const updateData = FolderDto.updateBodyToFolderDto(body); if (Object.keys(updateData).length === 0) { @@ -55,6 +59,10 @@ class FolderService { if (!folder) throw new NotFoundError("FOLDER_NOT_FOUND", "ํ•ด๋‹น ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); if (folder.userId !== userId) throw new ForbiddenError("FORBIDDEN", "์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค."); + if (folder.folderTitle === "ํŒ€") { + throw new BadRequestError("PROTECTED_FOLDER", "'ํŒ€' ํด๋”๋Š” ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + await folderRepository.removeFolder(userId, folderId); } } diff --git a/src/services/task.service.js b/src/services/task.service.js index a323547..f504115 100644 --- a/src/services/task.service.js +++ b/src/services/task.service.js @@ -38,6 +38,37 @@ class TaskService { if (!folder) throw new NotFoundError("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํด๋”์ž…๋‹ˆ๋‹ค."); } + let targetFolderId = folderId; // ์ตœ์ข…์ ์œผ๋กœ ์ €์žฅ๋  ํด๋” ID + + if (taskData.type === 'TEAM') { + // 1. ํŒ€ ๊ณผ์ œ๋ผ๋ฉด ๋ฌด์กฐ๊ฑด 'ํŒ€' ํด๋”๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. + const teamFolder = await prisma.folder.findFirst({ + where: { + userId: userId, + folderTitle: "ํŒ€" + } + }); + + if (!teamFolder) { + throw new NotFoundError("ํŒ€ ๊ณผ์ œ ์ „์šฉ ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + + // 2. ๊ฐ•์ œ๋กœ 'ํŒ€' ํด๋” ID๋กœ + targetFolderId = teamFolder.id; + + } else if (folderId) { + // 3. ๊ฐœ์ธ ๊ณผ์ œ์ธ๋ฐ ํด๋”๋ฅผ ์„ ํƒํ•œ ๊ฒฝ์šฐ -> ์œ ํšจ์„ฑ ๋ฐ ๊ถŒํ•œ ๊ฒ€์‚ฌ + const folder = await taskRepository.findFolderById(folderId); + + if (!folder) { + throw new NotFoundError("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํด๋”์ž…๋‹ˆ๋‹ค."); + } + + if (folder.userId !== userId) { + throw new ForbiddenError("๊ถŒํ•œ์ด ์—†๋Š” ํด๋”์ž…๋‹ˆ๋‹ค."); + } + } + return await prisma.$transaction(async (tx) => { // ๊ณผ์ œ ์ƒ์„ฑ const newTask = await taskRepository.createTask({ ...taskData, folderId }, tx); @@ -139,7 +170,7 @@ class TaskService { } // ๊ณผ์ œ ์ˆ˜์ • - async modifyTask(taskId, data) { + async modifyTask(taskId, data = {}) { const { subTasks, references, folderId, ...taskData } = data; // ๊ณผ์ œ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ @@ -205,9 +236,12 @@ class TaskService { } // ์ž๋ฃŒ ๊ฐฑ์‹  - await taskRepository.deleteAllReferences(taskId, tx); - if (references?.length > 0) { - await taskRepository.addReferences(taskId, references, tx); + if (references) { + await taskRepository.deleteAllReferences(taskId, tx); + + if (references.length > 0) { + await taskRepository.addReferences(taskId, references, tx); + } } return { taskId: updatedTask.id }; diff --git a/src/swagger/swagger.yml b/src/swagger/swagger.yml index fb23420..47a87e5 100644 --- a/src/swagger/swagger.yml +++ b/src/swagger/swagger.yml @@ -1092,14 +1092,14 @@ paths: type: integer example: 13 '400': - description: ์ž˜๋ชป๋œ ์š”์ฒญ (ํ•„์ˆ˜ ๊ฐ’ ๋ˆ„๋ฝ ๋˜๋Š” ํ˜•์‹ ์˜ค๋ฅ˜) + description: ์ž˜๋ชป๋œ ์š”์ฒญ (ํ•„์ˆ˜ ๊ฐ’ ๋ˆ„๋ฝ ๋˜๋Š” ํ˜•์‹ ์˜ค๋ฅ˜, ํŒ€ ๊ณผ์ œ ํด๋” ๊ทœ์น™ ์œ„๋ฐ˜ ๋“ฑ) content: application/json: example: resultType: "FAIL" code: 400 - errorCode: "BAD_REQUEST" - reason: "๊ณผ์ œ๋ช…์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค." + errorCode: "BAD_REQUEST | TEAM_FOLDER_REQUIRED" + reason: "๊ณผ์ œ๋ช…์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. | ํŒ€ ๊ณผ์ œ๋Š” 'ํŒ€' ํด๋”์—๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." data: null '401': description: ์ธ์ฆ ์‹คํŒจ (ํ† ํฐ ๋งŒ๋ฃŒ ๋˜๋Š” ๋ˆ„๋ฝ) @@ -1302,7 +1302,11 @@ paths: tags: - Task summary: ๊ณผ์ œ ์ˆ˜์ • - description: ๊ณผ์ œ์˜ ๊ธฐ๋ณธ ์ •๋ณด ๋ฐ ์„ธ๋ถ€ ํƒœ์Šคํฌ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + description: | + ๊ณผ์ œ ์ •๋ณด์™€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + - **files**: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํŒŒ์ผ์„ ํ•œ๊บผ๋ฒˆ์— ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + - **fileNames**: ์—…๋กœ๋“œํ•œ ํŒŒ์ผ ์ˆœ์„œ๋Œ€๋กœ ์ด๋ฆ„์„ ์‰ฝํ‘œ(,)๋กœ ๊ตฌ๋ถ„ํ•ด์„œ ์ ์–ด์ฃผ์„ธ์š”. + (์˜ˆ: `๋ณด๊ณ ์„œ์ตœ์ข…, ์ฐธ๊ณ ์ž๋ฃŒ1`) security: - bearerAuth: [] parameters: @@ -1314,7 +1318,7 @@ paths: requestBody: required: true content: - application/json: + multipart/form-data: schema: type: object properties: @@ -1337,37 +1341,23 @@ paths: type: integer example: 2 subTasks: - type: array - items: - type: object - properties: - title: - type: string - example: "์„ธ๋ถ€๊ณผ์ œ 1" - endDate: - type: string - format: date - example: "2026-05-10" - status: - type: string - example: "COMPLETED" - isAlarm: - type: boolean - example: true - assigneeId: - type: integer - example: 5 + type: string + description: "์„ธ๋ถ€ ๊ณผ์ œ ๋ฐฐ์—ด (JSON string)" + example: '[{"title": "์„ธ๋ถ€๊ณผ์ œ 1", "endDate": "2026-05-10", "status": "COMPLETED", "isAlarm": true, "assigneeId": 5}]' references: - type: array + type: string + description: "๊ธฐ์กด ์œ ์ง€ํ•  ์ž๋ฃŒ ๋ชฉ๋ก (JSON string)" + example: '[{"name": "์ž๋ฃŒ1", "url": "https://mariadb.org/documentation/"}]' + fileNames: + type: string + description: "ํŒŒ์ผ ์›๋ณธ ์ด๋ฆ„ ๋Œ€์‹  ์ €์žฅํ•  ์ด๋ฆ„ (์‰ผํ‘œ ๊ตฌ๋ถ„)" + example: "๊ณผ์ œ๋ฐœํ‘œ์ž๋ฃŒ, ์ฐธ๊ณ ๋„๋ฉด" + files: + type: array items: - type: object - properties: - name: - type: string - example: "์ž๋ฃŒ1" - url: - type: string - example: "https://mariadb.org/documentation/" + type: string + format: binary + description: "์—…๋กœ๋“œํ•  ํŒŒ์ผ๋“ค์„ ๋ชจ๋‘ ์„ ํƒํ•˜์„ธ์š”." responses: '200': description: ๊ณผ์ œ ์ˆ˜์ • ์„ฑ๊ณต @@ -1764,7 +1754,14 @@ paths: application/json: schema: type: object + required: [taskId, memberId, role] properties: + taskId: + type: integer + example: 1 + memberId: + type: integer + example: 5 role: type: integer description: "0: ์ผ๋ฐ˜, 1: ๊ด€๋ฆฌ์ž" @@ -2817,6 +2814,7 @@ paths: - INVALID_FOLDER_TITLE: ํด๋” ์ด๋ฆ„์ด ๊ธธ ๋•Œ. - INVALID_COLOR: ์ƒ‰์ƒ ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Œ. - DUPLICATE_COLOR: ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์ƒ‰์ƒ์œผ๋กœ ๋ณ€๊ฒฝ ์‹œ๋„. + - PROTECTED_FOLDER: 'ํŒ€' ํด๋”๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•จ. content: application/json: schema: @@ -2824,8 +2822,8 @@ paths: example: resultType: "FAIL" code: 400 - errorCode: "BAD_REQUEST | INVALID_FOLDER_TITLE | INVALID_COLOR | DUPLICATE_COLOR" - reason: "์ธ์ฆ ํ† ํฐ์ด ํ˜•์‹์— ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค. | ํด๋” ์ด๋ฆ„์€ ์ตœ๋Œ€ 11์ž๊นŒ์ง€๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. | ์ƒ‰์ƒ ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. | ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค." + errorCode: "BAD_REQUEST | INVALID_FOLDER_TITLE | INVALID_COLOR | DUPLICATE_COLOR | PROTECTED_FOLDER" + reason: "์ธ์ฆ ํ† ํฐ์ด ํ˜•์‹์— ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค. | ํด๋” ์ด๋ฆ„์€ ์ตœ๋Œ€ 11์ž๊นŒ์ง€๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. | ์ƒ‰์ƒ ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. | ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค. | 'ํŒ€' ํด๋”๋Š” ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." data: null '401': description: | @@ -2913,6 +2911,18 @@ paths: type: object nullable: true example: null + '400': + description: ์ž˜๋ชป๋œ ์š”์ฒญ (๋ณดํ˜ธ๋œ ํด๋” ์‚ญ์ œ ์‹œ๋„) + content: + application/json: + schema: + type: object + example: + resultType: "FAIL" + code: 400 + errorCode: "PROTECTED_FOLDER" + reason: "'ํŒ€' ํด๋”๋Š” ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + data: null '401': description: | - TOKEN_NOT_FOUND: ์ธ์ฆ ํ† ํฐ์ด ์—†์Œ. diff --git a/test-socket.js b/test-socket.js deleted file mode 100644 index eb0e7c6..0000000 --- a/test-socket.js +++ /dev/null @@ -1,62 +0,0 @@ -import { io } from "socket.io-client"; -/* -const socket = io("http://localhost:8000", { - path: "/socket.io/" -}); - -socket.on("connect", () => { - console.log("โœ… connected:", socket.id); -}); - -socket.on("disconnect", () => { - console.log("โŒ disconnected"); -}); -*/ -//const io = require('socket.io-client'); -//import { io } from 'socket.io-client'; - - - - - - -const socket = io('http://localhost:8000', { - path: '/socket.io/', - transports: ['websocket'] -}); - -// ์—ฐ๊ฒฐ ์ด๋ฒคํŠธ -socket.on('connect', () => { - console.log('โœ… ์—ฐ๊ฒฐ ์„ฑ๊ณต! Socket ID:', socket.id); - - // ํ…Œ์ŠคํŠธ ๋ฐฉ ์ฐธ๊ฐ€ - socket.emit('joinTaskRoom', '1'); - - // 2์ดˆ ํ›„์— ์ƒํƒœ ์—…๋ฐ์ดํŠธ - setTimeout(() => { - console.log('\n๐Ÿ”„ ์„œ๋ธŒํƒœ์Šคํฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹œ๋„...'); - socket.emit('updateSubtaskStatus', { - taskId: 2, - subTaskId: 2, - status: 'COMPLETED' - }, (response) => { - console.log('์„œ๋ฒ„ ์‘๋‹ต:', response); - }); - }, 2000); -}); - -// ์ด๋ฒคํŠธ ์ˆ˜์‹  -socket.on('subtaskStatusUpdated', (data) => { - console.log('\n๐Ÿ“ฉ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์ˆ˜์‹ :', data); -}); - -// ์—๋Ÿฌ ์ฒ˜๋ฆฌ -socket.on('connect_error', (error) => { - console.error('์—ฐ๊ฒฐ ์˜ค๋ฅ˜:', error); -}); - -// ์ข…๋ฃŒ ์ฒ˜๋ฆฌ -process.on('SIGINT', () => { - socket.disconnect(); - process.exit(); -}); \ No newline at end of file