Skip to content

✨ Feat: Admin 도메인 도입 및 Geocoding 기반 체육시설·매칭 관리 기능 확장#60

Merged
angoroa merged 7 commits intodevelopfrom
feat/admin
Feb 28, 2026
Merged

✨ Feat: Admin 도메인 도입 및 Geocoding 기반 체육시설·매칭 관리 기능 확장#60
angoroa merged 7 commits intodevelopfrom
feat/admin

Conversation

@angoroa
Copy link
Copy Markdown
Member

@angoroa angoroa commented Feb 28, 2026

#️⃣ Issue Number

  • closed #

📝 요약(Summary)

  • Admin 도메인 패키지 생성 및 /api/admin/** 엔드포인트 ADMIN 권한 보안 룰 적용
  • 매칭·시설 생성 API를 기존 컨트롤러에서 Admin 전용 엔드포인트로 이동
  • 카카오 로컬 API 기반 Geocoding 연동으로 주소 입력 시 좌표 자동 변환 구현
  • 매칭 시작 일시(scheduledAt) 필드 추가
  • 체육시설 목록 조회 응답에 주소(address) 필드 노출
  • Admin 전용 체육시설 수정·삭제, 매칭 삭제 API 추가

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 체육시설에 주소 정보 필드 추가
    • 매칭에 예약 일시 정보 추가
  • 개선 사항

    • 주소 기반 좌표 변환 기능 도입
    • 체육시설 갱신 및 삭제 기능 추가
    • 매칭 삭제 기능 추가

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 28, 2026

Walkthrough

관리자 전용 API 계층을 추가하고 시설 및 경기 관리 기능을 재구성합니다. 시설 생성 엔드포인트를 공개에서 관리자 전용으로 이동하고, 업데이트/삭제 기능을 추가합니다. 카카오 지오코딩 서비스를 통합하여 주소를 좌표로 변환하고, 주소 및 예약 시간 필드를 추가합니다.

Changes

응집 / 파일(들) 요약
관리자 계층
src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java, src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java, src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java, src/main/java/com/be/sportizebe/domain/admin/exception/AdminErrorCode.java
관리자 전용 REST 엔드포인트를 추가합니다. 시설 및 경기의 CRUD 작업을 처리하는 새로운 AdminController, AdminService 인터페이스, 구현 클래스를 작성합니다. ADMIN_ACCESS_DENIED 오류 코드를 정의합니다.
시설 DTO 및 응답
src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityCreateRequest.java, src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityUpdateRequest.java, src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityResponse.java, src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityNearResponse.java
시설 생성 요청에 address 필드를 추가하고 lat/lng을 제거합니다. 새로운 FacilityUpdateRequest 레코드를 추가합니다. FacilityResponse 및 FacilityNearResponse에 address 필드를 추가합니다.
시설 엔티티 및 서비스
src/main/java/com/be/sportizebe/domain/facility/entity/SportsFacility.java, src/main/java/com/be/sportizebe/domain/facility/service/SportsFacilityService.java, src/main/java/com/be/sportizebe/domain/facility/service/SportsFacilityServiceImpl.java, src/main/java/com/be/sportizebe/domain/facility/exception/FacilityErrorCode.java
SportsFacility 엔티티에 address 필드 및 changeAddress() 메서드를 추가합니다. SportsFacilityService에 update() 및 delete() 메서드를 추가합니다. 구현체에서 카카오 지오코딩 서비스를 이용하여 주소 기반 좌표 변환을 수행합니다. FACILITY_NOT_FOUND 및 ADDRESS_NOT_FOUND 오류 코드를 추가합니다.
시설 저장소 및 매퍼
src/main/java/com/be/sportizebe/domain/facility/repository/SportsFacilityRepository.java, src/main/java/com/be/sportizebe/domain/facility/repository/projection/FacilityNearProjection.java, src/main/java/com/be/sportizebe/domain/facility/mapper/FacilityMapper.java
findNear 쿼리에 address 열을 추가하고 FacilityNearProjection 인터페이스에 getAddress() 메서드를 추가합니다. FacilityMapper에서 address 필드를 DTO로 매핑합니다.
경기 DTO 및 엔티티
src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java, src/main/java/com/be/sportizebe/domain/match/dto/response/MatchResponse.java, src/main/java/com/be/sportizebe/domain/match/dto/response/MatchDetailResponse.java, src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java, src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java
MatchCreateRequest에 필수 scheduledAt 필드를 추가합니다. MatchResponse, MatchDetailResponse, MatchNearResponse에 scheduledAt 필드를 추가합니다. MatchRoom 엔티티에 scheduled_at 컬럼을 추가합니다.
경기 서비스 및 저장소
src/main/java/com/be/sportizebe/domain/match/service/MatchService.java, src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java, src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java, src/main/java/com/be/sportizebe/domain/match/repository/projection/MatchNearProjection.java
MatchService 인터페이스에 deleteMatch() 메서드를 추가합니다. MatchServiceImpl에서 경기 삭제 로직을 구현합니다. MatchRoomRepository의 findNear 쿼리에 scheduled_at을 추가하고 MatchNearProjection에 getScheduledAt() 메서드를 추가합니다.
카카오 지오코딩
src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java, src/main/java/com/be/sportizebe/global/kakao/dto/response/KakaoGeocodingResponse.java
새로운 KakaoGeocodingService를 추가하여 주소를 JTS Point(위도/경도)로 변환합니다. Kakao 로컬 검색 API를 호출하고 응답을 파싱합니다. KakaoGeocodingResponse 레코드를 정의하여 API 응답을 매핑합니다.
컨트롤러 변경
src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java, src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java
SportsFacilityController에서 create() 메서드를 제거합니다. MatchController에서 createMatch() 메서드를 제거합니다. 이 엔드포인트들은 관리자 전용 API로 이동합니다.
보안 설정
src/main/java/com/be/sportizebe/global/security/SecurityConfig.java
/api/admin/** 경로에 대한 접근을 ADMIN 권한으로 제한하는 규칙을 추가합니다.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AdminController
    participant AdminService
    participant SportsFacilityService
    participant KakaoGeocodingService
    participant Database as Database/<br/>PostGIS

    Client->>AdminController: POST /api/admin/facilities<br/>(FacilityCreateRequest)
    AdminController->>AdminService: createFacility(request)
    AdminService->>SportsFacilityService: create(request)
    SportsFacilityService->>KakaoGeocodingService: toPoint(address)
    KakaoGeocodingService->>KakaoGeocodingService: Call Kakao API<br/>Parse response
    KakaoGeocodingService-->>SportsFacilityService: Point(x, y)<br/>SRID 4326
    SportsFacilityService->>Database: Persist SportsFacility<br/>with address & location
    Database-->>SportsFacilityService: FacilityResponse
    SportsFacilityService-->>AdminService: FacilityResponse
    AdminService-->>AdminController: FacilityResponse
    AdminController-->>Client: 201 Created<br/>BaseResponse<FacilityResponse>
Loading
sequenceDiagram
    participant Client
    participant AdminController
    participant AdminService
    participant MatchService
    participant Database

    Client->>AdminController: POST /api/admin/matches<br/>(MatchCreateRequest)
    AdminController->>AdminService: createMatch(adminId,<br/>MatchCreateRequest)
    AdminService->>MatchService: createMatch(adminId,<br/>request)
    MatchService->>Database: Persist MatchRoom<br/>with scheduledAt
    Database-->>MatchService: MatchResponse
    MatchService-->>AdminService: MatchResponse
    AdminService-->>AdminController: MatchResponse
    AdminController-->>Client: 201 Created<br/>BaseResponse<MatchResponse>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Feat/match #58 — 경기 생성 엔드포인트를 관리자 전용으로 이동하고 경기 삭제 기능을 추가하는 등 MatchController 및 MatchService의 관리 기능을 수정합니다.
  • ✨Feat: 반경 내 지도 마커 목록 api #30 — 시설 근처 API 표면(FacilityNearResponse, FacilityMapper, SportsFacilityRepository 및 관련 프로젝션)을 수정하여 address 필드 추가와 관련됩니다.
  • ✨Feat: 운동 매칭 기능 추가 #56 — 경기 및 시설 API(MatchCreateRequest, SportsFacilityController 엔드포인트 등 동일한 DTO)를 수정하며, 생성 엔드포인트를 이동하고 요청/응답 타입을 진화시킵니다.

Poem

🐰 주소는 좌표로, 경기는 시간을 얻고,

관리자만 만들고 지울 수 있네!

카카오의 도움으로 지도 위에 빛나는

새로운 운동장들, 반갑고도 즐겁고,

스포츠의 꿈은 더욱 커져간다! 🎾✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.73% 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 제목이 주요 변경사항을 명확하게 요약합니다. Admin 도메인 도입, Geocoding 기반 기능 확장, 체육시설·매칭 관리 기능 추가라는 핵심 변경사항이 잘 반영되어 있습니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/admin

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

@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: 2

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/be/sportizebe/domain/facility/service/SportsFacilityServiceImpl.java (1)

72-88: ⚠️ Potential issue | 🟡 Minor

캐시 무효화 누락 가능성

create, update, delete 메서드가 시설 데이터를 변경하지만, getNeargetMarkers의 캐시(facilityNear, facilityMarkers)를 무효화하지 않습니다. 시설이 추가/수정/삭제되어도 캐시된 결과가 반환될 수 있습니다.

♻️ 캐시 무효화 추가 예시
 `@Override`
+@CacheEvict(cacheNames = {"facilityNear", "facilityMarkers"}, allEntries = true)
 public FacilityResponse create(FacilityCreateRequest request) {
     // ...
 }

 `@Override`
+@CacheEvict(cacheNames = {"facilityNear", "facilityMarkers"}, allEntries = true)
 public FacilityResponse update(Long facilityId, FacilityUpdateRequest request) {
     // ...
 }

 `@Override`
+@CacheEvict(cacheNames = {"facilityNear", "facilityMarkers"}, allEntries = true)
 public void delete(Long facilityId) {
     // ...
 }
🧹 Nitpick comments (4)
src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java (2)

44-46: SRID 설정 중복

GeometryFactory 생성자에서 이미 SRID 4326을 설정했으므로 (line 20), point.setSRID(4326)은 중복입니다. 제거해도 무방하지만 명시적으로 두는 것도 가독성 측면에서 괜찮습니다.

Based on learnings: "Store SportsFacility.location as JTS Point with SRID 4326"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java`
around lines 44 - 46, The Point SRID is being set twice: GEOMETRY_FACTORY is
already constructed with SRID 4326, so remove the redundant call to
point.setSRID(4326) in KakaoGeocodingService where you create the Point via
GEOMETRY_FACTORY.createPoint(new Coordinate(response.lng(), response.lat()));
keep GEOMETRY_FACTORY as the single source of truth for the SRID to avoid
duplication and potential inconsistency.

24-28: RestClient에 타임아웃 설정 고려

외부 API 호출 시 타임아웃이 설정되어 있지 않으면 카카오 API가 응답하지 않을 경우 스레드가 무한 대기할 수 있습니다. 연결 및 읽기 타임아웃 설정을 권장합니다.

♻️ 타임아웃 설정 예시
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import java.time.Duration;

 public KakaoGeocodingService(`@Value`("${kakao.rest-api-key}") String restApiKey) {
+    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+    factory.setConnectTimeout(Duration.ofSeconds(5));
+    factory.setReadTimeout(Duration.ofSeconds(10));
+
     this.restClient = RestClient.builder()
+            .requestFactory(factory)
             .defaultHeader("Authorization", "KakaoAK " + restApiKey)
             .build();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java`
around lines 24 - 28, The RestClient created in KakaoGeocodingService lacks
connection/read timeouts; update the constructor that builds restClient to
configure timeouts via RestClient.builder(...).use the requestConfigCallback
(e.g., requestConfigBuilder ->
requestConfigBuilder.setConnectTimeout(...).setSocketTimeout(...)) and/or
httpClientConfigCallback as appropriate so the restClient created in the
KakaoGeocodingService constructor has sensible connect and socket/read timeout
values instead of waiting indefinitely.
src/main/java/com/be/sportizebe/global/kakao/dto/response/KakaoGeocodingResponse.java (1)

20-26: lat()/lng() 메서드에서 빈 리스트 접근 시 예외 발생 가능

isEmpty() 체크 없이 lat() 또는 lng()를 호출하면 IndexOutOfBoundsException이 발생합니다. 현재 KakaoGeocodingService에서 isEmpty() 체크 후 사용하고 있지만, 방어적 코딩 관점에서 메서드 내부에서도 검증하는 것이 안전합니다.

♻️ 방어적 코드 제안
 public double lat() {
+    if (isEmpty()) {
+        throw new IllegalStateException("No geocoding results available");
+    }
     return Double.parseDouble(documents.get(0).y());
 }

 public double lng() {
+    if (isEmpty()) {
+        throw new IllegalStateException("No geocoding results available");
+    }
     return Double.parseDouble(documents.get(0).x());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/global/kakao/dto/response/KakaoGeocodingResponse.java`
around lines 20 - 26, The lat() and lng() methods in KakaoGeocodingResponse
access documents.get(0) without validation and can throw
IndexOutOfBoundsException; update both methods (lat() and lng()) to first check
that documents is non-null and not empty and, if empty, throw a clear
IllegalStateException (or another domain-appropriate runtime exception) with a
descriptive message like "No geocoding documents available" so callers get a
meaningful error instead of an index exception; keep the return type double and
reference the documents field in your checks.
src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java (1)

29-56: 시설 관리 엔드포인트에서 관리자 정보 누락 검토 필요.

createFacility, updateFacility, deleteFacility 엔드포인트에서 @AuthenticationPrincipal UserAuthInfo를 사용하지 않고 있습니다. 매칭 생성에서는 userAuthInfo.getId()를 사용하는데, 시설 관리 작업에서도 감사 로그(audit log) 또는 생성자/수정자 추적이 필요하다면 동일하게 관리자 정보를 전달하는 것이 좋습니다.

현재 요구사항에 감사 기능이 없다면 무시해도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java`
around lines 29 - 56, The createFacility, updateFacility, and deleteFacility
endpoints are missing the administrator identity; add an
`@AuthenticationPrincipal` UserAuthInfo userAuthInfo parameter to each controller
method (createFacility, updateFacility, deleteFacility) and forward
userAuthInfo.getId() (or the full UserAuthInfo if your service expects it) into
the corresponding adminService calls (e.g., adminService.createFacility(request,
adminId) / updateFacility(facilityId, request, adminId) /
deleteFacility(facilityId, adminId)) so audit/creator/updater tracking can be
recorded; update the adminService method signatures accordingly to accept and
persist the admin identity where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/be/sportizebe/domain/facility/entity/SportsFacility.java`:
- Around line 46-48: SportsFacility.changeAddress에서 전달된 address를 그대로 대입해 DB 무결성
예외가 발생할 수 있으니, 메소드 시작부에서 address가 null이거나 빈 문자열(공백만 포함)인지 검사하고 적절히 처리하세요: 입력을
trim한 뒤 빈값이면 IllegalArgumentException(또는 도메인 전용 예외)을 던지고, 유효하면 this.address에
할당하도록 수정하십시오; 참조 대상은 메소드 changeAddress와 필드 address입니다.

In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`:
- Around line 131-134: The current check-then-act using
matchRoomRepository.existsById(matchId) followed by
matchRoomRepository.deleteById(matchId) can race; instead call
deleteById(matchId) directly inside MatchServiceImpl and catch the
repository-specific "not found" exception (e.g., EmptyResultDataAccessException
or the data-access exception your JPA/DAO layer throws) and rethrow new
CustomException(MatchErrorCode.MATCH_NOT_FOUND). Remove the existsById call and
ensure only the deleteById invocation is used, mapping repository exceptions to
the domain CustomException so concurrent deletes return the correct domain
error.

---

Nitpick comments:
In
`@src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java`:
- Around line 29-56: The createFacility, updateFacility, and deleteFacility
endpoints are missing the administrator identity; add an
`@AuthenticationPrincipal` UserAuthInfo userAuthInfo parameter to each controller
method (createFacility, updateFacility, deleteFacility) and forward
userAuthInfo.getId() (or the full UserAuthInfo if your service expects it) into
the corresponding adminService calls (e.g., adminService.createFacility(request,
adminId) / updateFacility(facilityId, request, adminId) /
deleteFacility(facilityId, adminId)) so audit/creator/updater tracking can be
recorded; update the adminService method signatures accordingly to accept and
persist the admin identity where needed.

In
`@src/main/java/com/be/sportizebe/global/kakao/dto/response/KakaoGeocodingResponse.java`:
- Around line 20-26: The lat() and lng() methods in KakaoGeocodingResponse
access documents.get(0) without validation and can throw
IndexOutOfBoundsException; update both methods (lat() and lng()) to first check
that documents is non-null and not empty and, if empty, throw a clear
IllegalStateException (or another domain-appropriate runtime exception) with a
descriptive message like "No geocoding documents available" so callers get a
meaningful error instead of an index exception; keep the return type double and
reference the documents field in your checks.

In `@src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java`:
- Around line 44-46: The Point SRID is being set twice: GEOMETRY_FACTORY is
already constructed with SRID 4326, so remove the redundant call to
point.setSRID(4326) in KakaoGeocodingService where you create the Point via
GEOMETRY_FACTORY.createPoint(new Coordinate(response.lng(), response.lat()));
keep GEOMETRY_FACTORY as the single source of truth for the SRID to avoid
duplication and potential inconsistency.
- Around line 24-28: The RestClient created in KakaoGeocodingService lacks
connection/read timeouts; update the constructor that builds restClient to
configure timeouts via RestClient.builder(...).use the requestConfigCallback
(e.g., requestConfigBuilder ->
requestConfigBuilder.setConnectTimeout(...).setSocketTimeout(...)) and/or
httpClientConfigCallback as appropriate so the restClient created in the
KakaoGeocodingService constructor has sensible connect and socket/read timeout
values instead of waiting indefinitely.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3a3917 and 56d1600.

📒 Files selected for processing (32)
  • src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.java
  • src/main/java/com/be/sportizebe/domain/admin/dto/request/.gitkeep
  • src/main/java/com/be/sportizebe/domain/admin/dto/response/.gitkeep
  • src/main/java/com/be/sportizebe/domain/admin/exception/AdminErrorCode.java
  • src/main/java/com/be/sportizebe/domain/admin/service/AdminService.java
  • src/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.java
  • src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java
  • src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityCreateRequest.java
  • src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityUpdateRequest.java
  • src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityNearResponse.java
  • src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityResponse.java
  • src/main/java/com/be/sportizebe/domain/facility/entity/SportsFacility.java
  • src/main/java/com/be/sportizebe/domain/facility/exception/FacilityErrorCode.java
  • src/main/java/com/be/sportizebe/domain/facility/mapper/FacilityMapper.java
  • src/main/java/com/be/sportizebe/domain/facility/repository/SportsFacilityRepository.java
  • src/main/java/com/be/sportizebe/domain/facility/repository/projection/FacilityNearProjection.java
  • src/main/java/com/be/sportizebe/domain/facility/service/SportsFacilityService.java
  • src/main/java/com/be/sportizebe/domain/facility/service/SportsFacilityServiceImpl.java
  • src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java
  • src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java
  • src/main/java/com/be/sportizebe/domain/match/dto/response/MatchDetailResponse.java
  • src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java
  • src/main/java/com/be/sportizebe/domain/match/dto/response/MatchResponse.java
  • src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java
  • src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java
  • src/main/java/com/be/sportizebe/domain/match/repository/projection/MatchNearProjection.java
  • src/main/java/com/be/sportizebe/domain/match/service/MatchService.java
  • src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java
  • src/main/java/com/be/sportizebe/global/kakao/KakaoGeocodingService.java
  • src/main/java/com/be/sportizebe/global/kakao/dto/response/KakaoGeocodingResponse.java
  • src/main/java/com/be/sportizebe/global/security/SecurityConfig.java
  • src/main/resources
💤 Files with no reviewable changes (2)
  • src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java
  • src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java

Comment on lines +46 to +48
public void changeAddress(String address) {
this.address = address;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

changeAddress에서 null/blank 방어가 없어 DB 무결성 예외로 이어질 수 있습니다.

Line 46-48은 address를 그대로 대입해서 엔티티 레벨에서 불변식을 보장하지 못합니다. 최소한 null/blank 검증을 여기서 처리하는 편이 안전합니다.

제안 수정안
+import java.util.Objects;
+
     public void changeAddress(String address) {
-        this.address = address;
+        String normalized = Objects.requireNonNull(address, "address must not be null").trim();
+        if (normalized.isEmpty()) {
+            throw new IllegalArgumentException("address must not be blank");
+        }
+        this.address = normalized;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/facility/entity/SportsFacility.java`
around lines 46 - 48, SportsFacility.changeAddress에서 전달된 address를 그대로 대입해 DB 무결성
예외가 발생할 수 있으니, 메소드 시작부에서 address가 null이거나 빈 문자열(공백만 포함)인지 검사하고 적절히 처리하세요: 입력을
trim한 뒤 빈값이면 IllegalArgumentException(또는 도메인 전용 예외)을 던지고, 유효하면 this.address에
할당하도록 수정하십시오; 참조 대상은 메소드 changeAddress와 필드 address입니다.

Comment on lines +131 to +134
if (!matchRoomRepository.existsById(matchId)) {
throw new CustomException(MatchErrorCode.MATCH_NOT_FOUND);
}
matchRoomRepository.deleteById(matchId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

삭제 로직의 check-then-act 패턴은 경쟁 조건을 유발할 수 있습니다.

Line 131-134는 existsById 확인 후 deleteById를 호출해서, 동시 삭제 상황에서 MATCH_NOT_FOUND 대신 예기치 않은 예외로 500이 날 수 있습니다. 한 번의 삭제 호출로 처리하고 예외를 도메인 예외로 매핑하는 방식이 더 안전합니다.

수정 예시
 `@Override`
 public void deleteMatch(Long matchId) {
-    if (!matchRoomRepository.existsById(matchId)) {
-        throw new CustomException(MatchErrorCode.MATCH_NOT_FOUND);
-    }
-    matchRoomRepository.deleteById(matchId);
+    try {
+        matchRoomRepository.deleteById(matchId);
+    } catch (org.springframework.dao.EmptyResultDataAccessException e) {
+        throw new CustomException(MatchErrorCode.MATCH_NOT_FOUND);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`
around lines 131 - 134, The current check-then-act using
matchRoomRepository.existsById(matchId) followed by
matchRoomRepository.deleteById(matchId) can race; instead call
deleteById(matchId) directly inside MatchServiceImpl and catch the
repository-specific "not found" exception (e.g., EmptyResultDataAccessException
or the data-access exception your JPA/DAO layer throws) and rethrow new
CustomException(MatchErrorCode.MATCH_NOT_FOUND). Remove the existsById call and
ensure only the deleteById invocation is used, mapping repository exceptions to
the domain CustomException so concurrent deletes return the correct domain
error.

@angoroa angoroa merged commit 49c2144 into develop Feb 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.

2 participants