Skip to content

Feat: 배송지 정보 등록, 수정, 조회#68

Merged
neibler merged 3 commits intodevelopfrom
feature/66
Jan 28, 2026
Merged

Feat: 배송지 정보 등록, 수정, 조회#68
neibler merged 3 commits intodevelopfrom
feature/66

Conversation

@neibler
Copy link
Copy Markdown
Contributor

@neibler neibler commented Jan 28, 2026

🔍️ 작업 내용

  • Closes #

✨ 상세 설명

🛠️ 추후 리팩토링 및 고도화 계획

📸 스크린샷 (선택)

💬 리뷰 요구사항

Summary by CodeRabbit

  • New Features

    • 배송지 관리 API 추가: 본인 배송지 조회, 생성, 수정 지원
    • 배송지 응답에 상세 정보 및 존재 여부 표시
  • Enhancements

    • 생성/수정 요청에 대한 서버측 입력 검증 강화(필수/선택 필드 규칙)
    • 기본 배송지 플래그 처리 및 주소 업데이트 동작 개선
    • 사용자의 최신 배송지 조회 및 일괄 삭제 지원
  • Errors

    • 배송지 관련 명확한 오류 코드와 권한 오류 반환 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 28, 2026

Walkthrough

배송지 관리 기능을 추가합니다. 신규 컨트롤러, 서비스, 변환 유틸, 요청/응답 DTO, 엔티티 업데이트 메서드, 저장소 쿼리, 유효성 검증 어노테이션 및 주소 관련 에러 코드가 추가되었습니다.

Changes

Cohort / File(s) 변경 요약
컨트롤러
src/main/java/com/ongil/backend/domain/address/controller/AddressController.java
/api/addresses 아래 GET/POST/PATCH 엔드포인트 추가 (인증된 userId 사용, @Valid 검증, DataResponse 포장, Swagger 주석)
서비스
src/main/java/com/ongil/backend/domain/address/service/AddressService.java
getShippingInfo, createShippingInfo, updateShippingInfo 추가; create는 기존 주소 전부 삭제 후 저장, update는 소유권 검증 및 엔티티 업데이트
변환 유틸
src/main/java/com/ongil/backend/domain/address/converter/AddressConverter.java
엔티티 ↔ 응답 DTO 변환(toShippingInfoResDto), 생성요청 → 엔티티(toEntity) 추가
요청 DTO
src/main/java/com/ongil/backend/domain/address/dto/request/ShippingInfoCreateReqDto.java, src/main/java/com/ongil/backend/domain/address/dto/request/ShippingInfoUpdateReqDto.java
Create/Update 요청용 레코드 추가; Create에 @NotBlank, Update에 @NullOrNotBlank 사용
응답 DTO
src/main/java/com/ongil/backend/domain/address/dto/response/ShippingInfoResDto.java
hasShippingInfo + 중첩 ShippingDetail 레코드 추가 (빌더 지원)
엔티티
src/main/java/com/ongil/backend/domain/address/entity/Address.java
Booleanboolean으로 isDefault 타입 변경, update(...)setDefault(...) 메서드 추가, 선택적 필드 업데이트 로직 추가
저장소
src/main/java/com/ongil/backend/domain/address/repository/AddressRepository.java
findFirstByUserIdOrderByCreatedAtDesc(Long)deleteAllByUserId(Long) 메서드 추가
에러 코드
src/main/java/com/ongil/backend/global/common/exception/ErrorCode.java
ADDRESS_NOT_FOUND, ADDRESS_FORBIDDEN 에러 코드 추가
유효성 검증
src/main/java/com/ongil/backend/global/common/validation/NullOrNotBlank.java, .../NullOrNotBlankValidator.java
null 허용 또는 공백 불가를 검증하는 커스텀 어노테이션 및 Validator 추가

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as 클라이언트
    participant Controller as AddressController
    participant Service as AddressService
    participant Repo as AddressRepository
    participant DB as Database

    Client->>Controller: GET /api/addresses/me (userId)
    Controller->>Service: getShippingInfo(userId)
    Service->>Repo: findFirstByUserIdOrderByCreatedAtDesc(userId)
    Repo->>DB: SELECT ... (최신 주소)
    DB-->>Repo: Address or null
    Repo-->>Service: Address?
    Service-->>Controller: ShippingInfoResDto
    Controller-->>Client: DataResponse(ShippingInfoResDto)
