diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java index c1d506ef..1458314b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.entity.meeting; +import java.util.List; + import org.sopt.makers.crew.main.global.util.Time; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.springframework.data.domain.Page; @@ -7,4 +9,6 @@ public interface MeetingSearchRepository { Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time); + + List findRecommendMeetings(List meetingIds, Time time); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java index 469c552c..2be1a1c3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java @@ -31,7 +31,6 @@ @RequiredArgsConstructor public class MeetingSearchRepositoryImpl implements MeetingSearchRepository { private final JPAQueryFactory queryFactory; - //private final Time time; /** * @note: canJoinOnlyActiveGeneration 처리 유의 @@ -48,6 +47,26 @@ public Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); } + /** + * @param meetingIds : 조회하려는 모임 id 리스트 + * @implSpec : meetingIds 가 null 인 경우, '지금 모집중인 모임' 반환 + * */ + @Override + public List findRecommendMeetings(List meetingIds, Time time) { + + JPAQuery query = queryFactory.selectFrom(meeting) + .innerJoin(meeting.user, user) + .fetchJoin(); + + if (meetingIds == null) { + query.where(eqStatus(List.of(String.valueOf(EnMeetingStatus.APPLY_ABLE.getValue())), time)); + return query.fetch(); + } + + query.where(meeting.id.in(meetingIds)); + return query.fetch(); + } + private List getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time) { return queryFactory .selectFrom(meeting) @@ -153,7 +172,7 @@ private BooleanExpression eqJoinableParts(MeetingJoinablePart[] joinableParts) { // SQL 템플릿을 사용하여 BooleanExpression 생성 return Expressions.booleanTemplate( - "arraycontains({0}, "+ joinablePartsToString + ") || '' = 'true'", + "arraycontains({0}, " + joinablePartsToString + ") || '' = 'true'", meeting.joinableParts, joinablePartsToString ); diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index aa3d12e2..4ec4ca22 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -18,11 +18,13 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetRecommendDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -134,4 +136,12 @@ ResponseEntity getAppliesCsvFileUrl( @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content),}) ResponseEntity getMeetingById(@PathVariable Integer meetingId, Principal principal); + + @Operation(summary = "추천 모임 목록 조회", description = "추천 모임 목록 조회, 쿼리파라미터가 없는 경우 '지금 모집중인 모임' 반환") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "추천 모임 목록 조회 성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content) + }) + ResponseEntity getRecommendMeetingsByIds( + @RequestParam(name = "meetingIds", required = false) @Parameter(description = "추천할 모임들의 ID 리스트", example = "[101, 102, 103]") List meetingIds, + Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 5d555f3b..270fbc52 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -20,6 +20,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetRecommendDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service; import org.springframework.http.HttpStatus; @@ -196,4 +197,14 @@ public ResponseEntity getMeetingById(@PathVa return ResponseEntity.ok(meetingV2Service.getMeetingById(meetingId, userId)); } + + @Override + @GetMapping("/recommend") + public ResponseEntity getRecommendMeetingsByIds( + @RequestParam(name = "meetingIds", required = false) List meetingIds, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + + return ResponseEntity.ok().body(meetingV2Service.getRecommendMeetingsByIds(meetingIds, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetRecommendDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetRecommendDto.java new file mode 100644 index 00000000..7d661137 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetRecommendDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.global.dto.MeetingResponseDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "MeetingV2GetRecommendDto", description = "추천 모임 목록 조회 응답 Dto") +public record MeetingV2GetRecommendDto( + @Schema(description = "모임 객체 목록", example = "") + @NotNull + List meetings +) { + public static MeetingV2GetRecommendDto from(List meetings) { + return new MeetingV2GetRecommendDto(meetings); + } +} + diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index e902b6f4..f50f2efe 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -16,6 +16,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetRecommendDto; public interface MeetingV2Service { @@ -47,4 +48,6 @@ AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List meetingIds, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index d1997fa4..2924c2d5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -78,6 +78,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetRecommendDto; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Caching; import org.springframework.data.domain.Page; @@ -448,6 +449,22 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte meetingLeader, applyWholeInfoDtos, time.now()); } + @Override + public MeetingV2GetRecommendDto getRecommendMeetingsByIds(List meetingIds, Integer userId) { + + List meetings = meetingRepository.findRecommendMeetings(meetingIds, time); + List foundMeetingIds = meetings.stream().map(Meeting::getId).toList(); + + Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(foundMeetingIds)); + + List meetingResponseDtos = meetings.stream() + .map(meeting -> MeetingResponseDto.of(meeting, meeting.getUser(), + allApplies.getApprovedCount(meeting.getId()), time.now())) + .toList(); + + return MeetingV2GetRecommendDto.from(meetingResponseDtos); + } + private void deleteCsvFile(String filePath) { try { Files.deleteIfExists(Paths.get(filePath));