Skip to content

[FEATURE] 강사 수정 관련 기능 구현 #51

Merged
fervovita merged 14 commits into
devfrom
feat/#44-instructor-revision
Jun 23, 2026
Merged

[FEATURE] 강사 수정 관련 기능 구현 #51
fervovita merged 14 commits into
devfrom
feat/#44-instructor-revision

Conversation

@fervovita

@fervovita fervovita commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

🚀 Related issue

Closes #44

#️⃣ Summary

  • 강사의 시안 조회/수정 요청/시안 확정 기능을 구현했습니다.

🔧 Changes

  • 강사 시안 수정 상세 조회 API (GET /commissions/{commissionId}/revisions/current)
  • 강사 시안 수정 요청 생성 API (POST /commissions/{commissionId}/revisions)
  • 외주 최종 확정 API (POST /commissions/{commissionId}/drafts/{draftId}/finalize)

📸 Test Evidence

image image image

💬 Reviewer Notes

  • CommissionDraft에 있던 is_final 필드는 굳이 불필요하다고 판단해 제거했습니다.
  • 외주 상태 관련 에러는 외주(Commission) 자체의 룰 위반이므로 에러 코드 위치를 CommissionErrorCode로 정리했습니다.
    호출자(Facade)가 다른 애그리게이트의 에러 코드를 import하지 않도록 엔티티에 validateXxx() 패턴을 적용해 엔티티가 직접 throw하도록 했습니다.
  • 외주 확정시 이메일 발송은 금액 정산 부분까지 이 PR에서 하기에는 범위가 너무 커진다고 판단해서 추후 다른 issue에서 작업할 예정입니다.

Summary by CodeRabbit

Release Notes

New Features

  • 외주 프로젝트 “최종 확정” 기능 추가: 최신 시안을 기준으로 드래프트를 최종 확정할 수 있습니다(상태/시안 최신 여부에 따라 제한).
  • 강사 시안 수정(Revision) 기능 도입: 현재 수정 진행 정보(현재/최대 횟수), 시안 썸네일 및 디자이너 코멘트를 확인할 수 있습니다.
  • 수정 요청 생성 기능 추가: 카테고리별 피드백을 한 번에 제출하며, 중복 요청 및 수정 한도 초과 시 제한됩니다.

@fervovita fervovita self-assigned this Jun 22, 2026
@fervovita fervovita requested a review from Jong0128 as a code owner June 22, 2026 12:27
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: b785752b-acc9-4bc8-8b8b-0c3a92c1f02e

📥 Commits

Reviewing files that changed from the base of the PR and between fbca685 and 1c81136.

📒 Files selected for processing (7)
  • src/main/java/ditda/backend/domain/commission/core/entity/Commission.java
  • src/main/java/ditda/backend/domain/commission/core/exception/CommissionErrorCode.java
  • src/main/java/ditda/backend/domain/commission/revision/dto/response/InstructorRevisionDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/revision/exception/RevisionErrorCode.java
  • src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java
  • src/main/java/ditda/backend/domain/commission/revision/mapper/RevisionMapper.java
  • src/main/java/ditda/backend/domain/commission/revision/service/RevisionService.java
💤 Files with no reviewable changes (1)
  • src/main/java/ditda/backend/domain/commission/revision/dto/response/InstructorRevisionDetailResponse.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/ditda/backend/domain/commission/revision/exception/RevisionErrorCode.java
  • src/main/java/ditda/backend/domain/commission/revision/mapper/RevisionMapper.java
  • src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java

📝 Walkthrough

Walkthrough

Commission 엔티티에 상태 전이 검증 메서드와 complete()를 추가하고, 관련 에러 코드를 정비한다. 강사의 시안 최종 확정(finalizeDraft), 수정 상세 조회(getRevisionDetail), 수정 요청 생성(createRevision) API를 각각 퍼사드·서비스·컨트롤러 레이어로 신규 구현한다. RevisionRequest의 연관 매핑이 @ManyToOne에서 @OneToOne으로, RevisionResponse의 draft 필드가 producedDraft로 변경되며 check() 메서드가 추가된다.

Changes

강사 시안 수정/확정 기능 구현

