Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions src/controllers/task.controller.js
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
36 changes: 26 additions & 10 deletions src/dtos/task.dto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}))
]
};
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/routes/task.route.js
Original file line number Diff line number Diff line change
@@ -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();

// 완료된 과제
Expand All @@ -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);
Expand Down
11 changes: 7 additions & 4 deletions src/services/task.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class TaskService {
}

// 과제 수정
async modifyTask(taskId, data) {
async modifyTask(taskId, data = {}) {
const { subTasks, references, folderId, ...taskData } = data;

// 과제 존재 여부 확인
Expand Down Expand Up @@ -205,9 +205,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 };
Expand Down
59 changes: 28 additions & 31 deletions src/swagger/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,11 @@ paths:
tags:
- Task
summary: 과제 수정
description: 과제의 기본 정보 및 세부 태스크를 수정합니다.
description: |
과제 정보와 여러 개의 파일을 수정합니다.
- **files**: 여러 개의 파일을 한꺼번에 선택할 수 있습니다.
- **fileNames**: 업로드한 파일 순서대로 이름을 쉽표(,)로 구분해서 적어주세요.
(예: `보고서최종, 참고자료1`)
security:
- bearerAuth: []
parameters:
Expand All @@ -1314,7 +1318,7 @@ paths:
requestBody:
required: true
content:
application/json:
multipart/form-data:
schema:
type: object
properties:
Expand All @@ -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: 과제 수정 성공
Expand Down Expand Up @@ -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: 관리자"
Expand Down