-
Notifications
You must be signed in to change notification settings - Fork 43
[2주차] 설계 문서 제출 - 최숙희 #83
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
base: SukheeChoi
Are you sure you want to change the base?
Changes from all commits
3562127
f47a7d9
eeb137c
23fa221
533790c
e6ba4dd
24c7c17
4e2522f
a0bba79
7959cdc
0fd169a
59a2d19
26e5522
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,3 +38,7 @@ out/ | |
|
|
||
| ### Kotlin ### | ||
| .kotlin | ||
|
|
||
| ### Claude Code ### | ||
| *.md | ||
| !docs/**/*.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,10 @@ dependencies { | |
|
|
||
| // web | ||
| implementation("org.springframework.boot:spring-boot-starter-web") | ||
| implementation("org.springframework.boot:spring-boot-starter-validation") | ||
|
|
||
| // security (password encryption only) | ||
| implementation("org.springframework.security:spring-security-crypto") | ||
|
Comment on lines
+13
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Root build.gradle.kts 확인 - Spring Boot 버전 확인
find . -name "build.gradle.kts" -type f | head -5Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 252 🏁 Script executed: # Root build.gradle.kts 파일 읽기
cat build.gradle.kts | head -50Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1363 🏁 Script executed: # apps/commerce-api/build.gradle.kts 전체 내용 확인
cat apps/commerce-api/build.gradle.ktsRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1199 🏁 Script executed: # gradle wrapper properties에서 Gradle 버전 확인
cat gradle/wrapper/gradle-wrapper.properties 2>/dev/null || echo "Not found"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 335 🏁 Script executed: # Root build.gradle.kts 전체 내용 확인
wc -l build.gradle.ktsRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 105 🏁 Script executed: # Root build.gradle.kts 나머지 부분 읽기
tail -100 build.gradle.ktsRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 3399 🏁 Script executed: # gradle.properties 확인
cat gradle.propertiesRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 621 🏁 Script executed: # settings.gradle.kts 확인
cat settings.gradle.kts | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 960 🌐 Web query:
💡 Result: Spring Boot 3.4.4 (via its dependency management / BOM) aligns with Spring Security 6.4.x and, for 3.4.4 specifically, the managed Spring Security line is 6.4.4. [1], [2] Sources: Spring Boot 3.4 release notes (Spring Security 6.4 baseline) [1] and Spring Security 6.4.4 release announcement [2]. [1] (github.com) Citations:
🌐 Web query:
💡 Result: Spring Cloud Dependencies 2024.0.1 is based on Spring Boot 3.4.3. [1] Spring Boot 3.4.3 manages Spring Security (spring-security-bom) 6.4.3, so that’s the Spring Security version you get when using the 2024.0.1 Spring Cloud BOM. [2] Answer: Spring Security 6.4.3. [1][2] 🌐 Web query:
💡 Result: To use Option A (recommended): Spring Boot plugin (BOM imported automatically)plugins {
id "org.springframework.boot" version "3.x.y"
id "java"
}
dependencies {
implementation "org.springframework.security:spring-security-crypto"
}Spring Boot’s Gradle plugin imports the Option B: No Boot plugin — import the Boot BOM yourself (Gradle platform)plugins {
id "org.springframework.boot" version "3.x.y" apply false
id "java"
}
dependencies {
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
implementation "org.springframework.security:spring-security-crypto"
}Gradle’s Overriding Spring Security’s version (when using Spring Boot’s BOM)ext["spring-security.version"] = "7.0.3"Spring Security docs explicitly call out overriding via the Coordinates: Citations:
Spring Cloud Dependencies 버전과 Spring Boot 버전 동기화 필요 gradle.properties의 Spring Boot 3.4.4와 Spring Cloud Dependencies 2024.0.1(Spring Boot 3.4.3 기반) 간 버전 미스매치가 존재한다. 현재 해결 방안:
🤖 Prompt for AI Agents |
||
| implementation("org.springframework.boot:spring-boot-starter-actuator") | ||
| implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${project.properties["springDocOpenApiVersion"]}") | ||
|
|
||
|
|
||
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import com.loopers.domain.BaseEntity; | ||
| import com.loopers.domain.member.vo.BirthDate; | ||
| import com.loopers.domain.member.vo.Email; | ||
| import com.loopers.domain.member.vo.LoginId; | ||
| import com.loopers.domain.member.vo.Password; | ||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Embedded; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Table; | ||
|
|
||
| @Entity | ||
| @Table(name = "member") | ||
| public class Member extends BaseEntity { | ||
|
|
||
| @Embedded | ||
| private LoginId loginId; | ||
|
|
||
| @Embedded | ||
| private Password password; | ||
|
|
||
| @Column(nullable = false, length = 50) | ||
| private String name; | ||
|
|
||
| @Embedded | ||
| private BirthDate birthDate; | ||
|
|
||
| @Embedded | ||
| private Email email; | ||
|
|
||
| protected Member() {} | ||
|
|
||
| public Member(LoginId loginId, Password password, String name, | ||
| BirthDate birthDate, Email email) { | ||
| if (name == null || name.isBlank()) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "이름은 필수입니다."); | ||
| } | ||
| this.loginId = loginId; | ||
| this.password = password; | ||
| this.name = name; | ||
| this.birthDate = birthDate; | ||
| this.email = email; | ||
| } | ||
|
|
||
| public LoginId getLoginId() { return loginId; } | ||
| public Password getPassword() { return password; } | ||
| public String getName() { return name; } | ||
| public BirthDate getBirthDate() { return birthDate; } | ||
| public Email getEmail() { return email; } | ||
|
|
||
| public void changePassword(Password newPassword) { | ||
| this.password = newPassword; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import com.loopers.domain.member.vo.LoginId; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberRepository { | ||
| Member save(Member member); | ||
| Optional<Member> findByLoginId(LoginId loginId); | ||
| boolean existsByLoginId(LoginId loginId); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.loopers.domain.member; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.domain.member.vo.BirthDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.domain.member.vo.Email; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.domain.member.vo.LoginId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.domain.member.vo.Password; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.support.error.CoreException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.loopers.support.error.ErrorType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Optional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional(readOnly = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class MemberService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final MemberRepository memberRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final PasswordEncoder passwordEncoder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Member register(String loginId, String plainPassword, String name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String birthDate, String email) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LoginId loginIdVo = new LoginId(loginId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (memberRepository.existsByLoginId(loginIdVo)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new CoreException(ErrorType.CONFLICT, "이미 존재하는 ID입니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BirthDate birthDateVo = BirthDate.from(birthDate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Password password = Password.create(plainPassword, birthDateVo.value(), passwordEncoder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Email emailVo = new Email(email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Member member = new Member(loginIdVo, password, name, birthDateVo, emailVo); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return memberRepository.save(member); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복 가입 레이스로 500/중복 계정이 발생할 수 있다 운영 영향: 동시 가입 요청에서 existsByLoginId 검사와 save 사이에 TOCTOU가 생겨 중복 계정 생성 또는 제약 위반으로 500이 발생할 수 있어 장애 대응과 데이터 정합성에 부담이 된다. 수정 예시다 import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.crypto.password.PasswordEncoder;
@@
Member member = new Member(loginIdVo, password, name, birthDateVo, emailVo);
- return memberRepository.save(member);
+ try {
+ return memberRepository.save(member);
+ } catch (DataIntegrityViolationException e) {
+ throw new CoreException(ErrorType.CONFLICT, "이미 존재하는 ID입니다.", e);
+ }
}As per coding guidelines, '예외 처리 시 cause를 보존하고, 사용자 메시지와 로그 메시지를 분리하도록 제안한다'에 근거한 제안이다. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Optional<Member> findByLoginId(String loginId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return memberRepository.findByLoginId(new LoginId(loginId)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void changePassword(Member member, String currentPlain, String newPlain) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!member.getPassword().matches(currentPlain, passwordEncoder)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new CoreException(ErrorType.BAD_REQUEST, "현재 비밀번호가 일치하지 않습니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (member.getPassword().matches(newPlain, passwordEncoder)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new CoreException(ErrorType.BAD_REQUEST, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "새 비밀번호는 현재 비밀번호와 달라야 합니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Password newPassword = Password.create( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newPlain, member.getBirthDate().value(), passwordEncoder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| member.changePassword(newPassword); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# OpenEntityManagerInView 설정 확인
rg -n "open-in-view|OpenEntityManagerInView" -g "*.yml" -g "*.yaml" -g "*.properties" -g "*.java" --max-count=20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 149 🏁 Script executed: #!/bin/bash
# AuthMemberResolver 구현 확인
find . -type f -name "*.java" -exec grep -l "AuthMemberResolver" {} \;Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 244 🏁 Script executed: #!/bin/bash
# Member 엔티티와 changePassword 메서드 확인
find . -type f -name "Member.java" -path "*/domain/member/*" | head -5Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 156 🏁 Script executed: #!/bin/bash
# MemberRepository 사용 패턴 확인
rg -n "memberRepository\.(save|merge)" -g "*.java" --max-count=20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 210 🏁 Script executed: #!/bin/bash
# AuthMemberResolver 구현 확인
cat -n ./apps/commerce-api/src/main/java/com/loopers/support/auth/AuthMemberResolver.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2841 🏁 Script executed: #!/bin/bash
# Member.changePassword() 메서드 확인
rg -A 10 "public.*changePassword" ./apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 184 🏁 Script executed: #!/bin/bash
# MemberService의 전체 changePassword 메서드 및 주변 코드 확인
sed -n '40,70p' ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberService.java | cat -nRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1001 🏁 Script executed: #!/bin/bash
# MemberRepository 인터페이스 확인 (save 구현)
find . -type f -name "*MemberRepository*.java" | xargs catRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1238 🏁 Script executed: #!/bin/bash
# changePassword 메서드 호출 지점과 테스트 확인
rg -n "changePassword\(" -g "*.java" -B 3 -A 3 | head -50Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4411 🏁 Script executed: #!/bin/bash
# Member 엔티티가 JPA 엔티티인지 확인 (Entity 애노테이션)
rg -B 5 "class Member" ./apps/commerce-api/src/main/java/com/loopers/domain/member/Member.java | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 227 🏁 Script executed: #!/bin/bash
# 비밀번호 변경 관련 통합 테스트 존재 여부 확인
find . -type f -name "*.java" -path "*/test/*" -exec grep -l "changePassword" {} \;Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 160 🏁 Script executed: #!/bin/bash
# MemberService 전체 구조 확인 (다른 메서드와의 비교)
sed -n '1,100p' ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberService.java | cat -nRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2724 분리된 엔티티일 경우 비밀번호 변경이 영속화되지 않을 수 있다 운영 영향: 수정안: changePassword 메서드 마지막에 memberRepository.save(member)를 호출하여 변경 사항을 명시적으로 영속화하라. (register 메서드의 라인 38을 참고) 추가 테스트: 비밀번호 변경 후 리포지토리 재조회로 변경 값이 데이터베이스에 저장되는지 검증하는 통합 테스트를 추가하라. 수정 예시 `@Transactional`
public void changePassword(Member member, String currentPlain, String newPlain) {
if (!member.getPassword().matches(currentPlain, passwordEncoder)) {
throw new CoreException(ErrorType.BAD_REQUEST, "현재 비밀번호가 일치하지 않습니다.");
}
if (member.getPassword().matches(newPlain, passwordEncoder)) {
throw new CoreException(ErrorType.BAD_REQUEST,
"새 비밀번호는 현재 비밀번호와 달라야 합니다.");
}
Password newPassword = Password.create(
newPlain, member.getBirthDate().value(), passwordEncoder);
member.changePassword(newPassword);
+ memberRepository.save(member);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.loopers.domain.member.policy; | ||
|
|
||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.List; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public class PasswordPolicy { | ||
|
|
||
| private static final Pattern FORMAT_PATTERN = | ||
| Pattern.compile("^[A-Za-z0-9!@#$%^&*()_+=-]{8,16}$"); | ||
|
|
||
| public static void validate(String plain, LocalDate birthDate) { | ||
| validateFormat(plain); | ||
| validateNotContainsSubstrings(plain, | ||
| extractBirthDateStrings(birthDate), | ||
| "비밀번호에 생년월일을 포함할 수 없습니다."); | ||
| } | ||
|
|
||
| public static void validateFormat(String plain) { | ||
| if (plain == null || !FORMAT_PATTERN.matcher(plain).matches()) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, | ||
| "비밀번호는 8~16자의 영문, 숫자, 특수문자만 허용됩니다."); | ||
| } | ||
| } | ||
|
|
||
| public static void validateNotContainsSubstrings( | ||
| String plain, List<String> forbidden, String errorMessage) { | ||
| for (String s : forbidden) { | ||
| if (plain.contains(s)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, errorMessage); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static List<String> extractBirthDateStrings(LocalDate birthDate) { | ||
| return List.of( | ||
| birthDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")), | ||
| birthDate.format(DateTimeFormatter.ofPattern("yyMMdd")) | ||
| ); | ||
|
Comment on lines
+16
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. birthDate가 null이면 NPE로 500 응답이 발생할 수 있다. 운영에서 잘못된 요청이 들어오면 CoreException 경로를 우회해 500으로 처리되어 모니터링 노이즈와 사용자 응답 불일치가 생긴다. 🛠️ 수정안 public static void validate(String plain, LocalDate birthDate) {
validateFormat(plain);
+ if (birthDate == null) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "생년월일은 필수입니다.");
+ }
validateNotContainsSubstrings(plain,
extractBirthDateStrings(birthDate),
"비밀번호에 생년월일을 포함할 수 없습니다.");
}Based on learnings, "In the loop-pack-be-l2-vol3-java project, enforce unified error handling by routing errors through CoreException to ApiControllerAdvice to ensure a consistent response format. Do not introduce MethodArgumentNotValidException handlers or Bean Validation handling, as that would create inconsistent error handling patterns."를 따른다. 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
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.
Markdown 전체 무시 규칙은 운영 문서 누락 위험이 있다
운영 관점에서 README/CHANGELOG/런북/ADR 같은 핵심 문서가
docs/외부에 있을 경우 버전 관리에서 누락되어 배포·장애 대응 문서가 사라질 수 있다. 따라서*.md전체 무시는 피하고, Claude Code 산출물 경로만 좁게 무시하도록 수정하는 것이 안전하다. 추가로, 변경 후 Git이 문서를 올바르게 추적하는지 확인하는 테스트가 필요하다.수정안 예시는 다음과 같다.
🔧 제안 수정안
추가 테스트:
git check-ignore -v README.md docs/design/01-requirements.md실행 시 README는 무시되지 않고, docs 문서는 추적됨이 확인되어야 한다.📝 Committable suggestion
🤖 Prompt for AI Agents