Layer / File(s) Summary
Commission 상태 전이 및 에러 코드 계약
src/main/java/ditda/backend/domain/commission/core/entity/Commission.java, src/main/java/ditda/backend/domain/commission/core/exception/CommissionErrorCode.java, src/main/java/ditda/backend/domain/commission/draft/exception/DraftErrorCode.java, src/main/java/ditda/backend/domain/commission/revision/exception/RevisionErrorCode.java
CommissionvalidateRevisable/validateFinalizable/isRevisionLimitExceeded/complete 메서드를 구현하고, 세 에러 코드 enum에 신규 상수(COMMISSION_NOT_REVISABLE, COMMISSION_NOT_FINALIZABLE, DRAFT_NOT_LATEST, DUPLICATE_REVISION_CATEGORY, REVISION_ALREADY_REQUESTED, REVISION_LIMIT_EXCEEDED)를 추가한다.
엔티티 연관 관계 정비
src/main/java/ditda/backend/domain/commission/revision/entity/RevisionRequest.java, src/main/java/ditda/backend/domain/commission/revision/entity/RevisionResponse.java, src/main/java/ditda/backend/domain/commission/draft/entity/CommissionDraft.java
RevisionRequesttargetDraft@OneToOne으로 변경되고, RevisionResponse의 draft 필드가 producedDraft로 rename되며 check() 메서드가 추가된다. 테이블 유니크 제약이 컬럼 레벨에서 테이블 레벨로 이동한다. CommissionDraft에서 isFinal 필드가 제거된다.
저장소 쿼리 및 DraftService 조회 메서드
src/main/java/ditda/backend/domain/commission/draft/repository/CommissionDraftFileRepository.java, src/main/java/ditda/backend/domain/commission/draft/repository/CommissionDraftRepository.java, src/main/java/ditda/backend/domain/commission/draft/service/DraftService.java, src/main/java/ditda/backend/domain/commission/revision/repository/RevisionRequestRepository.java, src/main/java/ditda/backend/domain/commission/revision/repository/RevisionResponseRepository.java
썸네일 조회(findThumbnail), 상태 기반 최신 시안 조회(findDraftInCommissionByStatus), 수정 요청 집계(countByCommissionId), 존재 여부(existsByTargetDraftId), producedDraftId 기반 응답 조회(findByProducedDraftId) 메서드를 추가하고, DraftServicegetThumbnailgetLatestDraftOfSelectedApplication 조회 메서드를 노출한다.
RevisionService 및 RevisionMapper 구현
src/main/java/ditda/backend/domain/commission/revision/service/RevisionService.java, src/main/java/ditda/backend/domain/commission/revision/mapper/RevisionMapper.java
RevisionService에 디자이너 코멘트 확인 처리, 현재 수정 차수 계산, 수정 요청 존재 확인, 수정 요청·RevisionDetail 일괄 저장 로직을 구현한다. RevisionMapper는 watermark 완료 여부에 따라 S3 presigned URL을 생성해 InstructorRevisionDetailResponse로 변환한다.
수정 요청/응답 DTO 정의
src/main/java/ditda/backend/domain/commission/revision/dto/request/RevisionCreateRequest.java, src/main/java/ditda/backend/domain/commission/revision/dto/response/InstructorRevisionDetailResponse.java
RevisionCreateRequest(중첩 RevisionCreateCategory, JSR-380 검증 어노테이션)와 InstructorRevisionDetailResponse(중첩 DraftInfo, Swagger @Schema)를 신규 추가한다.
시안 최종 확정 퍼사드 및 컨트롤러
src/main/java/ditda/backend/domain/commission/draft/facade/DraftFacade.java, src/main/java/ditda/backend/domain/commission/draft/controller/DraftController.java
DraftFacadefinalizeDraft를 추가하여 finalizability 검증 → 최신 시안 일치 확인 → commission.complete() 흐름을 구현하고, DraftControllerPOST /{commissionId}/drafts/{draftId}/finalize 엔드포인트를 노출한다.
수정 상세 조회/수정 요청 생성 퍼사드 및 컨트롤러
src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java, src/main/java/ditda/backend/domain/commission/revision/controller/InstructorRevisionController.java
InstructorRevisionFacadegetRevisionDetail(소유 확인 → 수정 가능 검증 → 조합 → 매핑)과 createRevision(소유 확인 → 검증 체인 → 저장) 흐름을 구현하고, InstructorRevisionControllerGET /revisions/current·POST /revisions 엔드포인트를 노출한다.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(70, 130, 180, 0.5)
    Note over Client,DraftController: 시안 최종 확정
    Client->>DraftController: POST /{commissionId}/drafts/{draftId}/finalize
    DraftController->>DraftFacade: finalizeDraft(instructorId, commissionId, draftId)
    DraftFacade->>Commission: validateFinalizable()
    DraftFacade->>DraftService: getLatestDraftOfSelectedApplication(commissionId)
    DraftService-->>DraftFacade: latestDraft
    DraftFacade->>Commission: complete() → status = COMPLETED
    DraftFacade-->>DraftController: void
    DraftController-->>Client: ApiResponse(success)
  end
  rect rgba(60, 179, 113, 0.5)
    Note over Client,InstructorRevisionController: 수정 요청 생성
    Client->>InstructorRevisionController: POST /{commissionId}/revisions
    InstructorRevisionController->>InstructorRevisionFacade: createRevision(instructorId, commissionId, request)
    InstructorRevisionFacade->>Commission: validateRevisable()
    InstructorRevisionFacade->>Commission: isRevisionLimitExceeded(currentCount)
    InstructorRevisionFacade->>DraftService: getLatestDraftOfSelectedApplication(commissionId)
    DraftService-->>InstructorRevisionFacade: latestDraft
    InstructorRevisionFacade->>RevisionService: hasRevisionRequestOnDraft(draftId)
    RevisionService-->>InstructorRevisionFacade: false
    InstructorRevisionFacade->>RevisionService: createRevisionRequest(commission, draft, request)
    InstructorRevisionFacade-->>InstructorRevisionController: void
    InstructorRevisionController-->>Client: ApiResponse(success)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Ditda-Official/Ditda-Backend#38: DraftController/DraftFacade 기반의 강사 시안 선택 흐름을 구현한 PR으로, 이 PR의 finalizeDraft 확정 흐름이 그 연장선에 있다.

