-
Notifications
You must be signed in to change notification settings - Fork 1
feat(core-attendance): 코어 회의 출석 기능 추가 #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 출석 관리용 도메인 엔티티 및 DTO 구현 (Team, Member, AttendanceRecord 등) - CoreAttendanceController / Service 계층 추가 - 날짜 관리 (추가/삭제), 팀원 관리(추가/수정/삭제), 출석 체크(개별/전체) 기능 구현 - 출석 요약 API(summary) 구현 및 DaySummaryResponse 응답 DTO 적용 - 임시 버전에서는 권한 검증 제거, query parameter(leadName, teamId) 기반 필터링 처리 - CoreAttendanceMessage 상수 추가 (성공 메시지 일괄 관리)
Walkthrough핵심 출석 도메인 추가: 컨트롤러/서비스/리포지토리/엔티티/DTO를 신규 도입해 날짜, 팀, 팀원, 출석 기록 CRUD 및 요약 조회 API를 구현. 메시지 상수 클래스 추가. .gitignore에 .DS_Store 무시 규칙 추가. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Controller as CoreAttendanceController
participant Service as CoreAttendanceService
participant TeamRepo as TeamRepository
participant MemberRepo as MemberRepository
participant RecRepo as AttendanceRecordRepository
rect rgb(245,248,255)
note over Client,Controller: 날짜 생성
Client->>Controller: POST /api/v1/core-attendance/dates
Controller->>Service: addDate(date)
Service->>RecRepo: (옵션) 초기화/조회 없음
Service-->>Controller: void
Controller-->>Client: ApiResponse(DateListResponse)
end
rect rgb(245,255,245)
note over Client,RecRepo: 단일 출석 설정
Client->>Controller: PUT /records/one (date, teamId, memberId, present)
Controller->>Service: setAttendance(date, teamId, memberId, present)
Service->>TeamRepo: findById(teamId)
Service->>MemberRepo: find(team, memberId)
Service->>RecRepo: setPresence(dkey, teamId, memberId, present)
Service-->>Controller: void
Controller-->>Client: ApiResponse("OK")
end
rect rgb(255,248,240)
note over Client,Service: 일자 요약 조회
Client->>Controller: GET /summary?date[&leadName][&teamId]
Controller->>Service: summary(date, leadName, teamId)
Service->>TeamRepo: findAll()/findById
Service->>RecRepo: getDay(dkey)
Service-->>Controller: DaySummaryResponse
Controller-->>Client: ApiResponse(DaySummaryResponse)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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. 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (32)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java (1)
1-13: SetAttendanceRequest와 중복 구조 정리 제안.두 DTO가 동일한 단일 필드 구조입니다. 하나로 통합하거나 공용 타입(예: PresentRequest)으로 재사용하면 API/검증 일관성이 좋아집니다.
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (1)
4-16: 유효성 강화: name/lead 길이 제한 및 메시지 일관화.필드 길이 제한을 추가하고 한글 메시지 톤을 통일하면 입력 검증이 명확해집니다.
package inha.gdgoc.domain.core.attendance.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @Getter @NoArgsConstructor @AllArgsConstructor public class CreateTeamRequest { - @NotBlank + @NotBlank(message = "팀 이름은 비어 있을 수 없습니다.") + @Size(max = 50, message = "팀 이름은 최대 50자까지 입력 가능합니다.") private String name; + @Size(max = 50, message = "리드 이름은 최대 50자까지 입력 가능합니다.") private String lead; }src/main/java/inha/gdgoc/domain/core/attendance/repository/TeamRepository.java (1)
10-15: 리포지토리 설계 다듬기 제안.
- 인터페이스의 @repository는 구현체로 이동하는 편이 일반적입니다(선택).
- findAll은 호출측에서 순서를 기대할 수 있으므로 List 반환이 더 명시적입니다(선택).
-@Repository -public interface TeamRepository { +public interface TeamRepository { Team save(Team team); Optional<Team> findById(String id); - Collection<Team> findAll(); + List<Team> findAll(); void deleteById(String id); }-import java.util.Collection; +import java.util.List; import java.util.Optional;src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DateListResponse.java (1)
8-15: 타입 안정성 향상 제안: 문자열 대신 LocalDate 사용.서버 내부는 날짜 타입으로 다루고 JSON 포맷은 ObjectMapper 설정으로 제어하는 편이 안전합니다.
import java.util.List; +import java.time.LocalDate; @Getter @NoArgsConstructor @AllArgsConstructor public class DateListResponse { - private List<String> dates; + private List<LocalDate> dates; }src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (3)
10-11: name 세터에 널/공백 정규화 로직이 없습니다.입력 정상화가 필요하면 Lombok @Setter를 제거하고 커스텀 세터로 대체하는 편이 안전합니다.
다음처럼 적용을 고려해 주세요:
-@Setter -private String name; +private String name; + +public void setName(String name) { + this.name = name == null ? "" : name.trim(); +}
13-13: 생성자에서 널 가드 추가 권장.id/name에 대해 널 입력을 방지하면 이후 NPE 리스크를 줄일 수 있습니다.
- public Member(String id, String name){ this.id = id; this.name = name; } + public Member(String id, String name){ + if (id == null) throw new IllegalArgumentException("id must not be null"); + this.id = id; + this.name = name == null ? "" : name; + }
8-13: 동등성/해시코드(id 기반) 구현 제안.컬렉션(예: Team.members)에서 중복 제어/검색 안정성을 위해 id 기반 equals/hashCode 구현을 권장합니다.
public class Member { @@ public Member(String id, String name){ this.id = id; this.name = name; } + + @Override public boolean equals(Object o){ + if (this == o) return true; + if (!(o instanceof Member m)) return false; + return id.equals(m.id); + } + @Override public int hashCode(){ return id.hashCode(); } }src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (2)
14-17: members 필드 널 안전성 보강.응답에서 members가 null이면 클라이언트 단에서 NPE가 발생할 수 있습니다. 기본값을 빈 리스트로 초기화하는 편이 안전합니다.
-import java.util.List; +import java.util.List; +import java.util.ArrayList; @@ - private List<MemberResponse> members; + private List<MemberResponse> members = new ArrayList<>();
13-17: DTO 불변성/생성 편의성 보강(선택).src/main/java/inha/gdgoc/domain/core/attendance/repository/MemberRepository.java (1)
10-11: 인터페이스에 @repository 부착은 혼동 소지.실제 구현체에 @repository를 붙이고 여기서는 제거하는 편이 관례에 맞습니다. (선택)
-@Repository public interface MemberRepository {src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java (2)
9-15: Jackson 바인딩 호환성 위해 @Setter 추가 권장.환경에 따라 기본 생성자 + private 필드만으로는 역직렬화가 실패할 수 있습니다. @Setter를 추가해 안전성을 높이는 것을 권장합니다.
-import lombok.Getter; +import lombok.Getter; +import lombok.Setter; @@ -@Getter +@Getter +@Setter public class AddMemberRequest { @NotBlank private String name; }
13-14: 입력 검증 범위 확장(선택).@SiZe, 공백 트림 등 추가 제약을 고려해 주세요. 서비스 레이어에서 최종 검증도 병행하는 것을 권장합니다.
src/main/java/inha/gdgoc/domain/core/attendance/repository/AttendanceRecordRepository.java (1)
12-12: 주석과 시그니처 불일치.getDay는 dateKey를 인자로 받아 teamId→memberId→present 맵을 반환합니다. 현재 주석의 "day -> ..."는 혼동을 줍니다. 주석을 시그니처에 맞게 정정해 주세요.
- Map<String, Map<String, Boolean>> getDay(String dateKey); // day -> teamId -> memberId -> present + Map<String, Map<String, Boolean>> getDay(String dateKey); // teamId -> (memberId -> present)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (3)
12-12: id를 불변으로 고정 권장.생성자에서만 설정되는 id는 final로 두는 편이 안전합니다.
- private String id; + private final String id;
13-15: name/lead 널 정규화 일관성.lead는 널을 ""로 정규화하지만 name은 그렇지 않습니다. 동일 정책이 필요하면 name 세터를 커스텀하세요.
- @Setter - private String name; + private String name; @@ + public void setName(String name){ this.name = name == null ? "" : name; }
16-16: 내부 리스트 외부 노출 주의(선택).불변 뷰를 반환하거나 add/remove 메서드를 통해 관리하면 캡슐화가 강화됩니다.
- private final List<Member> members = new ArrayList<>(); + private final List<Member> members = new ArrayList<>(); + public List<Member> getMembers(){ return List.copyOf(members); } + public void addMember(Member m){ members.add(m); } + public boolean removeMember(String memberId){ return members.removeIf(x -> x.getId().equals(memberId)); }src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DaySummaryResponse.java (2)
14-17: 널 안전성 및 포맷 명시 제안.
- perTeam에 기본값을 부여하면 NPE를 예방할 수 있습니다.
- date의 포맷(예: yyyy-MM-dd)을 명시하거나 LocalDate 사용을 고려해 주세요.
-import java.util.List; +import java.util.List; +import java.util.ArrayList; @@ - private List<TeamSummary> perTeam; + private List<TeamSummary> perTeam = new ArrayList<>();
19-27: 내부 클래스도 동일 규칙 적용(선택).TeamSummary의 present/total은 음수 불가하므로 생성 시 검증을 고려해 주세요. (또는 서비스 레이어에서 보장)
src/main/java/inha/gdgoc/domain/core/attendance/controller/message/CoreAttendanceMessage.java (1)
3-21: 상수 전용 유틸 클래스화.인스턴스화 방지 및 의도 명확화를 위해 final + private 생성자를 추가하세요.
-public class CoreAttendanceMessage { +public final class CoreAttendanceMessage { @@ public static final String CSV_EXPORTED_SUCCESS = "성공적으로 CSV 파일을 내보냈습니다."; } +private CoreAttendanceMessage() {}src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java (8)
28-31: 동시성 안전하지 않은LinkedHashSet사용 — 드물게 CME 발생 가능.
Service는 싱글턴이며 여러 스레드에서 접근합니다. 현재 구조는getDates()복사 중에addDate/deleteDate가 동시에 실행되면ConcurrentModificationException위험이 있습니다. 간단 동기화로 방지하세요.- private final LinkedHashSet<String> dates = new LinkedHashSet<>(); + private final Set<String> dates = Collections.synchronizedSet(new LinkedHashSet<>()); /* Dates */ - public List<String> getDates() {return new ArrayList<>(dates);} + public List<String> getDates() { + synchronized (dates) { + return new ArrayList<>(dates); + } + }Also applies to: 60-66
38-39: 정규식 불필요 —replaceAll→replace.미세하지만 불필요한 정규식 오버헤드입니다.
- private static String dkey(String date) {return date.replaceAll("-", "");} + private static String dkey(String date) { return date.replace("-", ""); }
101-104: 존재하지 않는 팀/멤버에 대한 출석 기록 생성 가능성.검증 없이
recordRepo를 갱신하면 데이터 정합성이 깨질 수 있습니다. 최소한 팀 존재 및 멤버 소속을 확인한 뒤 반영하세요.- public void setAttendance(String date, String teamId, String memberId, boolean present) { - recordRepo.setPresence(dkey(date), teamId, memberId, present); - } + public void setAttendance(String date, String teamId, String memberId, boolean present) { + Team t = team(teamId); // 팀 존재 검증 + boolean exists = t.getMembers().stream().anyMatch(m -> m.getId().equals(memberId)); + if (!exists) { + throw new NoSuchElementException("member not found in team: " + memberId); + } + recordRepo.setPresence(dkey(date), teamId, memberId, present); + }출석 저장소가 자체적으로 무결성 검증을 수행한다면 알려주세요. 그 경우 예외 대신 no-op도 옵션입니다.
113-116:recordRepo.getDay(...)가 null이면 NPE.널 세이프 가드 추가를 권장합니다.
- var dm = recordRepo.getDay(dkey(date)); + var dm = java.util.Optional.ofNullable(recordRepo.getDay(dkey(date))).orElseGet(java.util.Map::of);
13-22: 트랜잭션 경계가 없습니다 — 부분 실패 시 정합성 위험.쓰기 작업을 트랜잭션으로 묶으세요. 클래스 기본은 읽기 전용, 변경 메서드만 readOnly 해제 권장.
import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -public class CoreAttendanceService { +@Transactional(readOnly = true) +public class CoreAttendanceService {
61-69: 변경 메서드에 트랜잭션 부여.아래 메서드들에 트랜잭션을 지정해 부분 커밋을 방지하세요.
- public List<String> getDates() {return new ArrayList<>(dates);} + public List<String> getDates() {return new ArrayList<>(dates);} // read-only OK - public void addDate(String date) {dates.add(date);} + @Transactional + public void addDate(String date) {dates.add(date);} public void deleteDate(String date) { + @Transactional dates.remove(date); recordRepo.removeDate(dkey(date)); } - public void addMember(String teamId, String name) { + @Transactional + public void addMember(String teamId, String name) { Team t = team(teamId); memberRepo.add(t, new Member(uuid("m"), name)); teamRepo.save(t); } - public void renameMember(String teamId, String memberId, String name) { + @Transactional + public void renameMember(String teamId, String memberId, String name) { Team t = team(teamId); Member m = memberRepo.find(t, memberId).orElseThrow(() -> new NoSuchElementException("member not found")); m.setName(name); teamRepo.save(t); } - public void removeMember(String teamId, String memberId) { + @Transactional + public void removeMember(String teamId, String memberId) { Team t = team(teamId); memberRepo.remove(t, memberId); teamRepo.save(t); recordRepo.removeMemberEverywhere(teamId, memberId); } - public void setAttendance(String date, String teamId, String memberId, boolean present) { + @Transactional + public void setAttendance(String date, String teamId, String memberId, boolean present) { recordRepo.setPresence(dkey(date), teamId, memberId, present); } - public long setAll(String date, String teamId, boolean present) { + @Transactional + public long setAll(String date, String teamId, boolean present) { Team t = team(teamId); List<String> memberIds = t.getMembers().stream().map(Member::getId).toList(); return recordRepo.setAll(dkey(date), teamId, memberIds, present); }Also applies to: 80-86, 87-93, 94-100, 101-105, 106-110
81-86: 이름 입력값 검증 누락.
name공백/과도한 길이 허용 시 품질 저하. 서버 레이어에서도 최소 검증을 권장합니다(트림, 빈값 거절).- public void addMember(String teamId, String name) { + public void addMember(String teamId, String name) { + name = name == null ? null : name.trim(); + if (name == null || name.isEmpty()) throw new IllegalArgumentException("name is blank"); Team t = team(teamId); memberRepo.add(t, new Member(uuid("m"), name)); teamRepo.save(t); }동일 검증을
renameMember에도 적용하세요.Also applies to: 87-92
31-36: 8자 UUID 절단 사용 — 충돌 가능성 주의.임시라면 수용 가능하지만, 영속 키로 사용 시 32비트 엔트로피(충돌 확률 상승)입니다. 장기적으로는 저장소/엔티티 레벨의 키 생성 전략(예: DB UUID/BIGINT 시퀀스)으로 이관을 권장합니다.
src/main/java/inha/gdgoc/domain/core/attendance/controller/CoreAttendanceController.java (5)
21-27: 임시 운용(권한 제거) 안내 — 배포 전 기능 플래그/프로파일로 가드 필요.실서버 노출을 피하려면 컨트롤러 자체를 플래그/프로파일로 제한하세요.
예시(권장):
feature.core-attendance.enabled=true일 때만 로드.import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.validation.annotation.Validated; @RestController @RequestMapping("/api/v1/core-attendance") @RequiredArgsConstructor +@Validated +@ConditionalOnProperty(name = "feature.core-attendance.enabled", havingValue = "true", matchIfMissing = false) public class CoreAttendanceController {배포 환경의 설정에 해당 플래그가 기본
false인지 확인 부탁드립니다.
72-75: 가짜 Page 생성 시 정렬 속성 이름 불일치.
createdAt는TeamResponse에 없고 실제 정렬도 수행하지 않습니다. 메타만 필요하면 정렬을 제거하세요.- var page = new PageImpl<>(list, PageRequest.of(0, Math.max(1, list.size()), - Sort.by(Sort.Direction.DESC, "createdAt")), list.size()); + var page = new PageImpl<>(list, PageRequest.of(0, Math.max(1, list.size()), Sort.unsorted()), list.size());
81-90: 멤버 관리에 쿼리 파라미터 사용 — 최소 검증 부재.간단 호출 목적은 이해되지만, 서버단 유효성 검증을 강화하려면 DTO +
@Valid로 전환을 권장합니다(공백/길이 제한 등). 추후 리팩토링 항목으로 남겨주세요.Also applies to: 91-100, 101-109
110-120:date형식 서버단 검증 추가 제안.
YYYY-MM-DD를 강제하려면 파라미터에 패턴 검증을 두세요. 클래스에@Validated가 필요합니다(위 코멘트 참고).+import jakarta.validation.constraints.Pattern; @@ - public ResponseEntity<ApiResponse<String, Void>> setAttendance( - @RequestParam String date, // YYYY-MM-DD + public ResponseEntity<ApiResponse<String, Void>> setAttendance( + @RequestParam @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$") String date, // YYYY-MM-DD @@ - public ResponseEntity<ApiResponse<Long, Void>> setAll( - @RequestParam String date, // YYYY-MM-DD + public ResponseEntity<ApiResponse<Long, Void>> setAll( + @RequestParam @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$") String date, // YYYY-MM-DD @@ - public ResponseEntity<ApiResponse<DaySummaryResponse, Void>> summary( - @RequestParam String date, // YYYY-MM-DD + public ResponseEntity<ApiResponse<DaySummaryResponse, Void>> summary( + @RequestParam @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$") String date, // YYYY-MM-DDAlso applies to: 122-130, 133-141
55-62: DELETE /dates/{date} 경로 파라미터 인코딩 주의.현재 포맷(YYYY-MM-DD)이면 안전하나, 공백/슬래시 등 비표준 입력 시 404/매핑 실패가 발생할 수 있습니다. 필요 시
@PathVariable에@Pattern을 적용하거나, 삭제도RequestParam로 통일하는 방안을 고려하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.gitignore(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/controller/CoreAttendanceController.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/controller/message/CoreAttendanceMessage.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateDateRequest.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DateListResponse.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DaySummaryResponse.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/repository/AttendanceRecordRepository.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/repository/MemberRepository.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/repository/TeamRepository.java(1 hunks)src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DateListResponse.java (3)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DaySummaryResponse.java (2)
Getter(10-28)Getter(19-27)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (1)
Getter(10-18)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (4)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (1)
Getter(9-16)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
Getter(7-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (1)
Getter(10-23)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java (1)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java (1)
Getter(8-13)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateDateRequest.java (5)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java (1)
Getter(9-15)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (1)
Getter(9-16)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java (1)
Getter(8-13)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java (1)
Getter(8-13)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DateListResponse.java (1)
Getter(10-15)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (3)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (1)
Getter(10-18)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
Getter(7-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (1)
Getter(10-23)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java (3)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (1)
Getter(9-16)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
Getter(7-14)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DaySummaryResponse.java (3)
src/main/java/inha/gdgoc/domain/core/attendance/dto/response/DateListResponse.java (1)
Getter(10-15)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (1)
Getter(10-18)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java (1)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java (1)
Getter(8-13)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (6)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java (1)
Getter(9-15)src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateDateRequest.java (1)
Getter(9-15)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (1)
Getter(10-18)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
Getter(7-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (1)
Getter(10-23)
src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (4)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateTeamRequest.java (1)
Getter(9-16)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/TeamResponse.java (1)
Getter(10-18)src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
Getter(7-14)
src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (3)
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/AddMemberRequest.java (1)
Getter(9-15)src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
Getter(8-14)src/main/java/inha/gdgoc/domain/core/attendance/entity/Team.java (1)
Getter(10-23)
src/main/java/inha/gdgoc/domain/core/attendance/controller/CoreAttendanceController.java (1)
src/main/java/inha/gdgoc/domain/core/attendance/controller/message/CoreAttendanceMessage.java (1)
CoreAttendanceMessage(3-22)
🔇 Additional comments (9)
.gitignore (1)
7-7: LGTM: macOS 메타파일 무시 규칙 추가 적절.src/main/java/inha/gdgoc/domain/core/attendance/dto/response/MemberResponse.java (1)
8-14: LGTM: 응답 DTO 단순/명확.src/main/java/inha/gdgoc/domain/core/attendance/repository/TeamRepository.java (1)
10-16: 빈 등록 확인 필요: 인터페이스 @repository 사용 시 구현체 존재 여부 검증.Spring Data 확장이 아니라면 인터페이스에 @repository만 붙이고 구현체 빈이 없을 경우 런타임 주입 실패합니다. 구현체(@repository) 유무를 확인해 주세요.
src/main/java/inha/gdgoc/domain/core/attendance/entity/Member.java (1)
7-13: 기본 구현은 깔끔합니다.단순 VO로서 id 불변 + name 가변 구조가 명확합니다.
src/main/java/inha/gdgoc/domain/core/attendance/repository/AttendanceRecordRepository.java (1)
8-16: 구현체/트랜잭션 전략 확인 필요.쓰기 메서드가 다수이므로 구현체에서 적절한 동시성/트랜잭션 처리를 보장하는지 확인 바랍니다. (예: setAll의 원자성)
동일 스크립트로 구현체/주입 존재를 점검해 주세요(위 MemberRepository 코멘트 참고).
src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java (2)
138-141: 응답 매핑 깔끔합니다.DTO 매핑 간결하고 가독성 좋습니다.
40-58: PostConstruct 시드가 재기동 때마다 중복 삽입 — 단일 실행 가드 필요서버 재기동 시 seed()가 반복 실행돼 무작위 ID의 팀/멤버가 계속 저장됩니다. 한 번만 실행되도록 가드하거나 로컬 전용으로 제한하세요.
File: src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java (Lines 40-58)
@PostConstruct void initSeed() { // ✅ 빈 생성 후 시드 - seed(); + // 이미 데이터가 있으면 시드 금지 + if (!dates.isEmpty() || teamRepo.count() > 0) { + return; + } + seed(); }별도 시드 컴포넌트를 만들어 @Profile("local") 또는 @ConditionalOnProperty로 로컬/환경 전용으로 제한하는 것을 권장합니다.
검증: 샌드박스에서 레포지토리 파일을 찾을 수 없어 teamRepo.count() 존재 여부를 확인하지 못했습니다. teamRepo가 JpaRepository/CrudRepository 등을 확장해 count()를 제공하는지 확인하거나, 없다면 teamRepo.findAll().isEmpty() 또는 적절한 existsBy... 체크로 대체하세요.
src/main/java/inha/gdgoc/domain/core/attendance/controller/CoreAttendanceController.java (2)
35-43: Dates 조회 응답 구성 깔끔합니다.일관된
ApiResponse래핑과 DTO 사용이 적절합니다.
128-130: 일괄 출석 변경 API 응답 적절합니다.수정된 건수 반환은 클라이언트 피드백으로 충분합니다.
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.AllArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CreateDateRequest { | ||
| @NotBlank(message = "날짜는 YYYY-MM-DD 형식이어야 합니다.") | ||
| private String date; | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
형식 검증 누락: 메시지는 형식을 요구하지만 실제로는 NotBlank만 검증.
YYYY-MM-DD 형식을 @pattern으로 강제하거나 LocalDate 타입으로 전환하세요. 빠른 수정은 아래 패턴 추가입니다.
package inha.gdgoc.domain.core.attendance.dto.request;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CreateDateRequest {
- @NotBlank(message = "날짜는 YYYY-MM-DD 형식이어야 합니다.")
- private String date;
+ @NotBlank(message = "날짜는 비어 있을 수 없습니다.")
+ @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "날짜는 YYYY-MM-DD 형식이어야 합니다.")
+ private String date;
}📝 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 jakarta.validation.constraints.NotBlank; | |
| import lombok.Getter; | |
| import lombok.NoArgsConstructor; | |
| import lombok.AllArgsConstructor; | |
| @Getter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class CreateDateRequest { | |
| @NotBlank(message = "날짜는 YYYY-MM-DD 형식이어야 합니다.") | |
| private String date; | |
| } | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.Pattern; | |
| import lombok.Getter; | |
| import lombok.NoArgsConstructor; | |
| import lombok.AllArgsConstructor; | |
| @Getter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class CreateDateRequest { | |
| @NotBlank(message = "날짜는 비어 있을 수 없습니다.") | |
| @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "날짜는 YYYY-MM-DD 형식이어야 합니다.") | |
| private String date; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/CreateDateRequest.java
around lines 4–15, the field uses @NotBlank but the message demands a YYYY-MM-DD
format; either change the field type to java.time.LocalDate or (quick fix) add a
validation annotation: import jakarta.validation.constraints.Pattern and apply
@Pattern(regexp="^\\d{4}-\\d{2}-\\d{2}$", message="날짜는 YYYY-MM-DD 형식이어야 합니다.")
to the date field so the format is actually enforced.
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.AllArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class SetAllRequest { | ||
| private boolean present; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요청 DTO에서 primitive boolean → Boolean + @NotNull로 변경 필요 (누락 시 false로 침묵 기본값 되는 위험).
present 필드가 전달되지 않으면 false로 처리되어 대량 출석 상태가 의도치 않게 해제될 수 있습니다. wrapper 타입과 @NotNull로 안전하게 강제하세요.
package inha.gdgoc.domain.core.attendance.dto.request;
+import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SetAllRequest {
- private boolean present;
+ @NotNull
+ private Boolean present;
}📝 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 lombok.Getter; | |
| import lombok.NoArgsConstructor; | |
| import lombok.AllArgsConstructor; | |
| @Getter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class SetAllRequest { | |
| private boolean present; | |
| package inha.gdgoc.domain.core.attendance.dto.request; | |
| import jakarta.validation.constraints.NotNull; | |
| import lombok.Getter; | |
| import lombok.NoArgsConstructor; | |
| import lombok.AllArgsConstructor; | |
| @Getter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class SetAllRequest { | |
| @NotNull | |
| private Boolean present; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java
around lines 4 to 12, change the primitive boolean field to the wrapper type and
annotate it with @NotNull to avoid silent false defaults when the field is
omitted: replace "private boolean present;" with "private Boolean present;" add
the javax.validation.constraints.NotNull import and annotate the field with
@NotNull; keep Lombok annotations as-is so constructors and getters are
generated; ensure any code constructing this DTO or controller methods validate
bean constraints (e.g., @Valid) so missing values produce a validation error
instead of defaulting to false.
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.AllArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class SetAttendanceRequest { | ||
| private boolean present; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
요청 DTO에서 primitive boolean → Boolean + @NotNull로 변경 필요 (누락 시 false로 침묵 기본값 되는 위험).
단일 출석 설정도 동일한 리스크가 있습니다. SetAllRequest와 동일하게 수정해 일관성을 맞추세요.
package inha.gdgoc.domain.core.attendance.dto.request;
+import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SetAttendanceRequest {
- private boolean present;
+ @NotNull
+ private Boolean present;
}🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for SetAttendanceRequest / SetAllRequest and occurrences of 'present' field..."
rg -n --hidden -S "class SetAttendanceRequest" || true
rg -n --hidden -S "class SetAllRequest" || true
rg -n --hidden -S "SetAttendanceRequest.java" || true
rg -n --hidden -S "SetAllRequest.java" || true
echo
echo "Searching for 'private boolean present' or 'private Boolean present'..."
rg -n --hidden -S "private\s+boolean\s+present" || true
rg -n --hidden -S "private\s+Boolean\s+present" || true
echo
echo "Searching for @NotNull usages in attendance DTOs..."
rg -n --hidden -S "@NotNull" || true
echo
# Print file contents if found
SA_FILES=$(rg -l --hidden -S "class SetAttendanceRequest" || true)
if [ -n "$SA_FILES" ]; then
for f in $SA_FILES; do
echo "=== $f ==="
sed -n '1,200p' "$f" || true
done
else
echo "No SetAttendanceRequest files found"
fi
ALL_FILES=$(rg -l --hidden -S "class SetAllRequest" || true)
if [ -n "$ALL_FILES" ]; then
for f in $ALL_FILES; do
echo "=== $f ==="
sed -n '1,200p' "$f" || true
done
else
echo "No SetAllRequest files found"
fiLength of output: 2110
primitive boolean → Boolean + @NotNull 적용 (필수)
값이 누락될 때 primitive boolean이 기본값 false로 처리되어 침묵하는 위험이 있으므로, 아래 파일들에서 필드를 primitive boolean → Boolean으로 변경하고 import jakarta.validation.constraints.NotNull 후 @NotNull을 붙이세요. 또한 해당 DTO를 받는 컨트롤러/핸들러 파라미터에 @Valid가 적용되어 검증이 동작하는지 확인하세요.
대상: src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java, src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java
예: import jakarta.validation.constraints.NotNull; @NotNull private Boolean present;
🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAttendanceRequest.java
(lines 4-12) and
src/main/java/inha/gdgoc/domain/core/attendance/dto/request/SetAllRequest.java,
change the primitive field type from boolean to the boxed Boolean, add the
import jakarta.validation.constraints.NotNull, and annotate the field with
@NotNull (e.g., @NotNull private Boolean present;) so missing values fail
validation instead of defaulting to false; finally ensure any controller or
handler receiving these DTOs has @Valid on the method parameter so the NotNull
constraint is enforced at runtime.
| @Repository | ||
| public interface MemberRepository { | ||
| Member add(Team team, Member member); | ||
| Optional<Member> find(Team team, String memberId); | ||
| void remove(Team team, String memberId); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
구현체/빈 등록 확인 필요.
Spring Data 확장이 아니므로 인터페이스에 @repository만으로는 빈이 생성되지 않습니다. 구현 클래스(@Repository/@component)를 별도로 제공하거나 @bean 팩토리를 정의했는지 확인해 주세요.
다음 스크립트로 구현체/팩토리/주입 지점을 점검할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# 구현체 존재 여부
rg -nP --type=java -C2 'class\s+\w+\s+implements\s+MemberRepository\b|class\s+\w+\s+implements\s+AttendanceRecordRepository\b'
# @Bean 팩토리 메서드
rg -nP --type=java -C2 '@Bean\s*\(\s*\)\s*public\s+(MemberRepository|AttendanceRecordRepository)\b'
# 주입 사용처
rg -nP --type=java -C2 '\b(MemberRepository|AttendanceRecordRepository)\b'Length of output: 3668
구현체/빈 등록 필요 — 즉시 수정 요망
src/main/java/inha/gdgoc/domain/core/attendance/repository/MemberRepository.java 및 AttendanceRecordRepository.java는 @repository만 붙은 순수 인터페이스이며, 구현 클래스나 @bean 팩토리 메서드가 검색되지 않았습니다. CoreAttendanceService가 이들을 주입하므로 애플리케이션 시작 시 빈 미등록(NoSuchBeanDefinitionException)으로 실패합니다.
조치(택1):
- 구현체 클래스 추가(@Repository/@component)
- 또는 @configuration에 @bean 팩토리 메서드 추가
- 또는 의도한 경우 Spring Data 리포지토리로 전환(JpaRepository/CrudRepository 상속)
확인 파일:
- src/main/java/inha/gdgoc/domain/core/attendance/repository/MemberRepository.java
- src/main/java/inha/gdgoc/domain/core/attendance/repository/AttendanceRecordRepository.java
- 주입 지점: src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java
🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/core/attendance/repository/MemberRepository.java
(around lines 10-15) and similarly for AttendanceRecordRepository, the
interfaces are plain interfaces annotated only with @Repository and have no
implementation/bean, causing NoSuchBeanDefinitionException when
CoreAttendanceService injects them (see
src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java).
Fix by either: 1) add concrete implementation classes (e.g.,
MemberRepositoryImpl and AttendanceRecordRepositoryImpl) annotated with
@Repository or @Component that implement the interface methods and register as
beans; or 2) create a @Configuration class with @Bean factory methods that
return implementations for these interfaces; or 3) if intended to be Spring Data
repositories, change the interfaces to extend JpaRepository/CrudRepository with
the correct generic types so Spring can auto-implement them.
| public List<TeamResponse> getTeams(String leadName, String teamId) { | ||
| return teamRepo.findAll() | ||
| .stream() | ||
| .filter(t -> leadName == null || leadName.isBlank() || t.getLead().equals(leadName)) | ||
| .filter(t -> teamId == null || teamId.isBlank() || t.getId().equals(teamId)) | ||
| .map(this::toTeamResponse) | ||
| .collect(Collectors.toList()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.getLead().equals(leadName) NPE 가능성.
리드명이 null인 팀이 존재하면 필터에서 NPE가 납니다. Objects.equals로 안전 비교하세요.
- .filter(t -> leadName == null || leadName.isBlank() || t.getLead().equals(leadName))
+ .filter(t -> leadName == null || leadName.isBlank() || Objects.equals(t.getLead(), leadName))📝 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.
| public List<TeamResponse> getTeams(String leadName, String teamId) { | |
| return teamRepo.findAll() | |
| .stream() | |
| .filter(t -> leadName == null || leadName.isBlank() || t.getLead().equals(leadName)) | |
| .filter(t -> teamId == null || teamId.isBlank() || t.getId().equals(teamId)) | |
| .map(this::toTeamResponse) | |
| .collect(Collectors.toList()); | |
| } | |
| public List<TeamResponse> getTeams(String leadName, String teamId) { | |
| return teamRepo.findAll() | |
| .stream() | |
| .filter(t -> leadName == null || leadName.isBlank() || Objects.equals(t.getLead(), leadName)) | |
| .filter(t -> teamId == null || teamId.isBlank() || t.getId().equals(teamId)) | |
| .map(this::toTeamResponse) | |
| .collect(Collectors.toList()); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java
around lines 71 to 78, the filter t.getLead().equals(leadName) can throw an NPE
when a team's lead is null; replace the equals call with a null-safe comparison
such as Objects.equals(t.getLead(), leadName) (import java.util.Objects) so the
filter safely handles null lead values; apply the same null-safe pattern to the
teamId comparison if needed.
📌 연관된 이슈
✨ 작업 내용
feat(core-attendance): 코어 회의 출석 기능 추가
chore(gitignore): .DS_Store 파일 추적 제외 추가
💬 리뷰 요구사항(선택)
Summary by CodeRabbit