Conversation
Walkthrough시즌별 스페셜 미션 조회/생성 기능과 팀 미션 제출 흐름을 재편했습니다. 인증 사용자 기반 접근 제어를 도입했고 아이템 뽑기 시 포인트 차감/부족 예외를 추가했습니다. TeamMission에 시간창(startedAt/endedAt) 기준을 적용했으며 여러 엔티티의 컬렉션 초기화를 가변 리스트로 변경했습니다. 패키지 경로가 team_mission 중심으로 정리되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Admin as Admin
participant AdminAPI as AdminTeamMissionController
participant Svc as TeamMissionService
participant Repo as TeamMissionRepository
participant Teams as Season.teams
Admin->>AdminAPI: POST /admin/seasons/{season}/teams/missions/{mission}
AdminAPI->>Svc: createSpecialMission(season, mission, startedAt, endedAt)
Svc->>Svc: validate time window / season current / mission type SPECIAL
Svc->>Teams: iterate teams in season
loop for each team
Svc->>Repo: save(TeamMission(team, mission, startedAt, endedAt))
end
Svc-->>AdminAPI: List<TeamMission>
AdminAPI-->>Admin: ApiResponse(TeamMissionListResponse)
sequenceDiagram
autonumber
participant U as User
participant API as ItemController
participant S as ItemService
participant UT as UserTeamRepository
participant T as Team
U->>API: POST /seasons/{season}/teams/{team}/items/draw
API->>S: drawItem(user, season, team)
S->>UT: isMember(user, team) and season match
alt not authorized
S-->>API: error (forbidden)
else authorized
S->>T: check points
alt points < 100
S-->>API: ItemExceptions.NOT_ENOUGH_POINT
else
S->>T: deduct 100
S-->>API: ItemResponse(...)
end
end
API-->>U: ApiResponse(...)
sequenceDiagram
autonumber
participant U as User
participant API as SubmissionController
participant S as SubmissionService
participant UT as UserTeamRepository
participant TM as TeamMissionRepository
participant SR as SubmissionRepository
U->>API: POST /team_mission/{teamMission}/submissions
API->>S: submitSubmission(user, season, team, teamMission, req)
S->>UT: canAccessSubmission(user, season, team)
alt not allowed
S-->>API: error (forbidden)
else
S->>TM: verify teamMission belongs to team
S->>SR: findByTeamMission(teamMission)
alt exists
S-->>API: TeamMissionExceptions.ALREADY_SUBMITTED
else
S->>SR: save(Submission(teamMission, req.image))
S-->>API: SubmissionResponse
end
end
API-->>U: ApiResponse
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
46-48: 미래 시작 미션까지 노출되는 버그: startedAt <= now 조건 추가 필요현재 endedAt 기준만 필터링되어, 미래 시작 미션도 목록에 포함됩니다. 활성/비활성 요구사항에 맞게 startedAt 조건을 추가해 주세요.
- return TeamMissionListResponse.from(teamMissionRepository - .findAllByTeamAndEndedAtGreaterThanEqual(team, LocalDateTime.now())); + LocalDateTime now = LocalDateTime.now(); + return TeamMissionListResponse.from(teamMissionRepository + .findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(team, now, now));레포지토리 메서드 추가(별도 파일):
List<TeamMission> findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual( Team team, LocalDateTime startedAt, LocalDateTime endedAt);
🧹 Nitpick comments (7)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/exceptions/TeamMissionExceptions.java (1)
12-12: 동일 시각 허용 여부(>= vs >) 및 타임존(UTC/KST) 기준 명시
서비스 레이어(src/main/java/.../TeamMissionService.java L77–78)에서endedAt.isAfter(startedAt)로 strict >검증 후INVALID_MISSION_TIME` 예외가 던져지고 있습니다. 동일 시각 허용 여부와 저장/비교 시 사용하는 타임존을 명확히 기재해 주세요.src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (1)
27-33: 메서드 타입 파라미터화 및 페이징/정렬 도입 검토
AdminSeasonMissionService의getSeasonSpecialMissions를 아래처럼getSeasonMissionsByType(Season, MissionType)로 일반화해 재사용성을 높이세요.- 향후 데이터 규모 확대를 대비해
Pageable인자를 추가하고, 리포지토리에findAllBySeasonAndMissionType(Season, MissionType, Pageable)시그니처를 병행 도입하세요. DB 복합 인덱스(season_id, mission_type)도 검토 바랍니다.- @Transactional(readOnly = true) - public MissionListResponse getSeasonSpecialMissions(Season season){ - - return MissionListResponse.fromSeasonMission( - seasonMissionRepository.findAllBySeasonAndMissionType(season, MissionType.SPECIAL) - ); - } + @Transactional(readOnly = true) + public MissionListResponse getSeasonMissionsByType(Season season, MissionType type) { + return MissionListResponse.fromSeasonMission( + seasonMissionRepository.findAllBySeasonAndMissionType(season, type) + ); + } + + @Transactional(readOnly = true) + public MissionListResponse getSeasonSpecialMissions(Season season) { + return getSeasonMissionsByType(season, MissionType.SPECIAL); + }src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/request/CreateSpecialTeamMissionRequest.java (1)
13-19: 필드는 private으로, 스키마에 date-time 포맷/예시 명시캡슐화와 문서 명확성을 위해 제안합니다.
- @NotNull - @Schema(description = "시작 일시") - LocalDateTime startedAt; + @NotNull + @Schema(description = "시작 일시", type = "string", format = "date-time", example = "2025-09-01T09:00:00") + private LocalDateTime startedAt; @@ - @NotNull - @Schema(description = "종료 일시") - LocalDateTime endedAt; + @NotNull + @Schema(description = "종료 일시", type = "string", format = "date-time", example = "2025-09-01T18:00:00") + private LocalDateTime endedAt;src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/entity/TeamMission.java (1)
30-33: 필드 접근 제어자 통일엔티티 필드도 private으로 통일해 주세요.
- @Column(nullable = false) - @NotNull - LocalDateTime startedAt; + @Column(nullable = false) + @NotNull + private LocalDateTime startedAt;src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/response/TeamMissionResponse.java (1)
23-27: OpenAPI 명세 개선: date-time 포맷/예시 추가클라이언트 스키마 가독성 향상.
- @Schema(description = "시작 일시") + @Schema(description = "시작 일시", type = "string", format = "date-time", example = "2025-09-01T09:00:00") LocalDateTime startedAt; - @Schema(description = "종료 일시") + @Schema(description = "종료 일시", type = "string", format = "date-time", example = "2025-09-01T18:00:00") LocalDateTime endedAt;src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
123-136: 랜덤 미션 startedAt이 시즌 시작으로 고정됨 — 의도 확인 필요스케줄링 시점(now)에 활성화하려면 now 사용이 자연스럽습니다. 현재 로직은 시즌 시작부터 항상 활성 상태가 됩니다.
- LocalDateTime startedAt = season.getStartedAt(); + LocalDateTime startedAt = now; // 스케줄은 자정 실행이므로 일/주 시작 시각과 동일의도적으로 시즌 시작을 사용한 것이라면 주석으로 근거를 남겨 주세요.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/controller/AdminTeamMissionController.java (1)
30-46: 중복 생성 응답 처리/문서화중복 skip 로직을 적용한다면, 생성/스킵 개수를 반환하거나 207(Multi-Status)/200+요약 메시지 등으로 결과를 명확히 전달하는 방식을 고려해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/controller/AdminSeasonMissionController.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/exception/SeasonMissionExceptions.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/mission/exception/MissionExceptions.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/controller/AdminTeamMissionController.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/request/CreateSpecialTeamMissionRequest.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/response/TeamMissionResponse.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/entity/TeamMission.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/exceptions/TeamMissionExceptions.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java(4 hunks)
🔇 Additional comments (5)
src/main/java/com/github/kmu_shell_we/domain/mission/exception/MissionExceptions.java (1)
12-12: 메시지 표현 수정- INVALID_SPECIAL_MISSION_TYPE("MISSION_002", "SPECIAL 타입이 아닙니다."), + INVALID_SPECIAL_MISSION_TYPE("MISSION_002", "미션 타입이 SPECIAL이 아닙니다."),해당 예외는
TeamMissionService의 타입 검증 가드에서만 사용되어 일관됩니다.src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/exception/SeasonMissionExceptions.java (1)
13-13: INVALID_SEASON_MISSION 예외 사용 여부 확인 필요
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/exception/SeasonMissionExceptions.java에 정의만 있고 호출되는 곳이 없습니다. 실제 서비스/컨트롤러에서 사용되는지 확인 후, 미사용 시 제거하거나 예외를 던지는 로직을 추가해 주세요.src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (1)
6-6: 필요한 enum 임포트 추가 확인됨
MissionType.SPECIAL필터링을 위한 임포트가 정확합니다.src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/controller/AdminSeasonMissionController.java (1)
35-42: DomainClassConverter 설정 검증 및 페이징 도입 검토
@PathVariable Season season이 DomainClassConverter 전제이므로, 프로젝트 전반에서 UUID→Season 엔티티 변환이 정상 작동하는지 확인 필요합니다.- 응답량 증가에 대비해 페이징(및 정렬) 쿼리 파라미터 도입을 고려해 주세요.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/controller/AdminTeamMissionController.java (1)
33-35: Spring Data Web 자동 설정으로 DomainClassConverter 등록됨
Spring Boot에서spring-boot-starter-web과spring-boot-starter-data-jpa의존성을 함께 사용하면SpringDataWebAutoConfiguration을 통해DomainClassConverter가 자동 등록되어
@PathVariable Season season,@PathVariable Mission mission바인딩이 정상 동작합니다. 추가 설정 불필요합니다. (docs.spring.io, stackoverflow.com)Likely an incorrect or invalid review comment.
* [SW-120] refactor(submission): 자신의 팀의 미션만 제출 및 팀 미션 제출 시 Mission ID가 아닌 TeamMission ID 받기 * [SW-120] refactor(item): 자신의 팀의 아이템만 뽑기 및 조회 가능 * [SW-120] refactor(submission): team이 teamMission을 갖고 있는지 검사 및 각 엔티티에서 List.of를 new ArrayList<>()로 변경 * [SW-120] refactor(submission): 미션 중복 제출 금지
* [SW-124] refactor(item): 아이템 뽑기 시 포인트 조회 및 차감 * [SW-124] chore(item): 예외 코드 수정
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
46-47: 미시작(미래) 미션이 함께 노출됩니다 — startedAt 조건 추가 필요현재 쿼리는 endedAt >= now만 필터링하여 미래에 시작될 미션도 포함될 수 있습니다. startedAt <= now 조건을 함께 적용해주세요.
- return TeamMissionListResponse.from(teamMissionRepository - .findAllByTeamAndEndedAtGreaterThanEqual(team, LocalDateTime.now())); + LocalDateTime now = LocalDateTime.now(); + return TeamMissionListResponse.from(teamMissionRepository + .findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(team, now, now));레포지토리에 대응 메서드가 없다면 추가가 필요합니다:
List<TeamMission> findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(Team team, LocalDateTime start, LocalDateTime end);
♻️ Duplicate comments (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
101-111: 중복 생성(멱등성) 보장 필요 — 동일 팀/미션/기간 중복 방지엔드포인트가 반복 호출되면 동일 구간의 TeamMission이 누적 생성될 수 있습니다. 존재 여부(기간 겹침) 체크로 skip하거나 DB 유니크 제약을 권장합니다.
List<TeamMission> teamMissions = new ArrayList<>(); for (Team team : teams) { + boolean exists = teamMissionRepository + .existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore( + team, mission, startedAt, endedAt); + if (exists) continue; teamMissions.add(TeamMission.builder() .mission(mission) .team(team) .startedAt(startedAt) .endedAt(endedAt) .build()); }레포지토리 메서드 예시:
boolean existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore(Team team, Mission mission, LocalDateTime start, LocalDateTime end);DB 권장:
- 유니크 인덱스 (team_id, mission_id, started_at, ended_at)
- 조회용 인덱스 (team_id, started_at, ended_at)
🧹 Nitpick comments (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (4)
131-131: 랜덤(일/주간) 미션 startedAt은 now가 자연스럽습니다활성/비활성 판정이 startedAt 기준이라면 시즌 시작 시각 대신 생성 시각(now)을 쓰는 편이 직관적입니다.
- LocalDateTime startedAt = season.getStartedAt(); + LocalDateTime startedAt = now;Also applies to: 143-143
69-75: 서비스 계층 접근 제어 고려컨트롤러에서만 권한을 제한했다면, 타 경로 호출 가능성을 차단하기 위해 서비스 메서드에도 역할 기반 @PreAuthorize 추가를 고려해주세요(예: 관리자 전용).
50-51: 크론 타임존 명시 제안서버 지역/서머타임 영향 방지를 위해 @scheduled에 zone 속성 지정(예: Asia/Seoul 또는 UTC)을 권장합니다.
Also applies to: 58-60
69-112: 엣지 케이스 테스트 추가 제안아래 케이스에 대한 단위/통합 테스트가 있으면 안정성이 올라갑니다.
- endedAt <= startedAt
- 시즌 경계 밖 시간
- mission null 또는 SPECIAL 아님
- 시즌에 팀 없음
- 중복 호출 시 멱등성 유지
필요하시면 테스트 스켈레톤을 생성해 드릴게요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/request/CreateSpecialTeamMissionRequest.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/dto/request/CreateSpecialTeamMissionRequest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (3)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (3)
Transactional(35-49)Service(15-59)Transactional(27-33)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
Entity(13-36)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java (1)
Transactional(50-66)
🔇 Additional comments (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (2)
88-96: 현재 시즌 제한 + SPECIAL 타입 검증 LGTM현재 시즌이 아닐 때 차단, 미션 null 체크, SPECIAL 타입 강제 모두 적절합니다.
94-96: 특별 미션이 시즌에 등록된 미션인지 확인 필요 여부비즈니스 규칙상 SPECIAL 미션도 해당 시즌(SeasonMission)에 등록되어 있어야 하는지 확인 부탁드립니다. 필요하다면 아래 검증을 추가해주세요.
+ seasonMissionRepository.findBySeasonAndMission(season, mission) + .orElseThrow(SeasonMissionExceptions.NOT_FOUND_SEASON_MISSION::toException);
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/repository/TeamMissionRepository.java (1)
26-26: 시간창 검색 성능 대비 인덱스 권장
team + startedAt + endedAt조합 검색이 빈번합니다. DB에 복합 인덱스 추가를 권장합니다.예:
(team_id, started_at, ended_at)또는 사용 패턴에 맞춰(team_id, started_at)+(team_id, ended_at).또한, 스페셜 미션 멱등성 보완용 존재 여부 체크 메서드도 추가해 두세요.
+ boolean existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore( + Team team, Mission mission, LocalDateTime start, LocalDateTime end);src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (2)
81-87: 시즌 경계 검증이 한쪽 경계(null)에서 무력화됩니다 — 각 경계를 독립 검증으로 변경- if (seasonStart != null && seasonEnd != null) { - if (startedAt.isBefore(seasonStart) || endedAt.isAfter(seasonEnd)) { - throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); - } - } + if (seasonStart != null && startedAt.isBefore(seasonStart)) + throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); + if (seasonEnd != null && endedAt.isAfter(seasonEnd)) + throw TeamMissionExceptions.INVALID_MISSION_TIME.toException();
102-112: 멱등성: 동일 팀/미션/기간 중복 생성 방지동시/반복 호출 시 중복 레코드가 생성될 수 있습니다. 존재 여부 검사 후 건너뛰거나(서비스), DB 유니크 인덱스로 보장하세요.
List<TeamMission> teamMissions = new ArrayList<>(); for (Team team : teams) { - teamMissions.add(TeamMission.builder() + boolean exists = teamMissionRepository + .existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore( + team, mission, startedAt, endedAt); + if (exists) continue; + teamMissions.add(TeamMission.builder() .mission(mission) .team(team) .startedAt(startedAt) .endedAt(endedAt) .build()); }DB 측 유니크 인덱스 예:
(team_id, mission_id, started_at, ended_at).
🧹 Nitpick comments (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/repository/TeamMissionRepository.java (1)
26-26: 활성 구간 조회 시그니처 단순화(+가독성 개선)동일 시점을 두 번 전달하는
now, now1네이밍이 혼란스럽습니다. JPQLBETWEEN을 써서 단일 파라미터로 의도를 드러내는 편이 명확합니다.- List<TeamMission> findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(Team team, LocalDateTime now, LocalDateTime now1); + @Query("SELECT tm FROM TeamMission tm WHERE tm.team = :team AND :asOf BETWEEN tm.startedAt AND tm.endedAt") + List<TeamMission> findActiveMissions(Team team, LocalDateTime asOf);서비스 호출부도 다음과 같이 정리하세요(서비스 파일 참조).
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (3)
46-49: 활성 미션 조회 메서드 간결화 + Clock 주입 제안리포지토리 메서드를
findActiveMissions(team, now)로 단순화하고, 테스트 용이성과 TZ 일관성을 위해Clock주입을 고려하세요.- LocalDateTime now = LocalDateTime.now(); - return TeamMissionListResponse.from(teamMissionRepository - .findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(team, now, now)); + LocalDateTime now = LocalDateTime.now(); + return TeamMissionListResponse.from(teamMissionRepository.findActiveMissions(team, now));Clock 주입 예시(추가 변경):
// 필드 private final java.time.Clock clock; // 사용 LocalDateTime now = LocalDateTime.now(clock);
132-146: 스케줄 미션 startedAt은 시즌 시작이 아니라 생성 시점(now)이 자연스럽습니다
startedAt = season.getStartedAt()는 의미가 넓어집니다. 실제 활성화 시점에 맞춰 now 사용을 권장합니다.- LocalDateTime startedAt = season.getStartedAt(); + LocalDateTime startedAt = now;
70-113: 스페셜 미션이 시즌 미션에 속하는지 검증(정책 정합성 확인 필요)스페셜 미션이 해당 시즌의 등록 미션(
SeasonMission)인지 확인 로직이 없으면 정책 일탈이 가능합니다. 필요 시seasonMissionRepository.findBySeasonAndMission(...)로 보강하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/repository/TeamMissionRepository.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/repository/TeamMissionRepository.java (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java (4)
Service(29-108)now(52-52)now(61-64)Transactional(38-44)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
Entity(13-36)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (3)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (3)
Transactional(35-49)Transactional(27-33)Service(15-59)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
Entity(13-36)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java (1)
TeamMissionRepository(13-21)
🔇 Additional comments (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
70-113: 마이그레이션 파일에 team_mission.started_at·ended_at 컬럼 DDL 미발견
프로젝트 내 SQL/XML/YAML 마이그레이션에서 team_mission 테이블에 started_at, ended_at 컬럼 생성 또는 수정 스크립트가 없습니다. DB 스키마 반영 여부(또는 JPA auto-ddl 설정 사용 여부)를 확인하고, 필요 시 마이그레이션 파일을 추가해주세요.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (1)
101-109: 중복 생성 방지(멱등성): 팀/미션/기간 겹침은 skip엔드포인트 반복 호출 시 동일(또는 겹치는) 기간 팀미션이 누적 생성됩니다. 겹침 존재 시 생성을 건너뛰세요. DB 유니크 제약도 병행 권장.
- for (Team team : teams) { - teamMissions.add(TeamMission.builder() + for (Team team : teams) { + boolean exists = teamMissionRepository + .existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore( + team, mission, startedAt, endedAt); + if (exists) continue; + teamMissions.add(TeamMission.builder() .mission(mission) .team(team) .startedAt(startedAt) .endedAt(endedAt) .build()); }레포지토리 메서드:
boolean existsByTeamAndMissionAndEndedAtAfterAndStartedAtBefore( Team team, Mission mission, LocalDateTime start, LocalDateTime end);DB 인덱스(권장): unique(team_id, mission_id, started_at, ended_at) + (team_id, started_at, ended_at) 일반 인덱스.
🧹 Nitpick comments (5)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (5)
46-49: 활성 미션 조회 성능 인덱스 권장팀별 활성 구간 조회가 빈번할 것이므로 (team_id, started_at, ended_at) 복합 인덱스를 추가하세요. started_at ≤ now AND ended_at ≥ now 패턴에 최적입니다.
46-46: 테스트 용이성: Clock 주입LocalDateTime.now() 직접 호출 대신 Clock을 주입하면 시간 고정 테스트가 쉬워집니다.
- LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(clock);(필드)
+ private final Clock clock;(설정에서 Clock 빈 등록 필요)
94-96: SPECIAL 미션은 시즌에 등록되어 있는지도 검증 권장시즌에 등록되지 않은 SPECIAL 미션이 배포될 수 있습니다. 일관성을 위해 SeasonMission 존재 여부를 체크하세요.
if (mission.getType() != MissionType.SPECIAL) throw MissionExceptions.INVALID_SPECIAL_MISSION_TYPE.toException(); + // 시즌에 해당 미션이 등록되어 있는지 확인 + seasonMissionRepository.findBySeasonAndMission(season, mission) + .orElseThrow(SeasonExceptions.NOT_FOUND_SEASON_MISSION::toException);
131-145: 랜덤(일/주) 미션 startedAt 의미 보정startedAt을 시즌 시작으로 고정하면 의미가 흐려집니다. 생성 시점을 startedAt으로 두는 편이 자연스럽고 응답/감사 로그 해석이 명확합니다.
- LocalDateTime startedAt = season.getStartedAt(); + LocalDateTime startedAt = now; ... - .startedAt(startedAt) + .startedAt(startedAt)(주간 스케줄은 이미 자정에 실행되므로 now가 주차 시작을 충분히 대표합니다. 필요 시 월요일 00:00로 정규화하는 별도 계산도 고려하세요.)
152-156: 권한 체크 메서드 명확성(Nitpick)teamCheck는 적절합니다. 다만 Admin 경로에서 별도 권한(ROLE_ADMIN) 우선 통과가 필요하면 조건을 분리해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
Entity(13-36)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java (1)
TeamMissionRepository(13-21)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (2)
Transactional(35-49)Service(15-59)src/main/java/com/github/kmu_shell_we/domain/mission/entity/Mission.java (1)
Entity(13-42)
🔇 Additional comments (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/service/TeamMissionService.java (2)
6-14: 필요 import 추가 LGTMMission/MissionExceptions, TeamMissionExceptions, TeamExceptions 추가가 본 파일 변경과 일관됩니다.
78-86: 시간 검증 로직 적절(LGTM)startedAt/endedAt 유효성 및 시즌 경계 독립 검증이 반영되었습니다.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/exceptions/TeamMissionExceptions.java (1)
1-1: 패키지 경로 불일치로 컴파일 실패 가능파일 경로는
_team/_team_mission/...인데 package 선언은_mission_team입니다. 아래처럼 정정 필요.-package com.github.kmu_shell_we.domain.season._team._mission_team.exceptions; +package com.github.kmu_shell_we.domain.season._team._team_mission.exceptions;src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java (2)
1-1: 패키지 경로 불일치로 컴파일 실패 가능성 큼파일 경로는
_team_mission/service인데 package는_mission_team.service입니다. 일관되게_team_mission로 변경하세요.-package com.github.kmu_shell_we.domain.season._team._mission_team.service; +package com.github.kmu_shell_we.domain.season._team._team_mission.service;
131-145: 일/주간 미션 startedAt 계산 부정확 및 시즌 범위 초과 가능성
- startedAt을 시즌 시작 시각으로 고정하면 “생성 시점 이전부터 활성”처럼 보이는 사이드이펙트가 납니다.
- endedAt이 시즌 종료 이후로 넘어갈 수 있습니다.
-LocalDateTime startedAt = season.getStartedAt(); -LocalDateTime endedAt = endCalculator.apply(now); +LocalDateTime startedAt = now; +LocalDateTime endedAt = endCalculator.apply(now); +// 시즌 종료를 넘지 않도록 클램프 +LocalDateTime seasonEnd = season.getEndedAt(); +if (seasonEnd != null && endedAt.isAfter(seasonEnd)) { + endedAt = seasonEnd; +} +// 시간 역전 방지 +if (!endedAt.isAfter(startedAt)) { + throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); +}src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java (1)
1-1: 패키지 경로 불일치 수정 필요파일 경로는
_team_mission/repository인데 package가_mission_team.repository입니다. 컴파일 에러 원인입니다.-package com.github.kmu_shell_we.domain.season._team._mission_team.repository; +package com.github.kmu_shell_we.domain.season._team._team_mission.repository;
🧹 Nitpick comments (26)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/controller/TeamScheduleController.java (1)
34-34: users 쿼리파라미터 — 문서·바인딩 정합성 보강 권고대상: src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/controller/TeamScheduleController.java
build.gradle에 spring-boot-starter-data-jpa 및 spring-boot-starter-web이 있어 Spring Data Web 자동설정(= @EnableSpringDataWebSupport)으로 DomainClassConverter가 기본 활성화될 가능성이 높음. (docs.spring.vmware.com)
리포지토리에서 @EnableSpringDataWebSupport 선언 및 SpringDataWebAutoConfiguration 제외 설정은 발견되지 않음(제공된 스크립트 결과).
- 최소(권장): OpenAPI 문서와 실행을 일치시키기 위해 파라미터 name·required를 명시하세요. 적용 예:
- @Parameter(description = "유저 ID 목록", array = @ArraySchema(schema = @Schema(type = "string", format = "uuid"))) @RequestParam List<User> users + @Parameter(name = "users", description = "유저 ID 목록", required = true, + array = @ArraySchema(schema = @Schema(type = "string", format = "uuid"))) + @RequestParam(name = "users") List<User> users
- 검증 포인트: 프로젝트에서 @EnableWebMvc 또는 WebMvcConfigurationSupport로 MVC auto-config를 대체하는지 확인하세요 — 그럴 경우 DomainClassConverter가 등록되지 않을 수 있습니다. (stackoverflow.com)
- 선택(권장): 성능·명확성 향상을 위해 컨트롤러는 List를 받고 서비스에서 일괄 조회(batch)로 List를 구성하는 방식 고려.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/request/CreateSubmissionRequest.java (1)
11-14: 필드 검증 강화 제안: 공백/URL 형태 검증현재
@NotNull만 있어 공백 문자열이 통과합니다. 최소@NotBlank추가를 권장합니다. (Hibernate Validator 사용 시@URL도 고려)- @NotNull + @NotBlank @Schema(description = "팀 미션 인증 이미지 URL") String image;외부 변경이 우려되면 필드명은 그대로 두고 검증만 보강하세요. (필요 시 추가 import)
import jakarta.validation.constraints.NotBlank;src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/exception/ItemExceptions.java (1)
12-12: 포인트 부족 예외 추가 LGTM아이템 뽑기 시 도메인 표현이 명확해졌습니다. 코드 포맷(언더스코어 vs 하이픈)은 레포 내 다른 예외 코드와 일관성만 유지하면 좋겠습니다.
src/main/java/com/github/kmu_shell_we/domain/mission/entity/Mission.java (1)
40-41: 컬렉션 가변화 LGTM — 연관관계 편의 메서드 추가 권장
@Setter(AccessLevel.NONE) + new ArrayList<>()선택 좋습니다. 양방향 일관성을 위한 편의 메서드를 추가해 두면 안전합니다.public void addSeasonMission(SeasonMission sm) { seasonMissions.add(sm); sm.setMission(this); } public void removeSeasonMission(SeasonMission sm) { seasonMissions.remove(sm); sm.setMission(null); }src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java (1)
16-16: N+1 예방을 위한 fetch 전략 선택목록 조회 시 Submission→연관 엔티티 접근이 잦다면
@EntityGraph메서드 변형을 추가해 N+1을 방지하세요.// 예시 @EntityGraph(attributePaths = {"teamMission"}) List<Submission> findAllByTeamMissionIn(List<TeamMission> teamMissions);src/main/java/com/github/kmu_shell_we/domain/user/entity/User.java (1)
39-45: 컬렉션 가변화 LGTM — 양방향 편의 메서드로 일관성 보강연관관계 편의 메서드를 두어 양쪽 세팅을 보장하는 것을 권장합니다.
public void addUserTeam(UserTeam ut) { userTeams.add(ut); ut.setUser(this); } public void addSchedule(Schedule s) { schedules.add(s); s.setUser(this); }src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/entity/Submission.java (2)
18-21: FK 컬럼 네이밍/제약 재확인 + 일대일 제약 명시 권장현재 컬럼명이
mission_team_id로 보이는데, 도메인 정리 후team_mission네이밍과 불일치합니다. 마이그레이션과 스키마를 확인해 주세요. 또한 일대일 보장을 위해unique = true명시를 권장합니다. 필요 시optional = false도 함께 표현해 주세요.아래와 같이 변경을 고려해 보세요(스키마에 맞춰 컬럼명은 최종 확인 필요).
- @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "mission_team_id", nullable = false) + @OneToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "team_mission_id", nullable = false, unique = true) TeamMission teamMission;추가로, Hibernate 버전에 따라
@OneToOne(fetch = LAZY)가 실제 Lazy로 동작하지 않을 수 있습니다. 필요 시@LazyToOne(LazyToOneOption.NO_PROXY)검토 부탁드립니다.
22-24: 필드 의미 명확화 및 길이 제한
image가 URL이라면imageUrl로 리네이밍 및 길이 제한을 권장합니다(긴 URL 대비).- @Column(nullable = false) - @NotNull - String image; + @Column(nullable = false, length = 2048) + @NotNull + String image; // imageUrl 권장src/main/java/com/github/kmu_shell_we/domain/season/entity/Season.java (4)
43-45: 컬렉션 불변 교체 방지 설정 👍 (+ 양방향 고정 헬퍼 메서드 제안)
@Setter(AccessLevel.NONE)과 가변 리스트 초기화는 JPA 관점에서 안전합니다. 양방향 연관 고정 편의를 위해 헬퍼 메서드 추가를 권장합니다.public void addTeam(Team team) { team.setSeason(this); this.teams.add(team); }
48-50: 필드명seasonMissions정리 👍 (+ 헬퍼 메서드 제안)네이밍 일관성이 좋아졌습니다. 연관 고정을 위한 편의 메서드도 함께 두면 사용성이 좋아집니다.
public void addSeasonMission(SeasonMission sm) { sm.setSeason(this); this.seasonMissions.add(sm); }
51-56: 경계 포함 여부 명확화현재 구현은 시작/종료 시각과 정확히 같은 순간을 “현재 시즌 아님”으로 처리합니다. 일반적으로 시작 포함, 종료 미포함([startedAt, endedAt))이 직관적입니다. 요구사항에 맞게 확인 후 아래와 같이 조정 권장.
- return now.isAfter(startedAt) && now.isBefore(endedAt); + // 시작 포함, 종료 미포함: [startedAt, endedAt) + return !now.isBefore(startedAt) && now.isBefore(endedAt);
33-40: 시간 기반 조회 대비 인덱스/타임존 정책 점검
startedAt/endedAt로 “현재 시즌” 조회가 잦다면 DB 인덱스를 고려해 주세요. 또한 서버/DB 타임존 정책(예: UTC 고정) 일관성 검증을 권장합니다.예시:
- JPA:
@Table(indexes = @Index(name = "idx_season_period", columnList = "startedAt,endedAt"))- 운영: 애플리케이션/DB 타임존 UTC 통일 후 표시 단계에서 지역 시간 변환
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionResponse.java (1)
17-18: 필드 명칭 미세 개선 제안응답 스키마에서 URL임이 명확하도록
image→imageUrl제안. API 문서와도 용어를 맞추면 좋습니다.src/main/java/com/github/kmu_shell_we/domain/season/_team/entity/Team.java (3)
39-41: JPA 안전한 컬렉션 초기화 👍 (+ 연관 고정 헬퍼 권장)
List.of()제거와@Setter(AccessLevel.NONE)적용 좋습니다. 양방향 고정을 위한 편의 메서드 추가를 권장합니다.public void addUserTeam(UserTeam ut) { ut.setTeam(this); this.userTeams.add(ut); }
44-46: 동일 패턴 일관 적용 👍
items에도 동일 전략 적용 OK. 편의 메서드도 함께 두면 일관성↑public void addItem(Item item) { item.setTeam(this); this.items.add(item); }
33-35: 조회 성능 관점 인덱스 고려(다대일 FK)팀을 시즌별로 자주 조회한다면
season_id인덱스를 확인/추가해 주세요.src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java (1)
51-69: 스케줄러 타임존 명시 필요배포 환경 JVM 기본 타임존에 의존하면 일 배치/주 배치 시각이 달라질 수 있습니다. 서비스 기준 TZ(예: Asia/Seoul)로 고정하세요.
-@Scheduled(cron = "0 0 0 * * ?") +@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Seoul") public void assignDailyMission() { ... } -@Scheduled(cron = "0 0 0 ? * MON") +@Scheduled(cron = "0 0 0 ? * MON", zone = "Asia/Seoul") public void assignWeeklyMission() { ... }src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
13-20: 조회 최적화를 위한 인덱스 권장활성 미션 조회가 (team, startedAt<=now, endedAt>=now)에 의존하므로 복합 인덱스를 권장합니다.
@Entity +@Table(indexes = { + @Index(name = "idx_team_started_ended", columnList = "team_id, startedAt, endedAt") +}) public class TeamMission extends BaseSchema {src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionResponse.java (1)
23-27: 시간 필드의 타임존 명시 또는 타입 개선 필요LocalDateTime는 타임존이 없어 클라이언트별 해석 차이가 날 수 있습니다. API에서 혼동 방지를 위해 OffsetDateTime/Instant 사용을 권장하거나 최소한 스키마에 TZ를 명시해 주세요. 예시처럼 포맷/예제를 추가하면 문서 가독성이 좋아집니다.
- @Schema(description = "시작 일시") + @Schema(description = "시작 일시 (KST, yyyy-MM-dd'T'HH:mm:ss)", example = "2025-09-01T09:00:00") LocalDateTime startedAt; - @Schema(description = "종료 일시") + @Schema(description = "종료 일시 (KST, yyyy-MM-dd'T'HH:mm:ss)", example = "2025-09-01T23:59:59") LocalDateTime endedAt;src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java (3)
81-83: 시간 소스 주입으로 테스트/일관성 개선
LocalDateTime.now()대신Clock을 주입해 사용하면 테스트 용이성과 일관성이 높아집니다.- .findCurrentMission(team, LocalDateTime.now()) + .findCurrentMission(team, LocalDateTime.now(clock))추가(필드/임포트):
+import java.time.Clock; ... private final TeamRepository teamRepository; private final UserTeamRepository userTeamRepository; private final ItemRepository itemRepository; private final TeamMissionRepository teamMissionRepository; private final SeasonMissionRepository seasonMissionRepository; + private final Clock clock;
85-94: MISSION_CHANGE 시 동일 미션으로 변경되는 경우 방지 권장현재 로직은 기존 미션과 동일 미션을 뽑아도 그대로 세팅됩니다. 사용자 체감상 “변경”이 무효가 될 수 있어 제외 조건을 추가해 주세요(리포지토리에서 제외 조회 또는 재추첨).
- Mission after = seasonMissionRepository.findRandomDailyMissionBySeason(season) + Mission after = seasonMissionRepository.findRandomDailyMissionBySeasonExcluding(season, current.getMission().getId()) .orElseThrow(SeasonMissionExceptions.NOT_FOUND_SEASON_MISSION::toException) .getMission();
55-56: 매직 넘버 상수화
100(뽑기 비용),50(감점치) 하드코딩을 상수로 올려두면 유지보수 용이합니다.+ private static final int DRAW_COST = 100; + private static final int SCORE_DEDUCTION_POINT = 50; ... - if (team.getPoint() < 100) { + if (team.getPoint() < DRAW_COST) { throw ItemExceptions.NOT_ENOUGH_POINT.toException(); } - team.setPoint(team.getPoint() - 100); + team.setPoint(team.getPoint() - DRAW_COST); ... - otherTeam.setExperience(Math.max(0, otherTeam.getExperience() - 50)); + otherTeam.setExperience(Math.max(0, otherTeam.getExperience() - SCORE_DEDUCTION_POINT)); ... - yield ScoreDeductionResponse.from(otherTeam, 50); + yield ScoreDeductionResponse.from(otherTeam, SCORE_DEDUCTION_POINT);Also applies to: 100-103, 107-116
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java (2)
33-39: 정렬 추가 제안목록 응답은 최신 제출 우선 등 정렬 기준을 명시하면 UX가 좋아집니다(예:
createdAt desc).
45-48: 404로 매핑 고려단건 조회에서 미존재 시
null대신 404가 더 명확할 수 있습니다. 컨트롤러/ApiResponse 정책에 맞춰 검토 부탁드립니다.src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/controller/SubmissionController.java (2)
55-66: POST는 201 Created 권장리소스 생성 응답 코드를 201로 명시하면 API 의미가 분명합니다.
+import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; ... @PostMapping("/team_mission/{teamMission}/submissions") @Operation(summary = "팀 미션 제출") + @ResponseStatus(HttpStatus.CREATED) public ApiResponse<SubmissionResponse> submitMission(
58-66: 메서드명 의미 일치화 제안
submitMission→submitSubmission이 컨트롤러 역할과 더 잘 맞습니다. 공개 API가 아니라면 리네이밍 고려 바랍니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
src/main/java/com/github/kmu_shell_we/domain/mission/entity/Mission.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/controller/ItemController.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/exception/ItemExceptions.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java(3 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/_submission_mission_team/repository/SubmissionRepository.java(0 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/_submission_mission_team/service/SubmissionService.java(0 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/controller/TeamScheduleController.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/controller/SubmissionController.java(3 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/request/CreateSubmissionRequest.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionListResponse.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionResponse.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/entity/Submission.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/TeamMissionController.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionListResponse.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionResponse.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/exceptions/TeamMissionExceptions.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java(1 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java(5 hunks)src/main/java/com/github/kmu_shell_we/domain/season/_team/entity/Team.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/season/entity/Season.java(2 hunks)src/main/java/com/github/kmu_shell_we/domain/user/entity/User.java(2 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/_submission_mission_team/repository/SubmissionRepository.java
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/_submission_mission_team/service/SubmissionService.java
🧰 Additional context used
🧬 Code graph analysis (13)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java (1)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java (1)
Service(33-124)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/repository/UserTeamRepository.java (1)
UserTeamRepository(12-17)src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/repository/ItemRepository.java (1)
ItemRepository(10-13)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/exceptions/TeamMissionExceptions.java (5)
src/main/java/com/github/kmu_shell_we/domain/mission/exception/MissionExceptions.java (1)
Getter(7-16)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/exception/SeasonMissionExceptions.java (1)
Getter(7-17)src/main/java/com/github/kmu_shell_we/domain/season/_team/exception/TeamExceptions.java (1)
Getter(7-16)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/exception/UserTeamExceptions.java (1)
Getter(7-17)src/main/java/com/github/kmu_shell_we/domain/user/_schedule/exception/ScheduleExceptions.java (1)
Getter(7-16)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/controller/ItemController.java (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/controller/SubmissionController.java (1)
MemberGuard(23-67)src/main/java/com/github/kmu_shell_we/domain/season/_team/controller/TeamController.java (1)
MemberGuard(23-59)src/main/java/com/github/kmu_shell_we/global/security/authentication/UserAuthentication.java (1)
RequiredArgsConstructor(14-52)src/main/java/com/github/kmu_shell_we/domain/user/controller/UserController.java (1)
MemberGuard(15-28)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/exception/ItemExceptions.java (3)
src/main/java/com/github/kmu_shell_we/domain/season/_team/exception/TeamExceptions.java (1)
Getter(7-16)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/exception/UserTeamExceptions.java (1)
Getter(7-17)src/main/java/com/github/kmu_shell_we/domain/auth/exception/AuthExceptions.java (1)
Getter(7-18)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java (4)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/AdminItemService.java (2)
Service(12-24)Transactional(18-23)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/service/AdminUserTeamService.java (1)
Service(14-41)src/main/java/com/github/kmu_shell_we/domain/season/_team/service/TeamService.java (1)
Service(17-47)src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/service/TeamScheduleService.java (1)
Service(20-42)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java (3)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/controller/AdminSeasonMissionController.java (3)
AdminGuard(17-65)PostMapping(44-52)GetMapping(35-42)src/main/java/com/github/kmu_shell_we/domain/season/_team/controller/AdminTeamController.java (1)
AdminGuard(18-66)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (1)
Service(15-59)
src/main/java/com/github/kmu_shell_we/domain/season/entity/Season.java (3)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/entity/SeasonMission.java (1)
Entity(9-26)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/service/AdminSeasonMissionService.java (1)
Service(15-59)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/repository/SeasonMissionRepository.java (1)
Nonnull(23-26)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/controller/TeamScheduleController.java (2)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/service/TeamScheduleService.java (3)
Service(20-42)verifyUserTeam(36-41)Transactional(29-34)src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/dto/response/TeamScheduleResponse.java (1)
Data(9-15)
src/main/java/com/github/kmu_shell_we/domain/user/entity/User.java (7)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/entity/UserTeam.java (1)
Entity(10-29)src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/dto/response/UserSchedulePair.java (1)
Data(16-37)src/main/java/com/github/kmu_shell_we/domain/user/_schedule/entity/Schedule.java (1)
Entity(15-60)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/service/AdminUserTeamService.java (1)
Service(14-41)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/repository/UserTeamRepository.java (1)
UserTeamRepository(12-17)src/main/java/com/github/kmu_shell_we/domain/user/_schedule/repository/ScheduleRepository.java (1)
findAllByUser(12-12)src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/service/TeamScheduleService.java (1)
Service(20-42)
src/main/java/com/github/kmu_shell_we/domain/mission/entity/Mission.java (2)
src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/entity/SeasonMission.java (1)
Entity(9-26)src/main/java/com/github/kmu_shell_we/domain/mission/_season_mission/repository/SeasonMissionRepository.java (1)
Nonnull(23-26)
src/main/java/com/github/kmu_shell_we/domain/season/_team/entity/Team.java (3)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/entity/UserTeam.java (1)
Entity(10-29)src/main/java/com/github/kmu_shell_we/domain/season/_team/_user_team/repository/UserTeamRepository.java (1)
UserTeamRepository(12-17)src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/repository/ItemRepository.java (1)
ItemRepository(10-13)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionResponse.java (3)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionResponse.java (1)
Data(9-27)src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionListResponse.java (1)
Data(10-22)src/main/java/com/github/kmu_shell_we/domain/mission/dto/response/MissionResponse.java (1)
Data(12-41)
🔇 Additional comments (16)
src/main/java/com/github/kmu_shell_we/domain/season/_team/_schedule/controller/TeamScheduleController.java (1)
12-12: Swagger ArraySchema import 추가 적합문서 스키마 명확화에 필요한 import가 정확히 추가되었습니다. 별도 이슈 없습니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/exceptions/TeamMissionExceptions.java (1)
12-12: 새 예외 코드 추가 LGTM — 시간 검증 전역 적용 여부 확인
INVALID_MISSION_TIME도입은 적절합니다. startedAt < endedAt, 시간대(UTC/Asia/Seoul) 처리, null 허용 정책 등에 대해 서비스/밸리데이터가 이 코드를 일관되게 던지는지 확인 부탁드립니다. 테스트도 추가해 주세요.src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/request/CreateSubmissionRequest.java (1)
1-1: 패키지 이동 LGTM — 잔존 참조 재검증 필요입력하신 rg 명령이 출력 없이 종료되어 잔존 참조 유무를 확정할 수 없습니다. 컨트롤러/서비스/테스트에서 새 FQN으로 모두 갱신되었는지 직접 확인하세요.
# 모든 참조(심볼) 검색 rg -n --hidden -S '\bCreateSubmissionRequest\b' # 새 패키지의 정확한 import 검색 (파일의 package 선언 기준) rg -nP 'import\s+com\.github\.kmu_shell_we\.domain\.season\._team\._team_mission\._submission\.dto\.request\.CreateSubmissionRequest' -S # 폭넓은 FQN 검색(오타나 이전 패키지 포착용) rg -nP 'com\.github\.kmu_shell_we\.domain\.season\.[\w._]*CreateSubmissionRequest' -Ssrc/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionListResponse.java (1)
1-1: 패키지/임포트 정리 LGTM — toList() 사용 확인 완료 (JDK 21 타겟)
build.gradle의 java.toolchain이 JavaLanguageVersion.of(21)로 설정되어 있어 Stream#toList() 사용이 안전합니다.src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionListResponse.java (1)
1-1: 패키지/임포트 정리 LGTM변경 사항 문제없습니다.
Also applies to: 3-3
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/entity/Submission.java (1)
1-3: 패키지/임포트 리네이밍 적절네이밍 정리가 일관성을 높입니다. 추가 동작 변경 없어서 영향도 낮습니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/TeamMissionController.java (1)
1-5: 패키지/임포트 리팩터링 적절네임스페이스 정돈으로 가독성/탐색성이 좋아졌습니다. 컨트롤러 시그니처 변화 없음 확인했습니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/dto/response/SubmissionResponse.java (1)
1-4: 패키지 정리 LGTM의존 경로 일관화 OK. 동작 변경 없음.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java (2)
46-49: 활성 구간 조회 로직 전환 LGTM단일 now로 startedAt/endedAt 윈도우 필터링하는 변경 적절합니다.
152-156: 팀 접근 체크 간결하고 명확함현재 구현(내 팀 소속 여부 확인) 적절합니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/controller/ItemController.java (1)
31-36: 인증 사용자 컨텍스트 전달 변경 LGTM@AuthenticationPrincipal 연결과 서비스 인자 업데이트 일관적입니다. 컨트롤러 단에서 추가 검증은 서비스 canAccess 로 위임된 것으로 보아 현재 수준 적절합니다.
Also applies to: 41-46
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/entity/TeamMission.java (1)
30-33: startedAt 필드 추가 타당활성 구간 윈도우 쿼리를 위해 @NotNull startedAt 추가 적절합니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/dto/response/TeamMissionResponse.java (1)
31-36: 매핑 LGTM엔티티 → DTO 변환에서 startedAt/endedAt 전달이 정확합니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java (1)
118-123: 접근 제어 LGTM팀 소속·시즌 동시 검증으로 권한 경계가 명확합니다.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java (1)
53-64: 활성 팀미션 검증 추가 필요File: src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java (lines 53-64)
submissionRepository.findByTeamMission(teamMission).ifPresent(submission -> { throw TeamMissionExceptions.ALREADY_SUBMITTED.toException(); }); Submission submission = submissionRepository.save( Submission.builder() .teamMission(teamMission) .image(request.getImage()) .build() );제안된 패치:
+ // 활성 팀 미션인지 확인 (현재 시간 기준) + var now = java.time.LocalDateTime.now(); + var current = teamMissionRepository.findCurrentMission(team, now) + .orElseThrow(TeamMissionExceptions.NOT_FOUND_TEAM_MISSION::toException); + if (!current.getId().equals(teamMission.getId())) { + throw TeamMissionExceptions.NOT_FOUND_TEAM_MISSION.toException(); + }확인 요청:
- teamMissionRepository.findCurrentMission의 활성 조건이 startedAt/endedAt 기준으로 올바르게 정의되어 있는지(특히 endedAt 포함 여부) 확인.
- 제출 정책이 "활성 상태인 팀 미션에만 제출 허용"과 일치하는지(비활성 미션에 대한 제출 차단) 확인.
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/controller/SubmissionController.java (1)
43-53: 컨트롤러 연계 LGTMUser 주입 및 TeamMission 기반 라우팅 전환이 서비스 변경과 일관됩니다.
| // 0. 포인트 조회 및 차감 | ||
| if (team.getPoint() < 100) { | ||
|
|
||
| throw ItemExceptions.NOT_ENOUGH_POINT.toException(); | ||
| } | ||
|
|
||
| team.setPoint(team.getPoint() - 100); | ||
|
|
There was a problem hiding this comment.
동시성 경쟁으로 포인트 음수/과차감 가능성 높음
현재 team.getPoint() 검사 후 차감까지 원자성이 보장되지 않아 동시에 호출되면 둘 다 통과해 과차감이 발생할 수 있습니다. DB 레벨에서 원자적으로 갱신하거나 행 잠금을 적용해 주세요.
두 가지 중 한 가지로 보완을 권장합니다.
- 원자적 업데이트 쿼리
- if (team.getPoint() < 100) {
- throw ItemExceptions.NOT_ENOUGH_POINT.toException();
- }
- team.setPoint(team.getPoint() - 100);
+ int updated = teamRepository.decrementPointIfEnough(team.getId(), 100); // UPDATE team SET point = point - 100 WHERE id = :id AND point >= 100
+ if (updated == 0) {
+ throw ItemExceptions.NOT_ENOUGH_POINT.toException();
+ }- 또는 PESSIMISTIC_WRITE 잠금(대안)
TeamRepository에@Lock(PESSIMISTIC_WRITE)메서드 추가 후 서비스에서 재조회하여 차감.- 혹은 Team 엔티티에
@Version추가(낙관적 잠금) + 재시도 로직.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_item/service/ItemService.java
around lines 54-61 the current check-then-update on team.getPoint() is not
atomic and allows race conditions; fix it by implementing an atomic DB-side
update or a row lock: Option A (recommended): add a TeamRepository method with
@Modifying and a JPQL/SQL update like "update Team t set t.point = t.point - 100
where t.id = :id and t.point >= 100" that returns the number of rows updated; in
the service call that method and throw NOT_ENOUGH_POINT if updated count is 0.
Option B (alternative): add a repository finder annotated with
@Lock(PESSIMISTIC_WRITE) to re-load the Team inside a transactional method, then
perform the point check and subtract and save; or add @Version to Team and
implement an optimistic retry loop catching OptimisticLockException — implement
one of these approaches and remove the non-atomic getPoint() then setPoint(...)
sequence.
|
|
||
| public interface SubmissionRepository extends JpaRepository<Submission, UUID> { | ||
|
|
||
| Optional<Submission> findByTeamMission(TeamMission teamMission); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
단건 조회는 1:1 제약 전제 — DB 유니크 제약 추가 권장
findByTeamMission이 Optional 단건을 반환하므로 Submission:TeamMission이 1:1임을 암시합니다. 스키마에 유니크 제약을 추가해 데이터 정합성을 보장하세요.
예) Submission 엔티티
@Table(
uniqueConstraints = @UniqueConstraint(columnNames = "team_mission_id")
)또는 레포에 존재 여부 체크 추가:
public interface SubmissionRepository extends JpaRepository<Submission, UUID> {
Optional<Submission> findByTeamMission(TeamMission teamMission);
+ boolean existsByTeamMission(TeamMission teamMission);
}🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== repo root: $(pwd) ==="
echo
echo "=== Locate SubmissionRepository.java ==="
rg -n --hidden --glob 'src/**' 'SubmissionRepository\.java' -S || true
echo
REPO_PATH="src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java"
if [ -f "$REPO_PATH" ]; then
echo "=== Showing $REPO_PATH ==="
sed -n '1,240p' "$REPO_PATH"
else
echo "SubmissionRepository not found at the expected path. Searching for interfaces named SubmissionRepository..."
rg -n --hidden --glob 'src/**' 'interface\s+SubmissionRepository\b' -S || true
for f in $(rg -n --hidden --glob 'src/**' 'interface\s+SubmissionRepository\b' -S | cut -d: -f1 | sort -u); do
echo "---- $f ----"
sed -n '1,240p' "$f"
done
fi
echo
echo "=== Find Submission entity files ==="
rg -n --hidden --glob 'src/**' '\bclass\s+Submission\b' -S || true
for f in $(rg -n --hidden --glob 'src/**' '\bclass\s+Submission\b' -S | cut -d: -f1 | sort -u); do
echo "---- $f ----"
sed -n '1,320p' "$f"
done
echo
echo "=== Search for @Table uniqueConstraints / team_mission_id / @Column(name = \"team_mission_id\") ==="
rg -n --hidden --glob 'src/**' 'uniqueConstraints|team_mission_id|@Table|@Column\s*\(\s*name\s*=\s*["'\'' ]*team_mission_id' -S || true
echo
echo "=== Search for existsByTeamMission in repo ==="
rg -n --hidden --glob 'src/**' 'existsByTeamMission' -S || trueLength of output: 2569
DB 유니크 제약 추가 필요 — Submission:TeamMission(1:1) 관계 보장
findByTeamMission이 Optional을 반환하고 Submission 엔티티는 @OnetoOne@JoinColumn(name = "mission_team_id", nullable = false)으로 선언되어 있지만 DB 수준의 unique 제약이 없어 같은 TeamMission을 여러 Submission이 참조할 수 있습니다. DB 유니크 제약을 추가하거나 JPA에 unique=true를 명시하세요.
-
엔티티 수정 (src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/entity/Submission.java)
- 옵션 A: @joincolumn에 unique 추가
@joincolumn(name = "mission_team_id", nullable = false, unique = true) - 옵션 B: 클래스 레벨로 유니크 제약 추가
@table(uniqueConstraints = @UniqueConstraint(columnNames = {"mission_team_id"}))
- 옵션 A: @joincolumn에 unique 추가
-
레포지토리 보완 (src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java)
- 존재 여부 검사 메서드 추가:
boolean existsByTeamMission(TeamMission teamMission);
- 존재 여부 검사 메서드 추가:
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/repository/SubmissionRepository.java
around line 14 and the Submission entity file, add a DB-level unique constraint
for the mission_team_id to enforce the 1:1 relationship and add an existence
check in the repository: update the Submission entity either by marking the
@JoinColumn(name = "mission_team_id", nullable = false, unique = true) or add a
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"mission_team_id"}))
at class level, then in SubmissionRepository add a boolean
existsByTeamMission(TeamMission teamMission) method; this ensures the database
prevents multiple Submissions for the same TeamMission and lets callers cheaply
check existence.
| @Transactional(readOnly = true) | ||
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team") | ||
| public SubmissionResponse getSubmission(User user, Season season, Team team, TeamMission teamMission) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
SpEL에서 엔티티 동등성 비교는 ID로 비교 권장
프록시/영속성 컨텍스트 차이로 객체 동등성 비교가 실패할 수 있습니다. ID 기반 비교로 바꿔주세요.
- @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team")
+ @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team.id == #team.id")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional(readOnly = true) | |
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team") | |
| public SubmissionResponse getSubmission(User user, Season season, Team team, TeamMission teamMission) { | |
| @Transactional(readOnly = true) | |
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team.id == #team.id") | |
| public SubmissionResponse getSubmission(User user, Season season, Team team, TeamMission teamMission) { |
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java
around lines 41-43, the SpEL expression compares entity instances which can fail
with proxies or different persistence contexts; change the expression to compare
IDs (e.g., replace "#teamMission.team == #team" with "#teamMission.team.id ==
#team.id") so authorization uses ID-based equality instead of object identity.
| @Transactional | ||
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team") | ||
| public SubmissionResponse submitSubmission(User user, Season season, Team team, TeamMission teamMission, CreateSubmissionRequest request) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
동일: ID 비교로 정정
- @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team")
+ @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team.id == #team.id")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team == #team") | |
| public SubmissionResponse submitSubmission(User user, Season season, Team team, TeamMission teamMission, CreateSubmissionRequest request) { | |
| @Transactional | |
| @PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and #teamMission.team.id == #team.id") | |
| public SubmissionResponse submitSubmission(User user, Season season, Team team, TeamMission teamMission, CreateSubmissionRequest request) { |
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java
around lines 50 to 52, the @PreAuthorize expression compares entity objects
(#teamMission.team == #team) which should be changed to compare their
identifiers; update the expression to compare IDs (for example:
@PreAuthorize("@submissionService.canAccessSubmission(#user, #season, #team) and
#teamMission.team.id == #team.id") or the correct ID property names used in your
models) so the authorization check uses ID equality instead of object reference
equality.
| submissionRepository.findByTeamMission(teamMission).ifPresent(submission -> { | ||
| throw TeamMissionExceptions.ALREADY_SUBMITTED.toException(); | ||
| }); | ||
|
|
There was a problem hiding this comment.
중복 제출 경쟁 상태: DB 유니크 제약과 예외 변환 필요
사전 조회 → 저장 사이 경쟁으로 중복 레코드가 생성될 수 있습니다. submission.team_mission에 유니크 인덱스를 추가하고, 저장 시 무결성 위반을 잡아 ALREADY_SUBMITTED로 변환하세요.
- submissionRepository.findByTeamMission(teamMission).ifPresent(submission -> {
- throw TeamMissionExceptions.ALREADY_SUBMITTED.toException();
- });
-
- Submission submission = submissionRepository.save(
- Submission.builder()
- .teamMission(teamMission)
- .image(request.getImage())
- .build()
- );
-
- return SubmissionResponse.from(submission);
+ submissionRepository.findByTeamMission(teamMission).ifPresent(s -> {
+ throw TeamMissionExceptions.ALREADY_SUBMITTED.toException();
+ });
+ try {
+ Submission submission = submissionRepository.save(
+ Submission.builder()
+ .teamMission(teamMission)
+ .image(request.getImage())
+ .build()
+ );
+ return SubmissionResponse.from(submission);
+ } catch (org.springframework.dao.DataIntegrityViolationException e) {
+ // 유니크 제약 위반 → 중복 제출
+ throw TeamMissionExceptions.ALREADY_SUBMITTED.toException();
+ }원하시면 DDL(유니크 인덱스) 패치 스니펫도 드리겠습니다.
Also applies to: 58-66
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/_submission/service/SubmissionService.java
around lines 54-57 (and also apply same change to 58-66), the current pre-check
findByTeamMission(...) can race with concurrent saves and allow duplicate
submissions; add a DB-level unique index on submission.team_mission (create a
migration / DDL to add UNIQUE constraint on the team_mission column) and keep or
remove the pre-check but crucially wrap repository.save(...) in a try/catch that
catches the persistence integrity exception (e.g.,
org.springframework.dao.DataIntegrityViolationException or
javax.persistence.ConstraintViolationException depending on stack) and translate
that exception into TeamMissionExceptions.ALREADY_SUBMITTED.toException();
ensure you only translate constraint-violation cases and rethrow other
exceptions.
| @@ -0,0 +1,47 @@ | |||
| package com.github.kmu_shell_we.domain.season._team._mission_team.controller; | |||
There was a problem hiding this comment.
패키지 경로 불일치로 컴파일 불가
디렉터리는 _team_mission/controller인데 package는 _mission_team.controller입니다. _team_mission로 정정하세요.
-package com.github.kmu_shell_we.domain.season._team._mission_team.controller;
+package com.github.kmu_shell_we.domain.season._team._team_mission.controller;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| package com.github.kmu_shell_we.domain.season._team._mission_team.controller; | |
| package com.github.kmu_shell_we.domain.season._team._team_mission.controller; |
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_mission_team/controller/AdminTeamMissionController.java
around line 1, the package declaration does not match the directory (directory
is _team_mission/controller but package is _mission_team.controller); update the
package declaration to use
com.github.kmu_shell_we.domain.season._team._mission.controller so it matches
the directory structure and restores successful compilation.
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | ||
| import com.github.kmu_shell_we.domain.season._team._mission_team.dto.request.CreateSpecialTeamMissionRequest; | ||
| import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse; | ||
| import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission; | ||
| import com.github.kmu_shell_we.domain.season._team._mission_team.service.TeamMissionService; | ||
| import com.github.kmu_shell_we.domain.season.entity.Season; | ||
| import com.github.kmu_shell_we.global.response.ApiResponse; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
이전 패키지 import 사용 중
엔티티/서비스/DTO import를 _team_mission로 교체하세요.
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.request.CreateSpecialTeamMissionRequest;
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse;
-import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission;
-import com.github.kmu_shell_we.domain.season._team._mission_team.service.TeamMissionService;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.request.CreateSpecialTeamMissionRequest;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse;
+import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission;
+import com.github.kmu_shell_we.domain.season._team._team_mission.service.TeamMissionService;Also applies to: 19-26, 30-46
🏁 Script executed:
#!/bin/bash
# 컨트롤러에서 _mission_team import 남아있는지 재확인
rg -n 'import .+_mission_team\.' src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controllerLength of output: 858
Import 경로 수정 필요 — _mission_team → _team_mission
AdminTeamMissionController 파일에 아직 _mission_team 패키지 import(라인 4–7)가 남아 있습니다. 아래 대체로 변경하세요.
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.request.CreateSpecialTeamMissionRequest;
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse;
-import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission;
-import com.github.kmu_shell_we.domain.season._team._mission_team.service.TeamMissionService;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.request.CreateSpecialTeamMissionRequest;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse;
+import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission;
+import com.github.kmu_shell_we.domain.season._team._team_mission.service.TeamMissionService;파일: src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java (라인 4–7)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | |
| import com.github.kmu_shell_we.domain.season._team._mission_team.dto.request.CreateSpecialTeamMissionRequest; | |
| import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse; | |
| import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission; | |
| import com.github.kmu_shell_we.domain.season._team._mission_team.service.TeamMissionService; | |
| import com.github.kmu_shell_we.domain.season.entity.Season; | |
| import com.github.kmu_shell_we.global.response.ApiResponse; | |
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.dto.request.CreateSpecialTeamMissionRequest; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.service.TeamMissionService; | |
| import com.github.kmu_shell_we.domain.season.entity.Season; | |
| import com.github.kmu_shell_we.global.response.ApiResponse; |
🤖 Prompt for AI Agents
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java
(lines 4-7): 현재 import 경로에 `_mission_team` 패키지를 참조하고 있으므로,
CreateSpecialTeamMissionRequest, TeamMissionListResponse, TeamMission,
TeamMissionService 네 개의 import 경로를 `_mission_team` 대신 `_team_mission`으로 수정하여 올바른
패키지로 가져오도록 변경하세요.
| List<TeamMission> findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(Team team, LocalDateTime now, LocalDateTime now1); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복 생성 방지용 exists 쿼리 추가 제안
스페셜 미션 생성 시 겹치는 기간 중복 생성을 막기 위한 파생 쿼리를 추가하세요.
List<TeamMission> findAllByTeamAndStartedAtLessThanEqualAndEndedAtGreaterThanEqual(Team team, LocalDateTime now, LocalDateTime now1);
+
+boolean existsByTeamAndMissionAndEndedAtGreaterThanEqualAndStartedAtLessThanEqual(
+ Team team,
+ Mission mission,
+ LocalDateTime startedAt,
+ LocalDateTime endedAt
+);활성 구간 조회가 잦다면 (team_id, startedAt, endedAt) 복합 인덱스를 고려하세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java
around lines 26-27, add a derived exists query that detects overlapping missions
to prevent duplicate creation by checking existing.startedAt <= newEnded AND
existing.endedAt >= newStarted (e.g. an existsBy... method returning boolean and
taking Team, newEnd, newStart in that order), or provide an equivalent @Query if
derived naming is awkward; also add a note to create a composite DB index on
(team_id, started_at, ended_at) if active-range lookups are frequent to improve
performance.
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | ||
| import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
엔티티/DTO/레포지토리 import 경로가 이전 패키지로 남아 있음
엔티티와 DTO가 _team_mission로 이전되었는데 import는 여전히 _mission_team를 가리킵니다. 컴파일 에러 납니다. 레포지토리 패키지도 통일 필요.
-import com.github.kmu_shell_we.domain.mission.entity.Mission;
-import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions;
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse;
-import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission;
-import com.github.kmu_shell_we.domain.season._team._mission_team.exceptions.TeamMissionExceptions;
-import com.github.kmu_shell_we.domain.season._team._mission_team.repository.TeamMissionRepository;
+import com.github.kmu_shell_we.domain.mission.entity.Mission;
+import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse;
+import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission;
+import com.github.kmu_shell_we.domain.season._team._team_mission.exceptions.TeamMissionExceptions;
+import com.github.kmu_shell_we.domain.season._team._team_mission.repository.TeamMissionRepository;다음 스크립트로 패키지 불일치 지점을 빠르게 스캔하세요.
Also applies to: 10-11, 13-14
🏁 Script executed:
#!/bin/bash
# 1) 실제 디렉터리 아래에 'mission_team' 패키지 선언이 남아있는지
rg -n '^package .+_mission_team\.' src/main/java/**/_team_mission/**
# 2) import에서 _mission_team 참조가 남아있는지
rg -n 'import .+_mission_team\.' src/main/java
# 3) TeamMission 엔티티의 실제 패키지
rg -n '^package .+TeamMission' src/main/java | sed -n '1p'Length of output: 1941
잘못된 패키지 import: _mission_team → _team_mission (컴파일 오류 발생)
엔티티/DTO/레포지토리 import가 이전 패키지명(_mission_team)으로 남아 있어 컴파일 오류가 납니다. 아래 파일들의 import를 com.github.kmu_shell_we.domain.season._team._team_mission.* 으로 변경하세요.
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java:4
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java:8-11
- src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java:4-7
-import com.github.kmu_shell_we.domain.mission.entity.Mission;
-import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions;
-import com.github.kmu_shell_we.domain.season._team._mission_team.dto.response.TeamMissionListResponse;
-import com.github.kmu_shell_we.domain.season._team._mission_team.entity.TeamMission;
-import com.github.kmu_shell_we.domain.season._team._mission_team.exceptions.TeamMissionExceptions;
-import com.github.kmu_shell_we.domain.season._team._mission_team.repository.TeamMissionRepository;
+import com.github.kmu_shell_we.domain.mission.entity.Mission;
+import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions;
+import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse;
+import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission;
+import com.github.kmu_shell_we.domain.season._team._team_mission.exceptions.TeamMissionExceptions;
+import com.github.kmu_shell_we.domain.season._team._team_mission.repository.TeamMissionRepository;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | |
| import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions; | |
| import com.github.kmu_shell_we.domain.mission.entity.Mission; | |
| import com.github.kmu_shell_we.domain.mission.exception.MissionExceptions; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.dto.response.TeamMissionListResponse; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.entity.TeamMission; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.exceptions.TeamMissionExceptions; | |
| import com.github.kmu_shell_we.domain.season._team._team_mission.repository.TeamMissionRepository; |
🤖 Prompt for AI Agents
In
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/service/TeamMissionService.java
around lines 6-7, the imports reference the old _mission_team package; update
the entity/DTO/repository imports to use
com.github.kmu_shell_we.domain.season._team._team_mission.* instead. Also apply
the same change in the two other files noted:
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/repository/TeamMissionRepository.java
(line 4) and
src/main/java/com/github/kmu_shell_we/domain/season/_team/_team_mission/controller/AdminTeamMissionController.java
(lines 4-7) so all imports point to
com.github.kmu_shell_we.domain.season._team._team_mission.{appropriate classes}
to fix the compilation errors.
| @Transactional | ||
| public List<TeamMission> createSpecialMission( | ||
| Season season, | ||
| Mission mission, | ||
| LocalDateTime startedAt, | ||
| LocalDateTime endedAt | ||
| ) { | ||
|
|
||
| if (startedAt == null || endedAt == null || !endedAt.isAfter(startedAt)) | ||
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | ||
|
|
||
| LocalDateTime seasonStart = season.getStartedAt(); | ||
| LocalDateTime seasonEnd = season.getEndedAt(); | ||
| if (seasonStart != null && startedAt.isBefore(seasonStart)) | ||
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | ||
| if (seasonEnd != null && endedAt.isAfter(seasonEnd)) | ||
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | ||
|
|
||
| if (!season.isCurrentSeason()) | ||
| throw SeasonExceptions.NOT_FOUND_CURRENT_SEASON.toException(); | ||
|
|
||
| if (mission == null) | ||
| throw MissionExceptions.NOT_FOUND_MISSION.toException(); | ||
|
|
||
| if (mission.getType() != MissionType.SPECIAL) | ||
| throw MissionExceptions.INVALID_SPECIAL_MISSION_TYPE.toException(); | ||
|
|
||
| List<Team> teams = season.getTeams(); | ||
| if (teams.isEmpty()) | ||
| throw TeamExceptions.NOT_FOUND_TEAM.toException(); | ||
|
|
||
| List<TeamMission> teamMissions = new ArrayList<>(); | ||
| for (Team team : teams) { | ||
| teamMissions.add(TeamMission.builder() | ||
| .mission(mission) | ||
| .team(team) | ||
| .startedAt(startedAt) | ||
| .endedAt(endedAt) | ||
| .build()); | ||
| } | ||
|
|
||
| return teamMissionRepository.saveAll(teamMissions); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
스페셜 미션 중복 생성 방지(Idempotency) 추가 권장
동일 팀/미션에 시간이 겹치는 TeamMission이 이미 있으면 중복 생성됩니다. 운영 중 실수/재시도에서 중복 데이터가 생깁니다. 겹침 여부 exists 쿼리를 추가하고, 발견 시 skip 또는 예외 처리하세요.
List<TeamMission> teamMissions = new ArrayList<>();
for (Team team : teams) {
+ // 동일 구간(or 겹치는 구간) 중복 생성 방지
+ if (teamMissionRepository
+ .existsByTeamAndMissionAndEndedAtGreaterThanEqualAndStartedAtLessThanEqual(
+ team, mission, startedAt, endedAt)) {
+ continue;
+ }
teamMissions.add(TeamMission.builder()
.mission(mission)
.team(team)
.startedAt(startedAt)
.endedAt(endedAt)
.build());
}레포지토리에 아래 시그니처도 추가해 주세요(아래 파일 코멘트에 diff 포함).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| public List<TeamMission> createSpecialMission( | |
| Season season, | |
| Mission mission, | |
| LocalDateTime startedAt, | |
| LocalDateTime endedAt | |
| ) { | |
| if (startedAt == null || endedAt == null || !endedAt.isAfter(startedAt)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| LocalDateTime seasonStart = season.getStartedAt(); | |
| LocalDateTime seasonEnd = season.getEndedAt(); | |
| if (seasonStart != null && startedAt.isBefore(seasonStart)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| if (seasonEnd != null && endedAt.isAfter(seasonEnd)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| if (!season.isCurrentSeason()) | |
| throw SeasonExceptions.NOT_FOUND_CURRENT_SEASON.toException(); | |
| if (mission == null) | |
| throw MissionExceptions.NOT_FOUND_MISSION.toException(); | |
| if (mission.getType() != MissionType.SPECIAL) | |
| throw MissionExceptions.INVALID_SPECIAL_MISSION_TYPE.toException(); | |
| List<Team> teams = season.getTeams(); | |
| if (teams.isEmpty()) | |
| throw TeamExceptions.NOT_FOUND_TEAM.toException(); | |
| List<TeamMission> teamMissions = new ArrayList<>(); | |
| for (Team team : teams) { | |
| teamMissions.add(TeamMission.builder() | |
| .mission(mission) | |
| .team(team) | |
| .startedAt(startedAt) | |
| .endedAt(endedAt) | |
| .build()); | |
| } | |
| return teamMissionRepository.saveAll(teamMissions); | |
| } | |
| @Transactional | |
| public List<TeamMission> createSpecialMission( | |
| Season season, | |
| Mission mission, | |
| LocalDateTime startedAt, | |
| LocalDateTime endedAt | |
| ) { | |
| if (startedAt == null || endedAt == null || !endedAt.isAfter(startedAt)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| LocalDateTime seasonStart = season.getStartedAt(); | |
| LocalDateTime seasonEnd = season.getEndedAt(); | |
| if (seasonStart != null && startedAt.isBefore(seasonStart)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| if (seasonEnd != null && endedAt.isAfter(seasonEnd)) | |
| throw TeamMissionExceptions.INVALID_MISSION_TIME.toException(); | |
| if (!season.isCurrentSeason()) | |
| throw SeasonExceptions.NOT_FOUND_CURRENT_SEASON.toException(); | |
| if (mission == null) | |
| throw MissionExceptions.NOT_FOUND_MISSION.toException(); | |
| if (mission.getType() != MissionType.SPECIAL) | |
| throw MissionExceptions.INVALID_SPECIAL_MISSION_TYPE.toException(); | |
| List<Team> teams = season.getTeams(); | |
| if (teams.isEmpty()) | |
| throw TeamExceptions.NOT_FOUND_TEAM.toException(); | |
| List<TeamMission> teamMissions = new ArrayList<>(); | |
| for (Team team : teams) { | |
| // 동일 구간(or 겹치는 구간) 중복 생성 방지 | |
| if (teamMissionRepository | |
| .existsByTeamAndMissionAndEndedAtGreaterThanEqualAndStartedAtLessThanEqual( | |
| team, mission, startedAt, endedAt)) { | |
| continue; | |
| } | |
| teamMissions.add(TeamMission.builder() | |
| .mission(mission) | |
| .team(team) | |
| .startedAt(startedAt) | |
| .endedAt(endedAt) | |
| .build()); | |
| } | |
| return teamMissionRepository.saveAll(teamMissions); | |
| } |
📋 변경사항 요약
관리자가 특별 미션을 배정하는 즉시 해당 학기 모든 팀에 팀 미션이 생성되고, startedAt 값에 따라 활성화/비활성화 가능합니다.
🎯 변경 이유
🔧 주요 변경사항
🔍 검토 포인트
✅ 체크리스트
📝 추가 노트
Summary by CodeRabbit
New Features
Refactor
Documentation