Loading
sequenceDiagram
    autonumber
    participant Client as 클라이언트
    participant Controller as AddressController
    participant Service as AddressService
    participant Repo as AddressRepository
    participant DB as Database

    Client->>Controller: POST /api/addresses (userId, request)
    Controller->>Service: createShippingInfo(userId, request)
    Service->>Repo: deleteAllByUserId(userId)
    Repo->>DB: DELETE FROM address WHERE user_id=...
    Service->>Service: AddressConverter.toEntity(user, request)
    Service->>Repo: save(Address)
    Repo->>DB: INSERT INTO address ...
    DB-->>Repo: saved Address
    Repo-->>Service: saved Address
    Service-->>Controller: ShippingInfoResDto
    Controller-->>Client: DataResponse(ShippingInfoResDto)
Loading
sequenceDiagram
    autonumber
    participant Client as 클라이언트
    participant Controller as AddressController
    participant Service as AddressService
    participant Repo as AddressRepository
    participant DB as Database

    Client->>Controller: PATCH /api/addresses/{addressId} (userId, request)
    Controller->>Service: updateShippingInfo(userId, addressId, request)
    Service->>Repo: findById(addressId)
    Repo->>DB: SELECT ... WHERE id=...
    DB-->>Repo: Address or null
    Repo-->>Service: Address?
    alt Address null
      Service-->>Controller: throw ADDRESS_NOT_FOUND
    else Owner mismatch
      Service-->>Controller: throw ADDRESS_FORBIDDEN
    else OK
      Service->>Service: address.update(...)
      Service->>Repo: (JPA flush via tx)
      Repo->>DB: UPDATE address ...
      Service-->>Controller: ShippingInfoResDto
      Controller-->>Client: DataResponse(ShippingInfoResDto)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • kangcheolung
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용을 명확하게 요약하고 있습니다. 배송지 정보의 등록, 수정, 조회 기능이 정확하게 반영되어 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@src/main/java/com/ongil/backend/domain/address/dto/request/ShippingInfoUpdateReqDto.java`:
- Around line 1-9: 업데이트 DTO ShippingInfoUpdateReqDto의 일부 필드(recipientName,
baseAddress, postalCode, phone)가 null은 허용하되 빈 문자열/공백만 들어오는 것을 막아야 합니다; 각 해당
컴포넌트에 Bean Validation의 `@Pattern`(regexp=".*\\S.*", message="값이 존재할 경우 공백만 허용할 수
없습니다")를 추가하세요(`@Pattern은` null을 유효로 처리하므로 부분 업데이트에서 null은 유지되고 빈 문자열·공백만 거부됩니다).
필요시 커스텀 메시지를 지정하고 import javax.validation.constraints.Pattern를 추가해 주세요.

In `@src/main/java/com/ongil/backend/domain/address/entity/Address.java`:
- Around line 62-85: Change the nullable Boolean usage in the Address entity to
a primitive boolean to match the `@Column`(nullable = false) constraint: update
the isDefault field's type to boolean, change the constructor parameter
(currently at the constructor that sets isDefault) from Boolean to boolean, and
change the setDefault(Boolean isDefault) method signature to setDefault(boolean
isDefault) so null cannot be assigned; remove any null checks related to
isDefault and ensure assignments use the primitive field and parameter
consistently in the Address class.

In
`@src/main/java/com/ongil/backend/domain/address/repository/AddressRepository.java`:
- Around line 10-14: findByUserId currently returns Optional<Address> but
User->Address is 1:N so calling it can throw
IncorrectResultSizeDataAccessException when multiple addresses exist; change the
repository to return the single default address by filtering on the isDefault
field (e.g. replace or add a method such as Optional<Address>
findByUserIdAndIsDefaultTrue(Long userId) or Optional<Address>
findDefaultByUserId(Long userId) that queries isDefault == true) and keep
findAllByUserId as-is for multi-results; update any service/usage that called
findByUserId to use the new default-address method name.

In `@src/main/java/com/ongil/backend/domain/address/service/AddressService.java`:
- Around line 44-58: Add a repository-level bulk delete to avoid loading
entities: declare void deleteByUserId(Long userId) in AddressRepository, then in
AddressService.createShippingInfo replace the existing findAllByUserId +
addressRepository.deleteAll(existingAddresses) sequence with a call to
addressRepository.deleteByUserId(userId) before converting and saving the new
Address (keep the User lookup and AddressConverter usage intact).
🧹 Nitpick comments (1)
src/main/java/com/ongil/backend/domain/address/service/AddressService.java (1)

31-42: 빈 응답 구성 로직을 Converter로 위임하면 중복을 줄일 수 있어요.
비어있는 경우도 AddressConverter에 위임하면 응답 규격이 더 일관됩니다.

Comment on lines +10 to +14
public interface AddressRepository extends JpaRepository<Address, Long> {

Optional<Address> findByUserId(Long userId);

List<Address> findAllByUserId(Long userId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# AddressRepository.java 전체 파일 확인
cat -n src/main/java/com/ongil/backend/domain/address/repository/AddressRepository.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 565


🏁 Script executed:

# Address 엔티티 파일 위치 찾기
fd -e java "Address\.java" --type f | grep -E "entity|domain"

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 133


🏁 Script executed:

# findByUserId 메서드 사용 위치 확인
rg -n "findByUserId" --type=java -B2 -A2

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 11259


🏁 Script executed:

# isDefault 필드 존재 여부 확인
rg -n "isDefault" --type=java -B2 -A2

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 2671


🏁 Script executed:

# Address 엔티티 정의 확인 (파일 크기 먼저 확인)
wc -l $(fd -e java Address.java --path "*/domain/address/*" | head -1)

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 319


🏁 Script executed:

# Address 엔티티 전체 확인
cat -n src/main/java/com/ongil/backend/domain/address/entity/Address.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 3096


🏁 Script executed:

# AddressService.getShippingInfo 메서드 확인
sed -n '30,40p' src/main/java/com/ongil/backend/domain/address/service/AddressService.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 335


🏁 Script executed:

# AddressRepository의 다른 메서드나 제약 조건 확인
cat -n src/main/java/com/ongil/backend/domain/address/repository/AddressRepository.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 565


🏁 Script executed:

# Address와 User 사이의 관계 확인 (1:1 or 1:N)
rg -n "Address" src/main/java/com/ongil/backend/domain/user/entity/ --type=java -B2 -A2

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 1073


findByUserId 단건 반환은 다건 결과 시 런타임 예외 위험

User-Address 관계가 1:N이고 유니크 제약이 없으므로, findByUserId()가 Optional

를 반환하면 한 사용자가 여러 주소를 가질 때 JPA가 IncorrectResultSizeDataAccessException을 발생시킵니다. Address 엔티티의 isDefault 필드를 활용하여 기본 배송지만 조회하도록 수정하세요.

✅ 제안 수정안
-Optional<Address> findByUserId(Long userId);
+Optional<Address> findByUserIdAndIsDefaultTrue(Long userId);

 List<Address> findAllByUserId(Long userId);
🤖 Prompt for AI Agents
In
`@src/main/java/com/ongil/backend/domain/address/repository/AddressRepository.java`
around lines 10 - 14, findByUserId currently returns Optional<Address> but
User->Address is 1:N so calling it can throw
IncorrectResultSizeDataAccessException when multiple addresses exist; change the
repository to return the single default address by filtering on the isDefault
field (e.g. replace or add a method such as Optional<Address>
findByUserIdAndIsDefaultTrue(Long userId) or Optional<Address>
findDefaultByUserId(Long userId) that queries isDefault == true) and keep
findAllByUserId as-is for multi-results; update any service/usage that called
findByUserId to use the new default-address method name.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/main/java/com/ongil/backend/domain/address/entity/Address.java`:
- Around line 62-82: The update() method in Address uses hasText() so empty
strings are ignored, causing silent no-ops when clients submit blank required
fields; to fix, add `@NotBlank` to the required fields (recipientName,
baseAddress, postalCode, phone/recipientPhone) in ShippingInfoUpdateReqDto
consistent with ShippingInfoCreateReqDto and ensure the controller uses `@Valid`
(or `@Validated`) on the DTO parameter so validation fails fast instead of
returning 200 with no DB change.
🧹 Nitpick comments (1)
src/main/java/com/ongil/backend/domain/address/entity/Address.java (1)