Suggested reviewers

  • Jong0128
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.85% 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 PR 제목은 강사 수정 관련 기능 구현이라는 전체 변경사항의 핵심을 명확하게 요약하고 있으며, 구현된 세 가지 엔드포인트(시안 조회, 수정 요청, 시안 확정)를 포괄적으로 나타냅니다.
Linked Issues check ✅ Passed PR은 연결된 이슈 #44의 세 가지 요구사항을 모두 충족합니다: (1) 시안 조회 GET 엔드포인트 구현, (2) 수정 요청 POST 엔드포인트 구현, (3) 시안 확정 POST 엔드포인트 구현이 모두 완료되었습니다.
Out of Scope Changes check ✅ Passed 변경사항은 모두 강사 수정 관련 기능 구현 범위 내입니다. CommissionDraft의 is_final 필드 제거, 커미션 검증 메서드 추가, 에러 코드 추가 등 모든 수정사항이 세 가지 엔드포인트 구현을 지원하는 관련 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#44-instructor-revision

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 4

🤖 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/main/java/ditda/backend/domain/commission/draft/repository/CommissionDraftRepository.java`:
- Around line 46-55: The findLatestDraftInCommissionByStatus method uses
non-standard JPQL syntax with `limit 1` which can cause runtime parsing errors.
Remove the `limit 1` clause from the `@Query` annotation and instead add a Limit
parameter of type org.springframework.data.domain.Limit as the third parameter
to the method signature, keeping the return type as Optional<CommissionDraft>.
Import the Limit class from Spring Data JPA
(org.springframework.data.domain.Limit). Callers of this method will now pass
Limit.of(1) as the third argument to retrieve only the latest draft, using
standard JPA-compliant syntax.

In
`@src/main/java/ditda/backend/domain/commission/revision/controller/InstructorRevisionController.java`:
- Around line 29-40: The getRevisionDetail method in
InstructorRevisionController is a GET endpoint that currently performs
state-changing operations (acknowledging/checking comments), which violates REST
principles requiring GET to be idempotent. Refactor this by keeping the GET
endpoint to only retrieve and return the revision details without any state
changes, and create a separate POST or PATCH endpoint (e.g., acknowledgeRevision
or checkRevision) to handle the state-changing operation of marking comments as
checked. This ensures proper separation of concerns between data retrieval and
state modification operations.

In
`@src/main/java/ditda/backend/domain/commission/revision/entity/RevisionRequest.java`:
- Around line 22-23: The RevisionRequest entity has a `@OneToOne` relationship but
is missing unique constraint enforcement on the database level, allowing race
conditions where multiple RevisionRequest rows could share the same
target_draft_id and break the 1:1 invariant. Locate the field in RevisionRequest
that represents the foreign key to the target draft (likely annotated with
`@OneToOne` and `@JoinColumn`) and add unique = true to the `@JoinColumn` annotation
to ensure Hibernate generates a unique constraint in the database DDL.

In
`@src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java`:
- Around line 72-84: The current implementation has a race condition where the
validation checks (calculateCurrentRevisionCount, validateCanCreateRevision,
hasRevisionRequestOnDraft) are performed separately from the actual revision
creation (createRevisionRequest), allowing concurrent requests to bypass
validation. Add synchronization protection around the entire
validation-to-creation block using pessimistic locking (such as a database lock
on the commission entity) or transactional synchronization to ensure the
check-then-act operations are atomic. Additionally, ensure appropriate database
unique constraints exist on the revision/draft relationship to prevent duplicate
entries at the database level as a secondary safety measure.
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 33df5266-7eba-466a-b4d4-37b6402f036a

📥 Commits

Reviewing files that changed from the base of the PR and between 0d20ce0 and 9f7b34d.

📒 Files selected for processing (20)
  • src/main/java/ditda/backend/domain/commission/core/entity/Commission.java
  • src/main/java/ditda/backend/domain/commission/core/exception/CommissionErrorCode.java
  • src/main/java/ditda/backend/domain/commission/draft/controller/DraftController.java
  • src/main/java/ditda/backend/domain/commission/draft/entity/CommissionDraft.java
  • src/main/java/ditda/backend/domain/commission/draft/exception/DraftErrorCode.java
  • src/main/java/ditda/backend/domain/commission/draft/facade/DraftFacade.java
  • src/main/java/ditda/backend/domain/commission/draft/repository/CommissionDraftFileRepository.java
  • src/main/java/ditda/backend/domain/commission/draft/repository/CommissionDraftRepository.java
  • src/main/java/ditda/backend/domain/commission/draft/service/DraftService.java
  • src/main/java/ditda/backend/domain/commission/revision/controller/InstructorRevisionController.java
  • src/main/java/ditda/backend/domain/commission/revision/dto/request/RevisionCreateRequest.java
  • src/main/java/ditda/backend/domain/commission/revision/dto/response/InstructorRevisionDetailResponse.java
  • src/main/java/ditda/backend/domain/commission/revision/entity/RevisionRequest.java
  • src/main/java/ditda/backend/domain/commission/revision/entity/RevisionResponse.java
  • src/main/java/ditda/backend/domain/commission/revision/exception/RevisionErrorCode.java
  • src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java
  • src/main/java/ditda/backend/domain/commission/revision/mapper/RevisionMapper.java
  • src/main/java/ditda/backend/domain/commission/revision/repository/RevisionRequestRepository.java
  • src/main/java/ditda/backend/domain/commission/revision/repository/RevisionResponseRepository.java
  • src/main/java/ditda/backend/domain/commission/revision/service/RevisionService.java
💤 Files with no reviewable changes (1)
  • src/main/java/ditda/backend/domain/commission/draft/entity/CommissionDraft.java

@Jong0128 Jong0128 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

수고하셨습니다!
아래 커멘트 확인 부탁드립니다 👍

Comment on lines +27 to +41
public static InstructorRevisionDetailResponse of(
Commission commission,
CommissionDraft draft,
String thumbnailUrl,
String designerComment,
int currentRevisionCount
) {
return new InstructorRevisionDetailResponse(
commission.getId(),
commission.getTitle(),
new DraftInfo(draft.getId(), thumbnailUrl, designerComment),
currentRevisionCount,
commission.getMaxRevision()
);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

mapper에서 썸네일 url만 만들고, 실제 응답 객체 조립은 다시 InstructorRevisionDetailResponse.of()가 하고 있어서, 응답을 위한 조립이 mapper랑 DTO 두 군데로 나뉘어 있는 것 같습니다!

기존 DraftResponseMapper처럼 mapper가 InstructorRevisionDetailResponse까지 직접 책임지고, DTO의 of()는 빼는 방향이 더 일관적일 것 같은데 어떻게 생각하시나요??

public int calculateCurrentRevisionCount(Commission commission) {
int used = revisionRequestRepository.countByCommissionId(commission.getId());

return Math.min(used, commission.getMaxRevision());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

해당 부분에서 실제 수정 회차와 플랜에 따른 수정횟수 값들 중 작은 값을 반환해주는걸로 이해했습니다!
혹시 이렇게 작성한 이유가 있으실까요? 보통 실제 회차가 플랜 최대 수정횟수보다 더 크게 되면 문제가 생기는거지 않나요?
제가 느끼기엔 에러가 발생해야할 부분이 숨겨질꺼같습니다!

Comment on lines +45 to +46
// 외주 수정 제한
REVISION_LIMIT_EXCEEDED(HttpStatus.CONFLICT, "COMMISSION_409_05", "수정 횟수를 모두 사용했습니다.");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

리뷰어 노트에 작성하신거 읽어봤습니다! 다만, 외주 수정 제한의 경우에는 그래도 RevisionErrorCode가 더 적절하지 않을까요?

@fervovita fervovita merged commit 1bcdbd5 into dev Jun 23, 2026
2 checks passed
@fervovita fervovita deleted the feat/#44-instructor-revision branch June 23, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 강사 수정 관련 기능 구현

2 participants