Skip to content

[Feat/#589] "세부 과제 다중 담당자 할당 가능" - #593

Merged
songwol38 merged 6 commits into
developfrom
feature/issue-589
Aug 8, 2026
Merged

songwol38 merged 6 commits into
developfrom
feature/issue-589

Conversation

@junu999

@junu999 junu999 commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

📌 관련 이슈번호

📌 PR 유형

어떤 변경 사항이 있나요?

  • 새 기능 추가
  • 버그 수정
  • 리팩토링

📌 PR 요약

세부 과제 담당자를 단일(1명)에서 다중(배열)으로 할당할 수 있도록 수정.

📌 작업 세부 내용

  1. Request Body 파라미터를 assigneeId(단일)에서 assigneeIds(배열, userId 기준)로 변경
  2. assigneeIds에 빈 배열([]) 전달 시 담당자가 0명으로 처리

📸 스크린샷 (선택)

image

🔗 참고 자료

Summary by CodeRabbit

새로운 기능

  • 하위 작업에 여러 담당자를 지정할 수 있습니다.
  • 담당자별 알림 생성 및 삭제를 지원합니다.
  • 하위 작업 상세와 담당자 변경 결과가 담당자 목록으로 제공됩니다.
  • 담당자 지정 API와 실시간 업데이트가 다중 담당자 입력을 지원합니다.
  • 기존 하위 작업의 담당자 정보가 새로운 방식으로 자동 이전됩니다.

개선 사항

  • 담당자 ID 입력값 검증이 강화되었습니다.
  • 담당자가 없는 하위 작업도 안정적으로 처리됩니다.
  • 담당자 삭제 시 관련 지정 정보가 자동으로 정리됩니다.

@junu999 junu999 self-assigned this Jul 30, 2026
@junu999 junu999 linked an issue Jul 30, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fee61927-358b-4685-8c6a-c1ccd251c429

📥 Commits

Reviewing files that changed from the base of the PR and between 7765c2c and 48c03ce.

📒 Files selected for processing (1)
  • src/helpers/task.helper.js

📝 Walkthrough

Walkthrough

세부 작업의 담당자 저장 구조를 SubTaskAssignee 조인 모델 기반의 다중 관계로 변경했습니다. 생성·수정·조회·삭제·알림·HTTP·소켓 흐름이 다중 담당자 배열을 처리하도록 갱신되었습니다.

Changes

다중 담당자 세부 작업

Layer / File(s) Summary
담당자 데이터 및 API 계약
prisma/schema.prisma, prisma/migrations/..., src/validators/task.schema.js, src/dtos/task.dto.js, src/helpers/task.helper.js, src/swagger/swagger.yml
SubTaskAssignee 조인 모델과 외래 키를 추가했습니다. 입력 검증과 DTO는 assigneeId 및 assigneeIds를 처리합니다. API 응답은 assignees 배열을 사용합니다.
담당자 저장 및 정리
prisma/seed.js, src/repositories/task.repository.js, src/services/member.service.js, src/repositories/alarm.repository.js
시드와 저장소가 담당자 조인 레코드를 생성·조회·교체합니다. 멤버 삭제 시 조인 레코드를 삭제합니다. 알람 상태 반환에서 단일 담당자 필드를 제거했습니다.
작업 담당자 처리 흐름
src/services/task.service.js
담당자 ID를 검증하고 작업 멤버 ID로 변환합니다. 생성·변경·일괄 수정에서 다중 담당자와 담당자별 알림을 처리합니다.
담당자별 알림 및 전송 흐름
src/services/alarm.service.js, src/controllers/task.controller.js, src/socket/handlers/task.handler.js
알림 서비스가 담당자별 설정으로 알림을 생성합니다. HTTP와 소켓 요청은 단일·다중 담당자 입력을 전달합니다. 응답과 방송은 assignees 배열을 사용합니다.
마감일 및 알림 동작 검증
tests/unit/subtask-enddate-nullable.test.js
테스트 목과 검증을 subTaskAssignees 구조에 맞게 갱신했습니다. 담당자별 알림 생성과 세부 작업 알림 일괄 삭제를 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TaskController
  participant TaskService
  participant TaskRepository
  participant AlarmService
  Client->>TaskController: assigneeIds 전송
  TaskController->>TaskService: setSubTaskAssignee 호출
  TaskService->>TaskRepository: 담당자 매핑 교체
  TaskService->>AlarmService: 담당자별 알림 생성
  TaskService-->>TaskController: assignees 배열 반환
  TaskController-->>Client: 갱신된 세부 작업 응답
Loading

Suggested reviewers: jungmini-developer

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 세부 과제에 여러 담당자를 할당하는 핵심 변경을 명확하게 설명합니다.
Linked Issues check ✅ Passed 코드 변경은 이슈 #589의 다중 담당자 할당 요구사항을 스키마, API, 서비스, 알림 및 테스트에 반영합니다.
Out of Scope Changes check ✅ Passed 변경 사항은 이슈 #589의 다중 담당자 할당 기능 구현과 관련된 범위에 포함됩니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-589

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/validators/task.schema.js (1)

176-183: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Both assignee fields optional means an empty body clears all assignees.

With assigneeId and assigneeIds both optional, PATCH .../assignee with {} validates and setSubTaskAssignee resolves to an empty list, replacing all existing assignees with none (src/services/task.service.js lines 762-802). Consider requiring at least one of the two fields so accidental empty payloads can't wipe assignments.

♻️ Suggested guard
-    assigneeIds: z.array(z.coerce.number().int().positive()).optional(),
-  }),
+    assigneeIds: z.array(z.coerce.number().int().positive()).optional(),
+  }).refine(
+    (body) => body.assigneeId !== undefined || body.assigneeIds !== undefined,
+    { error: "assigneeIds 또는 assigneeId 중 하나는 필수입니다." },
+  ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/task.schema.js` around lines 176 - 183, Update the assignee
validation schema containing assigneeId and assigneeIds to require at least one
field, rejecting an empty object before setSubTaskAssignee can resolve it to an
empty assignment list. Preserve the existing coercion and validation rules when
either field is provided.
src/services/alarm.service.js (2)

277-320: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

isAlarm is persisted before the assignee check — inconsistent state on failure.

updateSubtaskAlarmStatus (line 277-280) writes isAlarm to the DB, and only afterward (line 294-300) does the code validate that assignees exist, throwing SUBTASK_NO_ASSIGNEE if not. Since subTask.subTaskAssignees is already fetched pre-update, this validation can (and should) run before the persistence call. As written, a request to enable alarms on an unassigned subtask fails the API call but leaves isAlarm = true permanently persisted with zero alarms created — a state that cannot be self-corrected by retrying the same request.

Separately, deleteAlarmsBySubTaskIds followed by a per-assignee loop of createSubTaskAlarm calls (lines 302-320) is not wrapped in a transaction. If any iteration fails partway, some assignees end up with alarms and others don't, with no rollback.

🐛 Proposed fix: validate assignees before persisting the status change
-    const updatedSubTask = await this.alarmRepository.updateSubtaskAlarmStatus(
-      subTaskId,
-      isAlarm,
-    );
-
-    if (!updatedSubTask) {
-      throw new NotFoundError(
-        "SUBTASK_UPDATE_FAILED",
-        "세부과제 업데이트에 실패했습니다.",
-      );
-    }
-
-    const assignments = subTask.subTaskAssignees || [];
-    const notifyAssignments = assignments.filter(
-      (assignment) => assignment.member?.user?.id != null,
-    );
-
-    if (updatedSubTask.isAlarm === true) {
-      if (notifyAssignments.length === 0) {
-        throw new BadRequestError(
-          "SUBTASK_NO_ASSIGNEE",
-          "담당자가 지정된 세부과제만 알림을 켤 수 있습니다.",
-        );
-      }
-
+    const assignments = subTask.subTaskAssignees || [];
+    const notifyAssignments = assignments.filter(
+      (assignment) => assignment.member?.user?.id != null,
+    );
+
+    if (isAlarm === true && notifyAssignments.length === 0) {
+      throw new BadRequestError(
+        "SUBTASK_NO_ASSIGNEE",
+        "담당자가 지정된 세부과제만 알림을 켤 수 있습니다.",
+      );
+    }
+
+    const updatedSubTask = await this.alarmRepository.updateSubtaskAlarmStatus(
+      subTaskId,
+      isAlarm,
+    );
+
+    if (!updatedSubTask) {
+      throw new NotFoundError(
+        "SUBTASK_UPDATE_FAILED",
+        "세부과제 업데이트에 실패했습니다.",
+      );
+    }
+
+    if (updatedSubTask.isAlarm === true) {
       await this.alarmRepository.deleteAlarmsBySubTaskIds([updatedSubTask.id]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/alarm.service.js` around lines 277 - 320, Move the
SUBTASK_NO_ASSIGNEE validation using subTask.subTaskAssignees ahead of
updateSubtaskAlarmStatus so enabling alarms cannot persist an invalid status.
Keep the existing notification creation flow, but execute the status update,
deleteAlarmsBySubTaskIds, and createSubTaskAlarm operations within the service’s
existing transaction mechanism so partial alarm creation rolls back on failure.

302-320: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wrap the alarm rebuild in a transaction. Pass a shared tx through deleteAlarmsBySubTaskIds and each createSubTaskAlarm call here; a failure mid-loop leaves the alarm set partially updated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/alarm.service.js` around lines 302 - 320, Wrap the alarm
deletion and rebuild sequence in a shared transaction, passing its tx context to
deleteAlarmsBySubTaskIds and every createSubTaskAlarm call in the
notifyAssignments loop. Ensure the transaction commits only after all alarm
creations succeed and rolls back on any failure, preventing a partially updated
alarm set.
src/services/task.service.js (1)

716-721: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Thrown errors are missing reason.

These handler-bound errors set statusCode/errorCode but no reason, so the global middleware has no message field to serialize. Prefer the existing BadRequestError/NotFoundError classes, which populate all required fields.

As per coding guidelines, "Thrown errors handled by global middleware must include statusCode, errorCode, reason, and optional data."

Also applies to: 771-778

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 716 - 721, Replace the manually
constructed errors in the missing-user checks near the current block and the
additional occurrence around the referenced later branch with the existing
NotFoundError or BadRequestError class, preserving the 404/USER_NOT_FOUND
semantics and Korean message. Ensure the thrown handler-bound error exposes
statusCode, errorCode, and reason through the shared error class.

Source: Coding guidelines

🧹 Nitpick comments (5)
src/services/member.service.js (1)

77-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the join-table cleanup behind the repository layer.

The service reaches into tx.subTaskAssignee directly; a taskRepository.deleteSubTaskAssigneesByMemberId(memberId, tx) keeps data access in the repository layer consistent with the rest of this transaction (alarmRepository, taskRepository).

As per coding guidelines, "Use repositories as the data-access abstraction layer" and "Follow the Controller → Service → Repository flow for new API behavior."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/member.service.js` around lines 77 - 79, Replace the direct
tx.subTaskAssignee.deleteMany call in the member service with
taskRepository.deleteSubTaskAssigneesByMemberId(requestingMember.id, tx), and
add or update that repository method to perform the join-table cleanup while
preserving the transaction context.

Source: Coding guidelines

src/repositories/task.repository.js (1)

299-348: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the repeated assignee include graph.

The same subTaskAssignees → member → user → alarmSetting include is duplicated in findSubTaskById, createSubTask, and updateSubTask (and partially in findTaskDetail). A shared constant reduces drift risk.

♻️ Suggested extraction
const SUBTASK_ASSIGNEES_INCLUDE = {
  subTaskAssignees: {
    include: {
      member: { include: { user: { include: { alarmSetting: true } } } },
    },
  },
};

Then use include: SUBTASK_ASSIGNEES_INCLUDE in each query.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/repositories/task.repository.js` around lines 299 - 348, Extract the
repeated subTaskAssignees → member → user → alarmSetting include graph into a
shared SUBTASK_ASSIGNEES_INCLUDE constant, then replace the duplicated include
objects in findSubTaskById, createSubTask, and updateSubTask, and reuse it where
applicable in findTaskDetail.
src/controllers/task.controller.js (1)

286-306: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

HTTP path skips the shared assignee validation used by the socket path.

The socket handler runs taskHelper.buildUpdateSubTaskAssigneeParams before calling the service, while this controller forwards raw req.body values. Reusing the helper keeps validation/normalization identical across transports and keeps the growing positional argument list (subTaskId, assigneeId, assigneeIds, userId) constructed in one place.

♻️ Suggested change
-      const { assigneeId, assigneeIds } = req.body;
-      const userId = req.user.id;
-
-      const result = await taskService.setSubTaskAssignee(
-        subTaskId,
-        assigneeId,
-        assigneeIds,
-        userId,
-      );
+      const { assigneeId, assigneeIds } = req.body ?? {};
+      const params = taskHelper.buildUpdateSubTaskAssigneeParams({
+        userId: req.user.id,
+        subTaskId,
+        assigneeId,
+        assigneeIds,
+      });
+
+      const result = await taskService.setSubTaskAssignee(
+        params.subTaskId,
+        params.assigneeId,
+        params.assigneeIds,
+        params.userId,
+      );

Verify taskHelper is imported in this controller before applying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/task.controller.js` around lines 286 - 306, Update
setSubTaskAssignee in the task controller to call
taskHelper.buildUpdateSubTaskAssigneeParams with the request values before
invoking taskService.setSubTaskAssignee. Reuse the helper’s validated and
normalized output, including the positional arguments and userId, so the HTTP
path matches the socket handler; verify taskHelper is imported.
src/services/task.service.js (2)

1300-1318: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assignee normalization here duplicates the resolver's logic.

Lines 1308-1314 repeat the same Number → NaN/> 0 filter → dedupe pipeline present in resolveSubTaskAssigneeMemberIds, setSubTaskAssignee, and task.helper.js. Extracting a shared normalizeUserIds(input) helper would remove four copies.

Also consider returning the updated assignees array in results.push(...) (line 1376) for parity with setSubTaskAssignee/createSingleSubTask, since clients now expect assignee arrays after an update.

Also applies to: 1337-1374

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 1300 - 1318, Extract the repeated
assignee ID conversion, positive-number filtering, and deduplication into a
shared normalizeUserIds(input) helper, then reuse it in
resolveSubTaskAssigneeMemberIds, setSubTaskAssignee, task.helper.js, and this
update flow instead of maintaining separate pipelines. In the results.push(...)
update response, include the updated assignees array to match setSubTaskAssignee
and createSingleSubTask.

50-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused single-assignee wrapper

resolveSubTaskAssigneeMemberId only forwards to resolveSubTaskAssigneeMemberIds, and the codebase only references the array-based helper. Dropping the wrapper would trim dead API surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 50 - 63, Remove the unused
resolveSubTaskAssigneeMemberId method from the task service, leaving
resolveSubTaskAssigneeMemberIds as the sole subtask assignee resolution API.
Verify no remaining references require updating, and do not alter the
array-based helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prisma/schema.prisma`:
- Around line 266-277: Add a database migration for the SubTaskAssignee schema
change: backfill one SubTaskAssignee row for each existing SubTask.assignee_id,
preserving the associated sub-task and member, then remove the obsolete
SubTask.assignee_id column and its related constraint/index. Ensure the
migration handles existing assignment data before dropping the column.

In `@src/helpers/task.helper.js`:
- Around line 52-56: Update the assigneeIds normalization in the relevant task
helper to validate every provided entry and throw INVALID_ASSIGNEE_ID when any
value is non-numeric or not greater than zero, matching the existing assigneeId
handling. Do not filter invalid entries into an empty array; only produce
normalizedAssigneeIds when the entire input is valid.

In `@src/repositories/task.repository.js`:
- Around line 375-383: Make replaceSubTaskAssignees atomic when using its
default prisma client by wrapping the deleteMany and createMany operations in a
transaction, or require callers to provide tx and enforce that contract.
Preserve the empty-array behavior, and deduplicate memberIds before createMany
to prevent duplicate member assignments from causing a constraint failure.

In `@src/services/task.service.js`:
- Around line 65-112: Deduplicate the normalized IDs in
resolveSubTaskAssigneeMemberIds before resolving members, preserving the
existing numeric and positive-ID filtering. Rename the local memberIds
collection to reflect that it contains member objects, and continue returning
each resolved member’s id so createSubTask, replaceSubTaskAssignees,
registerTask, and createSingleSubTask receive unique assignee IDs.
- Around line 693-704: Guard the assignee update flow before constructing or
applying uniqueAssigneeUserIds: when both assigneeId and assigneeIds are omitted
or undefined, preserve the existing assignments and do not call
replaceSubTaskAssignees. Continue allowing explicit assigneeIds: [] or
assigneeId: null to clear assignments, and ensure this behavior applies to both
request and socket payloads handled by the surrounding assignee-update logic.
- Around line 1223-1231: Update the flow around the newSubTask handling to avoid
returning early when subTaskAssignees exists but isAlarm is false. Skip only the
alarm-specific processing in that case, then use the existing final return block
to map subTaskAssignees into the assignees payload instead of returning an empty
array.
- Around line 1320-1335: Hoist the member lookup out of the subtask loop, fetch
it once using the transaction client `tx.member.findMany({ where: { taskId }
})`, and reuse the result when calling `resolveSubTaskAssigneeMemberIds` for
each subtask. Remove the per-subtask global `prisma.member.findMany` call while
preserving the existing assignment-change behavior.

---

Outside diff comments:
In `@src/services/alarm.service.js`:
- Around line 277-320: Move the SUBTASK_NO_ASSIGNEE validation using
subTask.subTaskAssignees ahead of updateSubtaskAlarmStatus so enabling alarms
cannot persist an invalid status. Keep the existing notification creation flow,
but execute the status update, deleteAlarmsBySubTaskIds, and createSubTaskAlarm
operations within the service’s existing transaction mechanism so partial alarm
creation rolls back on failure.
- Around line 302-320: Wrap the alarm deletion and rebuild sequence in a shared
transaction, passing its tx context to deleteAlarmsBySubTaskIds and every
createSubTaskAlarm call in the notifyAssignments loop. Ensure the transaction
commits only after all alarm creations succeed and rolls back on any failure,
preventing a partially updated alarm set.

In `@src/services/task.service.js`:
- Around line 716-721: Replace the manually constructed errors in the
missing-user checks near the current block and the additional occurrence around
the referenced later branch with the existing NotFoundError or BadRequestError
class, preserving the 404/USER_NOT_FOUND semantics and Korean message. Ensure
the thrown handler-bound error exposes statusCode, errorCode, and reason through
the shared error class.

In `@src/validators/task.schema.js`:
- Around line 176-183: Update the assignee validation schema containing
assigneeId and assigneeIds to require at least one field, rejecting an empty
object before setSubTaskAssignee can resolve it to an empty assignment list.
Preserve the existing coercion and validation rules when either field is
provided.

---

Nitpick comments:
In `@src/controllers/task.controller.js`:
- Around line 286-306: Update setSubTaskAssignee in the task controller to call
taskHelper.buildUpdateSubTaskAssigneeParams with the request values before
invoking taskService.setSubTaskAssignee. Reuse the helper’s validated and
normalized output, including the positional arguments and userId, so the HTTP
path matches the socket handler; verify taskHelper is imported.

In `@src/repositories/task.repository.js`:
- Around line 299-348: Extract the repeated subTaskAssignees → member → user →
alarmSetting include graph into a shared SUBTASK_ASSIGNEES_INCLUDE constant,
then replace the duplicated include objects in findSubTaskById, createSubTask,
and updateSubTask, and reuse it where applicable in findTaskDetail.

In `@src/services/member.service.js`:
- Around line 77-79: Replace the direct tx.subTaskAssignee.deleteMany call in
the member service with
taskRepository.deleteSubTaskAssigneesByMemberId(requestingMember.id, tx), and
add or update that repository method to perform the join-table cleanup while
preserving the transaction context.

In `@src/services/task.service.js`:
- Around line 1300-1318: Extract the repeated assignee ID conversion,
positive-number filtering, and deduplication into a shared
normalizeUserIds(input) helper, then reuse it in
resolveSubTaskAssigneeMemberIds, setSubTaskAssignee, task.helper.js, and this
update flow instead of maintaining separate pipelines. In the results.push(...)
update response, include the updated assignees array to match setSubTaskAssignee
and createSingleSubTask.
- Around line 50-63: Remove the unused resolveSubTaskAssigneeMemberId method
from the task service, leaving resolveSubTaskAssigneeMemberIds as the sole
subtask assignee resolution API. Verify no remaining references require
updating, and do not alter the array-based helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e275346-9e50-4eac-940f-1b918d3be5a8

📥 Commits

Reviewing files that changed from the base of the PR and between 7622de7 and 17831f6.

📒 Files selected for processing (13)
  • prisma/schema.prisma
  • prisma/seed.js
  • src/controllers/task.controller.js
  • src/dtos/task.dto.js
  • src/helpers/task.helper.js
  • src/repositories/alarm.repository.js
  • src/repositories/task.repository.js
  • src/services/alarm.service.js
  • src/services/member.service.js
  • src/services/task.service.js
  • src/socket/handlers/task.handler.js
  • src/swagger/swagger.yml
  • src/validators/task.schema.js
💤 Files with no reviewable changes (1)
  • src/repositories/alarm.repository.js

Comment thread prisma/schema.prisma
Comment on lines +266 to +277
model SubTaskAssignee {
id Int @id @default(autoincrement()) @map("sub_task_assignee_id")
subTaskId Int @map("sub_task_id")
memberId Int @map("member_id")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp

subTask SubTask @relation(fields: [subTaskId], references: [id], onDelete: Cascade)
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)

@@unique([subTaskId, memberId])
@@map("SubTaskAssignee")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd . prisma/migrations --type d | sort | tail -20
rg -n -i 'sub_task_assignee|assignee_id' prisma/migrations || echo "no migration references found"

Repository: check-task/backend

Length of output: 762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== migrations =="
fd . prisma/migrations --type d | sort

echo
echo "== schema references =="
rg -n -i 'model SubTaskAssignee|assignee_id|sub_task_assignee' prisma/schema.prisma prisma/migrations || true

echo
echo "== migration sql files =="
fd migration.sql prisma/migrations -t f | sort | xargs -r -I{} sh -c 'echo "--- {}"; rg -n -i "assignee_id|sub_task_assignee|SubTaskAssignee" "{}" || true'

Repository: check-task/backend

Length of output: 1590


Add a migration to backfill SubTaskAssignee and drop SubTask.assignee_id Existing migrations only define assignee_id in the initial schema, so deploying this schema change without a data-migration step would lose assignment data on existing databases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/schema.prisma` around lines 266 - 277, Add a database migration for
the SubTaskAssignee schema change: backfill one SubTaskAssignee row for each
existing SubTask.assignee_id, preserving the associated sub-task and member,
then remove the obsolete SubTask.assignee_id column and its related
constraint/index. Ensure the migration handles existing assignment data before
dropping the column.

Comment thread src/helpers/task.helper.js Resolved
Comment thread src/repositories/task.repository.js Resolved
Comment thread src/services/task.service.js Resolved
Comment thread src/services/task.service.js Resolved
Comment thread src/services/task.service.js Resolved
Comment thread src/services/task.service.js Resolved
songwol38 and others added 2 commits August 9, 2026 04:19
- 마이그레이션 추가
- 유효하지 않은 값에 대한 예외처리 보완

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
src/swagger/swagger.yml (2)

2585-2628: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

assigneeIds.items에 minimum: 1을 추가하십시오.

구현은 각 값을 양의 정수로 검증합니다. OpenAPI 명세는 현재 모든 정수를 허용합니다. assigneeIds와 빈 배열은 선택 사항으로 유지하십시오. 응답의 sub_task_id와 assignees 구조는 컨트롤러 반환값과 일치합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/swagger/swagger.yml` around lines 2585 - 2628, Update the assigneeIds
item schema in the sub-task assignee endpoint to add minimum: 1, matching the
positive-integer validation. Keep assigneeIds optional, allow empty arrays, and
preserve the existing response structure.

6055-6066: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

요청별 state와 nonce를 생성하고 콜백에서 검증하십시오.

/apple은 local 또는 prod를 그대로 state로 전달합니다. /apple/callback은 state를 리다이렉트 대상 선택에만 사용하며, 세션·Redis 값·nonce와 비교하지 않습니다. 따라서 공격자가 자신의 Apple 콜백을 피해자 브라우저에 제출하면 피해자를 공격자 계정으로 로그인시킬 수 있습니다.

환경 값은 서버 측 저장소에 보관하고, 요청별 예측 불가능한 state를 생성하십시오. 콜백에서 state를 일회성으로 검증한 뒤 로그인 처리를 수행하십시오. Apple nonce도 생성하고 ID 토큰의 nonce와 검증하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/swagger/swagger.yml` around lines 6055 - 6066, Update the Apple OAuth
flow documented around the /apple and /apple/callback endpoints to generate a
cryptographically unpredictable, request-specific state and nonce, store the
environment mapping server-side, and include them in the authorization request.
In the callback, validate and consume state exactly once before selecting the
redirect environment or completing login, and verify the Apple ID token nonce
against the stored request nonce; do not use the client-provided environment
value directly.
src/services/task.service.js (8)

772-776: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

404 오류 객체에 reason을 포함하십시오.

라인 773의 Error는 statusCode와 errorCode만 설정합니다. 전역 오류 미들웨어는 reason이 없는 오류를 계약대로 직렬화할 수 없습니다. NotFoundError를 사용하거나 reason을 설정하십시오.

As per coding guidelines, “Thrown errors handled by global middleware must include statusCode, errorCode, reason, and optional data.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 772 - 776, Update the missing-user
error in the task service flow to include the required reason field, or replace
the generic Error with the established NotFoundError type. Preserve the existing
404 statusCode, USER_NOT_FOUND errorCode, and Korean message while ensuring the
thrown error satisfies the global middleware contract.

Source: Coding guidelines


1288-1306: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

새 세부 과제의 다중 담당자 알림을 생성하십시오.

newSubTask.assigneeId와 newSubTask.assignee는 조인 관계 기반 반환값에 없습니다. 이 조건은 거짓이 되어 알림이 생성되지 않습니다. 조건이 참이 되더라도 assignee 변수가 선언되지 않아 실패합니다.

newSubTask.subTaskAssignees를 순회하고 각 담당자별로 알림을 생성하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 1288 - 1306, Update the alarm
creation block for newSubTask to iterate over newSubTask.subTaskAssignees
instead of relying on newSubTask.assigneeId, newSubTask.assignee, or the
undeclared assignee variable. For each assigned user, calculate the alarm using
that user’s alarm settings and create a separate alarm with the corresponding
assignee identifier, while preserving the existing isAlarm and endDate
requirements.

749-753: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

유효하지 않은 assigneeIds 값을 담당자 해제로 처리하지 마십시오.

현재 필터는 ["invalid"]를 []로 바꿉니다. 이후 replaceSubTaskAssignees(subTaskId, [])가 실행되어 기존 담당자를 모두 삭제합니다. PR 계약에서 담당자 해제는 명시적인 assigneeIds: []에만 허용해야 합니다.

배열의 각 값이 양의 정수인지 검증하십시오. 하나라도 유효하지 않으면 BadRequestError를 발생시키십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 749 - 753, Update the assigneeIds
parsing near parsedAssigneeUserIds to validate every array element as a positive
integer; if any value is invalid, throw BadRequestError instead of filtering it
out. Preserve replacement with an empty list only when the caller explicitly
provides assigneeIds: [].

879-910: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

정의되지 않은 단일 담당자 변수를 제거하십시오.

nextAssigneeMemberId, memberIdChanged, newAssignments는 이 메서드에서 선언되지 않았습니다. 담당자 변경 요청은 라인 881에서 즉시 ReferenceError를 발생시켜 트랜잭션을 롤백합니다.

memberIdsChanged를 사용하십시오. 변경된 경우 updatedTask.subTaskAssignees를 순회하여 새 담당자 전체의 알림을 생성하십시오.

수정 방향
-        if (
-          nextAssigneeMemberId != null &&
-          updatedTask.isAlarm &&
-          updatedTask.endDate &&
-          memberIdChanged
-        ) {
-          const newMember = await tx.member.findFirst({
-            where: { id: nextAssigneeMemberId, taskId: task.id },
-            include: { user: { include: { alarmSetting: true } } },
-          });
-
-          for (const assignment of newAssignments) {
+        if (
+          memberIdsChanged &&
+          updatedTask.isAlarm &&
+          updatedTask.endDate
+        ) {
+          for (const assignment of updatedTask.subTaskAssignees) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 879 - 910, Remove the undefined
nextAssigneeMemberId, memberIdChanged, and newAssignments references in the
alarm creation block. Use the declared memberIdsChanged condition, then iterate
updatedTask.subTaskAssignees to create alarms for every newly assigned member
while preserving the existing endDate and alarm-setting calculations.

1427-1457: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

관련 필드가 변경될 때만 기존 알림을 교체하십시오.

현재 흐름은 담당자 변경이나 알림 비활성화일 때만 기존 알림을 삭제합니다. 그러나 이후 블록은 제목 또는 상태만 수정해도 새 알림을 생성합니다. 기존 알림이 남아 있으므로 담당자별 중복 알림이 생성되거나 고유 제약 오류가 발생합니다.

endDate, isAlarm, 또는 담당자가 변경된 경우에만 알림을 재계산하십시오. 재생성 전에는 해당 세부 과제의 기존 알림을 삭제하거나 사용자별 upsert를 사용하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 1427 - 1457, 업데이트 흐름에서
assignmentChangeRequested 및 알림 생성 블록을 수정하여 endDate, isAlarm 또는 assigneeId가 변경된
경우에만 alarmDate를 재계산하고 알림을 교체하십시오. 재생성 전에 해당 subTaskId의 기존 알림을 삭제하거나 사용자별 upsert를
사용해 중복 및 고유 제약 오류를 방지하고, 제목이나 상태만 변경된 경우 기존 알림과 생성 흐름을 건드리지 마십시오.

1431-1458: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

중복 선언과 담당자 관계 기반 알림 처리를 수정하십시오.

const alarmHours 중복 선언과 정의되지 않은 assignee 참조로 서비스를 로드할 수 없습니다. SubTask에는 assigneeId 필드가 없으므로 해당 조건을 제거하십시오. updatedSubTask.subTaskAssignees를 순회하여 각 담당자에게 알림을 생성하십시오. updateSubTask가 필요한 조인 관계를 이미 반환하므로 재조회는 필요하지 않습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 1431 - 1458, In the
alarm-processing block of updateSubTask, remove the duplicate alarmHours
declaration and the undefined assignee reference. Do not gate processing on
updatedSubTask.assigneeId; instead iterate updatedSubTask.subTaskAssignees and
create an alarm for each assignee using the joined data already returned by
updateSubTask, without re-querying the SubTask.

605-701: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

updateSubTaskDeadline에서 assignee 관계를 제거하십시오.

SubTask 모델에는 assignee와 assigneeId가 없고 subTaskAssignees만 있습니다. 현재 include.assignee는 Prisma 쿼리를 실패시킵니다. subTaskAssignees.member.user.alarmSetting을 조회하고, 기존 알림이 없으면 각 담당자에게 createSubTaskAlarm을 호출하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 605 - 701, updateSubTaskDeadline의
기존 assignee 조회를 제거하고 subTaskAssignees.member.user.alarmSetting 관계를 포함하도록 Prisma
조회를 변경하십시오. 알림 생성 조건을 단일 assignee 기준이 아닌 각 subTaskAssignees 담당자 기준으로 처리하고, 기존
알림이 없을 때 각 담당자에게 createSubTaskAlarm을 호출하십시오.

281-287: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

다중 담당자 관계로 알림 생성 조건을 변경하십시오.

createSubTask는 subTaskAssignees를 반환합니다. 라인 285의 subTask.assigneeId와 라인 286의 subTask.assignee는 이 결과에 없습니다. 따라서 새 세부 과제의 담당자 알림이 생성되지 않습니다.

subTask.subTaskAssignees를 순회하고, 각 assignment.member.user의 taskAlarm 설정으로 알림을 생성하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/task.service.js` around lines 281 - 287, Update the notification
condition and creation flow around createSubTask to use subTask.subTaskAssignees
instead of subTask.assigneeId and subTask.assignee. Iterate each assignment, use
assignment.member.user.taskAlarm to determine eligibility, and create the
corresponding alarm for every assigned user while preserving the existing
endDate and isAlarm requirements.
src/services/alarm.service.js (2)

289-300: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

담당자 검증을 상태 저장 전에 수행하십시오.

현재 코드는 먼저 isAlarm을 true로 저장합니다. 그 후 담당자가 없으면 SUBTASK_NO_ASSIGNEE 오류를 반환합니다. 요청은 실패하지만 세부 과제는 알림 활성화 상태로 남습니다.

notifyAssignments 검증을 updateSubtaskAlarmStatus 호출 전에 수행하십시오. 또는 상태 변경과 알림 처리를 하나의 트랜잭션으로 처리하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/alarm.service.js` around lines 289 - 300, Move the
notifyAssignments validation for updatedSubTask.isAlarm === true before the
updateSubtaskAlarmStatus call, so SUBTASK_NO_ASSIGNEE is thrown before
persisting the alarm state. Preserve the existing validation condition and error
response while ensuring failed requests cannot leave the subtask enabled.

314-334: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

기존 세부 과제 알림을 사용자별로 갱신하거나 교체하십시오.

existingAlarm을 조회하지만 결과를 사용하지 않습니다. isAlarm: true 요청이 이미 활성화된 세부 과제에 다시 들어오면 모든 담당자에 대한 createSubTaskAlarm이 다시 실행됩니다. 이 동작은 중복 알림을 만들거나 고유 제약 오류를 반환합니다.

담당자별 알림을 upsert하십시오. 대안으로, 재생성 전에 deleteAlarmsBySubTaskIds([updatedSubTask.id])를 호출하고 모든 담당자 알림을 다시 생성하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/alarm.service.js` around lines 314 - 334, Update the alarm
handling around existingAlarm and the notifyAssignments loop so existing subtask
alarms are not duplicated when isAlarm remains enabled. Prefer upserting each
assignee’s alarm using its existing record, or delete all alarms for
updatedSubTask.id via deleteAlarmsBySubTaskIds before recreating them, while
preserving per-assignee alarm dates and recipients.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/services/alarm.service.js`:
- Around line 289-300: Move the notifyAssignments validation for
updatedSubTask.isAlarm === true before the updateSubtaskAlarmStatus call, so
SUBTASK_NO_ASSIGNEE is thrown before persisting the alarm state. Preserve the
existing validation condition and error response while ensuring failed requests
cannot leave the subtask enabled.
- Around line 314-334: Update the alarm handling around existingAlarm and the
notifyAssignments loop so existing subtask alarms are not duplicated when
isAlarm remains enabled. Prefer upserting each assignee’s alarm using its
existing record, or delete all alarms for updatedSubTask.id via
deleteAlarmsBySubTaskIds before recreating them, while preserving per-assignee
alarm dates and recipients.

In `@src/services/task.service.js`:
- Around line 772-776: Update the missing-user error in the task service flow to
include the required reason field, or replace the generic Error with the
established NotFoundError type. Preserve the existing 404 statusCode,
USER_NOT_FOUND errorCode, and Korean message while ensuring the thrown error
satisfies the global middleware contract.
- Around line 1288-1306: Update the alarm creation block for newSubTask to
iterate over newSubTask.subTaskAssignees instead of relying on
newSubTask.assigneeId, newSubTask.assignee, or the undeclared assignee variable.
For each assigned user, calculate the alarm using that user’s alarm settings and
create a separate alarm with the corresponding assignee identifier, while
preserving the existing isAlarm and endDate requirements.
- Around line 749-753: Update the assigneeIds parsing near parsedAssigneeUserIds
to validate every array element as a positive integer; if any value is invalid,
throw BadRequestError instead of filtering it out. Preserve replacement with an
empty list only when the caller explicitly provides assigneeIds: [].
- Around line 879-910: Remove the undefined nextAssigneeMemberId,
memberIdChanged, and newAssignments references in the alarm creation block. Use
the declared memberIdsChanged condition, then iterate
updatedTask.subTaskAssignees to create alarms for every newly assigned member
while preserving the existing endDate and alarm-setting calculations.
- Around line 1427-1457: 업데이트 흐름에서 assignmentChangeRequested 및 알림 생성 블록을 수정하여
endDate, isAlarm 또는 assigneeId가 변경된 경우에만 alarmDate를 재계산하고 알림을 교체하십시오. 재생성 전에 해당
subTaskId의 기존 알림을 삭제하거나 사용자별 upsert를 사용해 중복 및 고유 제약 오류를 방지하고, 제목이나 상태만 변경된 경우 기존
알림과 생성 흐름을 건드리지 마십시오.
- Around line 1431-1458: In the alarm-processing block of updateSubTask, remove
the duplicate alarmHours declaration and the undefined assignee reference. Do
not gate processing on updatedSubTask.assigneeId; instead iterate
updatedSubTask.subTaskAssignees and create an alarm for each assignee using the
joined data already returned by updateSubTask, without re-querying the SubTask.
- Around line 605-701: updateSubTaskDeadline의 기존 assignee 조회를 제거하고
subTaskAssignees.member.user.alarmSetting 관계를 포함하도록 Prisma 조회를 변경하십시오. 알림 생성 조건을
단일 assignee 기준이 아닌 각 subTaskAssignees 담당자 기준으로 처리하고, 기존 알림이 없을 때 각 담당자에게
createSubTaskAlarm을 호출하십시오.
- Around line 281-287: Update the notification condition and creation flow
around createSubTask to use subTask.subTaskAssignees instead of
subTask.assigneeId and subTask.assignee. Iterate each assignment, use
assignment.member.user.taskAlarm to determine eligibility, and create the
corresponding alarm for every assigned user while preserving the existing
endDate and isAlarm requirements.

In `@src/swagger/swagger.yml`:
- Around line 2585-2628: Update the assigneeIds item schema in the sub-task
assignee endpoint to add minimum: 1, matching the positive-integer validation.
Keep assigneeIds optional, allow empty arrays, and preserve the existing
response structure.
- Around line 6055-6066: Update the Apple OAuth flow documented around the
/apple and /apple/callback endpoints to generate a cryptographically
unpredictable, request-specific state and nonce, store the environment mapping
server-side, and include them in the authorization request. In the callback,
validate and consume state exactly once before selecting the redirect
environment or completing login, and verify the Apple ID token nonce against the
stored request nonce; do not use the client-provided environment value directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 48c33c52-3177-4d91-b7f6-cca9cc2d7b08

📥 Commits

Reviewing files that changed from the base of the PR and between 17831f6 and a1a95b1.

📒 Files selected for processing (9)
  • prisma/schema.prisma
  • src/controllers/task.controller.js
  • src/dtos/task.dto.js
  • src/helpers/task.helper.js
  • src/repositories/task.repository.js
  • src/services/alarm.service.js
  • src/services/task.service.js
  • src/swagger/swagger.yml
  • src/validators/task.schema.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/controllers/task.controller.js
  • src/helpers/task.helper.js
  • src/validators/task.schema.js
  • src/dtos/task.dto.js
  • prisma/schema.prisma
  • src/repositories/task.repository.js

…issue-589

# Conflicts:
#	src/services/task.service.js
merge 시 develop 쪽 변경이 다중 담당자 리팩토링 이전 코드로 일부 로직을
덮어써서 생긴 문제 수정:
- task.service.js updateSubTaskDeadline: 존재하지 않는 Prisma 관계
  `assignee`를 include하여 호출 시마다 예외가 발생하던 문제를
  subTaskAssignees 기반 다중 담당자 순회로 수정
- alarm.service.js updateSubtaskAlarmStatus: 사용되지 않는 단일 담당자
  잔재 코드(assignee 참조, 미사용 쿼리) 제거 및 알림 재생성 전
  기존 알림 삭제 로직 복원(중복 알림 생성 방지)
- 관련 유닛 테스트 mock을 다중 담당자 구조에 맞게 갱신

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
prisma/migrations/20260809040000_add_sub_task_assignee/migration.sql (1)

26-30: 🩺 Stability & Availability | 🔵 Trivial

무중단 배포에서는 assignee_id를 즉시 삭제하지 마십시오.

Line 30은 백필 직후 열을 삭제합니다. 이전 애플리케이션 인스턴스가 배포 중에 "SubTask"."assignee_id"를 읽거나 쓰면 SQL 오류가 발생합니다. 무중단 배포를 사용하면 expand/contract 배포로 분리하거나, 마이그레이션 전에 이전 인스턴스가 모두 종료되는지 확인하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/migrations/20260809040000_add_sub_task_assignee/migration.sql` around
lines 26 - 30, Update the migration around the SubTask assignee_id backfill and
DROP COLUMN so zero-downtime deployments remain compatible: do not remove
assignee_id immediately after backfilling while older application instances may
still read or write it. Use an expand/contract sequence that defers dropping the
column and foreign key until all old instances are retired, or explicitly
enforce a deployment process that stops them before this destructive migration.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/helpers/task.helper.js`:
- Around line 68-76: normalizedAssigneeIds를 생성하는 assigneeIds.map 검증을 수정해 담당자 ID가
양의 안전한 정수만 허용되도록 하십시오. 문자열은 10진 정수 형식만 통과시키고, true 같은 비문자열 값과 소수·Infinity·안전 범위를
벗어난 값은 Number 변환 전에 거부하십시오. 유효성 검증 실패 시 기존 BadRequestError를 유지하고, 통과한 값만 parsed로
반환하십시오.

---

Nitpick comments:
In `@prisma/migrations/20260809040000_add_sub_task_assignee/migration.sql`:
- Around line 26-30: Update the migration around the SubTask assignee_id
backfill and DROP COLUMN so zero-downtime deployments remain compatible: do not
remove assignee_id immediately after backfilling while older application
instances may still read or write it. Use an expand/contract sequence that
defers dropping the column and foreign key until all old instances are retired,
or explicitly enforce a deployment process that stops them before this
destructive migration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 165ed2f5-6455-4522-8367-1d0df5ba592c

📥 Commits

Reviewing files that changed from the base of the PR and between a1a95b1 and 7765c2c.

📒 Files selected for processing (6)
  • prisma/migrations/20260809040000_add_sub_task_assignee/migration.sql
  • src/helpers/task.helper.js
  • src/repositories/task.repository.js
  • src/services/alarm.service.js
  • src/services/task.service.js
  • tests/unit/subtask-enddate-nullable.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/services/alarm.service.js
  • src/repositories/task.repository.js
  • src/services/task.service.js

Comment thread src/helpers/task.helper.js Resolved
@songwol38
songwol38 merged commit 0f018f8 into develop Aug 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 세부 TASK 담당자 여러명 설정

2 participants