88-90: Spring StringUtils.hasText() 활용 고려

Spring Framework에서 이미 동일한 기능을 제공하므로 중복 구현 대신 활용할 수 있습니다. 단, 현재 구현도 간단하고 명확하여 유지해도 무방합니다.

♻️ Spring StringUtils 활용 예시
+import org.springframework.util.StringUtils;
+
 public void update(String recipientName, String recipientPhone, String baseAddress,
 	String detailAddress, String postalCode, String deliveryRequest) {
-	if (hasText(recipientName)) {
+	if (StringUtils.hasText(recipientName)) {
 		this.recipientName = recipientName;
 	}
 	// ... 나머지 필드도 동일하게 변경
 }
 
-private boolean hasText(String value) {
-	return value != null && !value.isBlank();
-}

Comment on lines +62 to +82
public void update(String recipientName, String recipientPhone, String baseAddress,
String detailAddress, String postalCode, String deliveryRequest) {
if (hasText(recipientName)) {
this.recipientName = recipientName;
}
if (hasText(recipientPhone)) {
this.recipientPhone = recipientPhone;
}
if (hasText(baseAddress)) {
this.baseAddress = baseAddress;
}
if (detailAddress != null) {
this.detailAddress = detailAddress;
}
if (hasText(postalCode)) {
this.postalCode = postalCode;
}
if (deliveryRequest != null) {
this.deliveryRequest = deliveryRequest;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Address.java 전체 파일 확인
find . -type f -name "Address.java" -path "*/domain/address/*"

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 135


🏁 Script executed:

# Read Address.java file
wc -l ./src/main/java/com/ongil/backend/domain/address/entity/Address.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 138


🏁 Script executed:

# Read the entire Address.java file
cat -n ./src/main/java/com/ongil/backend/domain/address/entity/Address.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 3217


🏁 Script executed:

# Find AddressService to see how update() is called
find . -type f -name "*AddressService*" -path "*/service/*"

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 143


🏁 Script executed:

# Read AddressService
cat -n ./src/main/java/com/ongil/backend/domain/address/service/AddressService.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 3271


🏁 Script executed:

# Find the DTO classes
find . -type f -name "ShippingInfoUpdateReqDto*"

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 157


🏁 Script executed:

# Read the DTO class
cat -n ./src/main/java/com/ongil/backend/domain/address/dto/request/ShippingInfoUpdateReqDto.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 370


🏁 Script executed:

# Find AddressController or related endpoints
find . -type f -name "*AddressController*" -o -name "*ShippingController*" | head -5

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 149


🏁 Script executed:

# Read AddressController
cat -n ./src/main/java/com/ongil/backend/domain/address/controller/AddressController.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 2910


🏁 Script executed:

# Check if validation annotations exist in request DTOs
find . -type f -name "ShippingInfoCreateReqDto*"

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 157


🏁 Script executed:

# Read ShippingInfoCreateReqDto
cat -n ./src/main/java/com/ongil/backend/domain/address/dto/request/ShippingInfoCreateReqDto.java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 651


🏁 Script executed:

# Search for any validation decorators in the update DTO or related code
rg -n "@NotBlank|@NotNull|@Valid" ./src/main/java/com/ongil/backend/domain/address/ --type=java

Repository: IT-Cotato/12th-OnGil-BE

Length of output: 872


부분 업데이트 시 필드 검증 일관성 부재

ShippingInfoUpdateReqDto에 필수 필드에 대한 @NotBlank 검증이 없어, 사용자가 필수 필드에 빈 문자열을 전달하면 update() 메서드의 hasText() 검증이 무시하고 DB에는 반영되지 않으면서도 API는 성공 응답합니다:

상황 결과
필수 필드에 빈 문자열 전달 API 200 응답 → 실제 업데이트 안 됨
선택 필드에 빈 문자열 전달 API 200 응답 → 필드값이 빈 문자열로 변경

ShippingInfoCreateReqDto처럼 ShippingInfoUpdateReqDto의 필수 필드(recipientName, baseAddress, postalCode, phone)에도 @NotBlank 검증을 추가하면, 컨트롤러에서 요청 검증 단계에 잡히므로 침묵하는 실패를 방지할 수 있습니다.

🤖 Prompt for AI Agents
In `@src/main/java/com/ongil/backend/domain/address/entity/Address.java` around
lines 62 - 82, The update() method in Address uses hasText() so empty strings
are ignored, causing silent no-ops when clients submit blank required fields; to
fix, add `@NotBlank` to the required fields (recipientName, baseAddress,
postalCode, phone/recipientPhone) in ShippingInfoUpdateReqDto consistent with
ShippingInfoCreateReqDto and ensure the controller uses `@Valid` (or `@Validated`)
on the DTO parameter so validation fails fast instead of returning 200 with no
DB change.

@neibler neibler merged commit 853851c into develop Jan 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant