diff --git a/.gitignore b/.gitignore index 871f511..cc12f90 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ src/main/resources/*.yml # 기타 CLAUDE.md +Makefile \ No newline at end of file diff --git a/src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java b/src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java index efa6bbb..d3202af 100644 --- a/src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java +++ b/src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java @@ -3,6 +3,8 @@ import com.be.sportizebe.domain.match.dto.request.MatchNearRequest; import com.be.sportizebe.domain.match.dto.response.MatchDetailResponse; import com.be.sportizebe.domain.match.dto.response.MatchNearResponse; +import com.be.sportizebe.domain.match.dto.response.MatchParticipantResponse; +import com.be.sportizebe.domain.match.dto.response.MatchResponse; import com.be.sportizebe.domain.match.dto.response.MyMatchResponse; import com.be.sportizebe.domain.match.service.MatchService; import com.be.sportizebe.global.cache.dto.UserAuthInfo; @@ -23,6 +25,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -75,7 +78,25 @@ public ResponseEntity>> getMyMatches( return ResponseEntity.ok(BaseResponse.success("내 매칭 목록 조회 성공", response)); } -@Operation(summary = "내 주변 매칭 목록 조회", description = "매칭상태가 OPEN인 매칭들만 보여준다.") +@Operation(summary = "매칭 참가자 목록 조회", description = "JOINED 상태인 참가자만 반환한다.") + @GetMapping("/{matchId}/participants") + public ResponseEntity>> getMatchParticipants( + @PathVariable Long matchId + ) { + List response = matchService.getMatchParticipants(matchId); + return ResponseEntity.ok(BaseResponse.success("매칭 참가자 목록 조회 성공", response)); + } + + @Operation(summary = "구장 기반 매칭 목록 조회", description = "해당 구장의 OPEN/FULL 상태이며 아직 시작 전인 매칭을 반환한다.") + @GetMapping + public ResponseEntity>> getMatchesByFacility( + @RequestParam Long facilityId + ) { + List response = matchService.getMatchesByFacility(facilityId); + return ResponseEntity.ok(BaseResponse.success("구장 매칭 목록 조회 성공", response)); + } + + @Operation(summary = "내 주변 매칭 목록 조회", description = "매칭상태가 OPEN인 매칭들만 보여준다.") @GetMapping("/near") public ResponseEntity>> getNearMatches( @ParameterObject @Valid @ModelAttribute MatchNearRequest request diff --git a/src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java b/src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java index ad7a326..52130b2 100644 --- a/src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java +++ b/src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java @@ -32,4 +32,8 @@ public interface MatchParticipantRepository extends JpaRepository findAllByUserIdAndStatusFetch(@Param("userId") Long userId, @Param("status") MatchParticipantStatus status); + // 매칭 참가자 목록 (N+1 방지 fetch join) + @Query("SELECT mp FROM MatchParticipant mp JOIN FETCH mp.user WHERE mp.matchRoom.id = :matchId AND mp.status = :status") + List findAllByMatchRoomIdAndStatusFetch(@Param("matchId") Long matchId, @Param("status") MatchParticipantStatus status); + } \ No newline at end of file diff --git a/src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java b/src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java index 921d0a6..11ddf4d 100644 --- a/src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java +++ b/src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java @@ -1,6 +1,7 @@ package com.be.sportizebe.domain.match.repository; import com.be.sportizebe.domain.match.entity.MatchRoom; +import com.be.sportizebe.domain.match.entity.MatchStatus; import com.be.sportizebe.domain.match.repository.projection.MatchNearProjection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; @@ -46,6 +47,10 @@ List findNear( @Param("sportsName") String sportsName ); + // 구장 기반 모집 중 매칭 목록 (OPEN/FULL, 미래 일정만) + List findByFacilityIdAndStatusInAndScheduledAtAfterOrderByScheduledAtAsc( + Long facilityId, List statuses, java.time.LocalDateTime now); + // scheduledAt이 지난 OPEN/FULL → CLOSED @Modifying @Query(value = """ diff --git a/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java b/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java index 42bd489..22882f9 100644 --- a/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java +++ b/src/main/java/com/be/sportizebe/domain/match/service/MatchService.java @@ -4,6 +4,7 @@ import com.be.sportizebe.domain.match.dto.request.MatchNearRequest; import com.be.sportizebe.domain.match.dto.response.MatchDetailResponse; import com.be.sportizebe.domain.match.dto.response.MatchNearResponse; +import com.be.sportizebe.domain.match.dto.response.MatchParticipantResponse; import com.be.sportizebe.domain.match.dto.response.MatchResponse; import com.be.sportizebe.domain.match.dto.response.MyMatchResponse; @@ -27,4 +28,8 @@ public interface MatchService { List getMyMatches(Long userId); // 참여 중인 매칭 목록 + List getMatchParticipants(Long matchId); // 매칭 참가자 목록 + + List getMatchesByFacility(Long facilityId); // 구장 기반 매칭 목록 + } diff --git a/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java b/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java index 28958eb..3af35bf 100644 --- a/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java +++ b/src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java @@ -3,6 +3,7 @@ import com.be.sportizebe.domain.match.dto.request.MatchNearRequest; import com.be.sportizebe.domain.match.dto.response.MatchDetailResponse; import com.be.sportizebe.domain.match.dto.response.MatchNearResponse; +import com.be.sportizebe.domain.match.dto.response.MatchParticipantResponse; import com.be.sportizebe.domain.match.dto.response.MatchResponse; import com.be.sportizebe.domain.match.dto.response.MyMatchResponse; import com.be.sportizebe.domain.match.entity.MatchParticipant; @@ -23,6 +24,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.be.sportizebe.domain.match.dto.request.MatchCreateRequest; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -165,6 +167,33 @@ public List getMyMatches(Long userId) { } @Override + @Transactional(readOnly = true) + public List getMatchParticipants(Long matchId) { + if (!matchRoomRepository.existsById(matchId)) { + throw new CustomException(MatchErrorCode.MATCH_NOT_FOUND); + } + return matchParticipantRepository + .findAllByMatchRoomIdAndStatusFetch(matchId, MatchParticipantStatus.JOINED) + .stream() + .map(MatchParticipantResponse::from) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public List getMatchesByFacility(Long facilityId) { + return matchRoomRepository + .findByFacilityIdAndStatusInAndScheduledAtAfterOrderByScheduledAtAsc( + facilityId, + List.of(MatchStatus.OPEN, MatchStatus.FULL), + LocalDateTime.now() + ) + .stream() + .map(MatchResponse::from) + .toList(); + } + + @Override @Transactional(readOnly = true) public List getNearMatches(MatchNearRequest request) { String sportsName = request.getSportsName() == null