diff --git a/src/main/java/ita/tinybite/domain/chat/dto/GroupChatRoomDetailResDto.java b/src/main/java/ita/tinybite/domain/chat/dto/GroupChatRoomDetailResDto.java index d7598f0..da514c8 100644 --- a/src/main/java/ita/tinybite/domain/chat/dto/GroupChatRoomDetailResDto.java +++ b/src/main/java/ita/tinybite/domain/chat/dto/GroupChatRoomDetailResDto.java @@ -1,7 +1,33 @@ package ita.tinybite.domain.chat.dto; +import ita.tinybite.domain.chat.entity.ChatRoom; +import ita.tinybite.domain.party.enums.PartyStatus; import lombok.Builder; @Builder -public record GroupChatRoomDetailResDto() { +public record GroupChatRoomDetailResDto( + Long groupChatRoomId, + Long partyId, + String partyTitle, + PartyStatus status, + int currentParticipantCnt, + int maxParticipantCnt, + String formattedPartyCnt +) { + public static GroupChatRoomDetailResDto of(ChatRoom groupChatRoom) { + return GroupChatRoomDetailResDto.builder() + .groupChatRoomId(groupChatRoom.getId()) + .partyId(groupChatRoom.getParty().getId()) + .partyTitle(groupChatRoom.getParty().getTitle()) + .status(groupChatRoom.getParty().getStatus()) + .currentParticipantCnt(groupChatRoom.getParty().getCurrentParticipants()) + .maxParticipantCnt(groupChatRoom.getParty().getMaxParticipants()) + .formattedPartyCnt(formatParticipantStatus(groupChatRoom.getParty().getCurrentParticipants(), groupChatRoom.getParty().getMaxParticipants())) + .build(); + } + + private static String formatParticipantStatus(int current, int max) { + return String.format("%d/%d명", current, max); + } + } diff --git a/src/main/java/ita/tinybite/domain/chat/dto/res/OneToOneChatRoomDetailResDto.java b/src/main/java/ita/tinybite/domain/chat/dto/res/OneToOneChatRoomDetailResDto.java index 45cfc49..401b886 100644 --- a/src/main/java/ita/tinybite/domain/chat/dto/res/OneToOneChatRoomDetailResDto.java +++ b/src/main/java/ita/tinybite/domain/chat/dto/res/OneToOneChatRoomDetailResDto.java @@ -40,12 +40,12 @@ public static OneToOneChatRoomDetailResDto of(Party party, PartyParticipant part switch(participantStatus) { case PENDING -> { - if(type.equals(ParticipantType.PARTICIPANT)) { + if(type.equals(ParticipantType.HOST)) { // type = HOST resDtoBuilder .participantStatus(ParticipantStatus.REQUESTED) .targetProfileImage(targetUser.getProfileImage()) .targetLocation(targetUser.getLocation()); - } else { // type = HOST + } else { // type = PARTICIPANT resDtoBuilder .participantStatus(ParticipantStatus.PENDING); } diff --git a/src/main/java/ita/tinybite/domain/chat/service/ChatRoomService.java b/src/main/java/ita/tinybite/domain/chat/service/ChatRoomService.java index 64fd3a7..0cbbd37 100644 --- a/src/main/java/ita/tinybite/domain/chat/service/ChatRoomService.java +++ b/src/main/java/ita/tinybite/domain/chat/service/ChatRoomService.java @@ -17,6 +17,8 @@ import ita.tinybite.domain.party.enums.ParticipantStatus; import ita.tinybite.domain.party.repository.PartyParticipantRepository; import ita.tinybite.domain.user.entity.User; +import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.exception.errorcode.ChatRoomErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Limit; import org.springframework.stereotype.Service; @@ -101,21 +103,21 @@ public List getGroupRooms() { public OneToOneChatRoomDetailResDto getOneToOneRoom(Long chatRoomId) { User currentUser = securityProvider.getCurrentUser(); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId).orElseThrow(); - PartyParticipant partyParticipant = partyParticipantRepository.findByOneToOneChatRoom(chatRoom).orElseThrow(); - ChatRoom groupChatRoom = chatRoomRepository.findByPartyAndType(chatRoom.getParty(), ChatRoomType.GROUP).orElseGet(null); - - User host = chatRoom.getParty().getHost(); - host.getNickname(); + if(!chatRoom.getType().equals(ChatRoomType.ONE_TO_ONE)) throw BusinessException.of(ChatRoomErrorCode.NOT_ONE_TO_ONE); - User targetUser = partyParticipant.getUser(); - targetUser.getNickname(); + PartyParticipant partyParticipant = partyParticipantRepository.findByOneToOneChatRoomAndStatus(chatRoom, ParticipantStatus.PENDING); + ChatRoom groupChatRoom = chatRoomRepository.findByPartyAndType(chatRoom.getParty(), ChatRoomType.GROUP).orElseGet(null); return OneToOneChatRoomDetailResDto.of(chatRoom.getParty(), partyParticipant, currentUser, groupChatRoom); } public GroupChatRoomDetailResDto getGroupRoom(Long chatRoomId) { - return null; + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId).orElseThrow(); + + if(!chatRoom.getType().equals(ChatRoomType.GROUP)) throw BusinessException.of(ChatRoomErrorCode.NOT_GROUP); + + return GroupChatRoomDetailResDto.of(chatRoom); } private static String getTimeAgo(LocalDateTime then) { diff --git a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java index 9454125..4bbeeaa 100644 --- a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java +++ b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java @@ -380,25 +380,22 @@ public ResponseEntity getPartyList( ) { Double lat = null; Double lon = null; - // 거리순 정렬 시 위치 정보 검증 - if (sortType == PartySortType.DISTANCE) { - if (userLat == null || userLon == null) { - throw new IllegalArgumentException("거리순 정렬을 위해서는 현재 위치 정보가 필요합니다."); - } - - try { - lat = Double.parseDouble(userLat); - lon = Double.parseDouble(userLon); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - String.format("위치 정보 형식이 올바르지 않습니다. userLat: %s, userLon: %s", userLat, userLon) - ); - } - - // 위도/경도 범위 검증 - if (lat < -90 || lat > 90 || lon < -180 || lon > 180) { - throw new IllegalArgumentException("위도/경도 값이 유효한 범위를 벗어났습니다."); - } + if (userLat == null || userLon == null) { + throw new IllegalArgumentException("거리순 정렬을 위해서는 현재 위치 정보가 필요합니다."); + } + + try { + lat = Double.parseDouble(userLat); + lon = Double.parseDouble(userLon); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + String.format("위치 정보 형식이 올바르지 않습니다. userLat: %s, userLon: %s", userLat, userLon) + ); + } + + // 위도/경도 범위 검증 + if (lat < -90 || lat > 90 || lon < -180 || lon > 180) { + throw new IllegalArgumentException("위도/경도 값이 유효한 범위를 벗어났습니다."); } PartyListRequest request = PartyListRequest.builder() diff --git a/src/main/java/ita/tinybite/domain/party/dto/response/PartyDetailResponse.java b/src/main/java/ita/tinybite/domain/party/dto/response/PartyDetailResponse.java index 5c70009..9ce72ad 100644 --- a/src/main/java/ita/tinybite/domain/party/dto/response/PartyDetailResponse.java +++ b/src/main/java/ita/tinybite/domain/party/dto/response/PartyDetailResponse.java @@ -16,6 +16,7 @@ public class PartyDetailResponse { private String title; private PartyCategory category; private String timeAgo; + private String town; // 파티장 정보 private HostInfo host; diff --git a/src/main/java/ita/tinybite/domain/party/entity/Party.java b/src/main/java/ita/tinybite/domain/party/entity/Party.java index a6b5e37..39625c9 100644 --- a/src/main/java/ita/tinybite/domain/party/entity/Party.java +++ b/src/main/java/ita/tinybite/domain/party/entity/Party.java @@ -52,6 +52,9 @@ public class Party { @Column(length = 500) private String link; // 링크 (예: 배달앱 링크) + @Column(length = 30) + private String town; + @Embedded @Column private PickupLocation pickupLocation; diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java index 7366746..18bc945 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java @@ -91,8 +91,10 @@ boolean existsByParty_IdAndUser_UserIdAndStatus( ParticipantStatus status ); - @Query("SELECT pp FROM PartyParticipant pp WHERE pp.party.id = :partyId AND pp.user.id = :userId") + @Query("SELECT pp FROM PartyParticipant pp WHERE pp.party.id = :partyId AND pp.user.userId = :userId") Optional findByPartyIdAndUserId(@Param("partyId") Long partyId, @Param("userId") Long userId); Optional findByOneToOneChatRoom(ChatRoom oneToOneChatRoom); + + PartyParticipant findByOneToOneChatRoomAndStatus(ChatRoom oneToOneChatRoom, ParticipantStatus status); } \ No newline at end of file diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java index fc92205..29bccde 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java @@ -22,4 +22,8 @@ public interface PartyRepository extends JpaRepository { List findByPickupLocation_PlaceAndCategory(String place, PartyCategory category); List findByHostUserIdAndStatus(Long userId, PartyStatus partyStatus); + + List findByTown(String location); + + List findByTownAndCategory(String location, PartyCategory category); } diff --git a/src/main/java/ita/tinybite/domain/party/service/PartyService.java b/src/main/java/ita/tinybite/domain/party/service/PartyService.java index b627e45..d9821e4 100644 --- a/src/main/java/ita/tinybite/domain/party/service/PartyService.java +++ b/src/main/java/ita/tinybite/domain/party/service/PartyService.java @@ -70,6 +70,8 @@ public Long createParty(Long userId, PartyCreateRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + String myTown = getMyTown(request.getPickupLocation().getPickupLatitude(), request.getPickupLocation().getPickupLongitude()); + // 카테고리별 유효성 검증 validateProductLink(request.getCategory(), request.getProductLink()); @@ -78,6 +80,7 @@ public Long createParty(Long userId, PartyCreateRequest request) { .category(request.getCategory()) .price(request.getTotalPrice()) .maxParticipants(request.getMaxParticipants()) + .town(myTown) .pickupLocation(PickupLocation.builder() .place(request.getPickupLocation().getPlace()) .pickupLatitude(request.getPickupLocation().getPickupLatitude()) @@ -140,13 +143,17 @@ public PartyListResponse getPartyList(Long userId, PartyListRequest request) { int page = request.getPage() != null ? request.getPage() : 0; int size = request.getSize() != null ? request.getSize() : 20; + String myTown = getMyTown(request.getUserLat(),request.getUserLon()); + // 동네 기준으로 파티 조회 - List parties = fetchPartiesByLocation(user, request); + List parties = fetchPartiesByTown(user, request, myTown); // PartyCardResponse로 변환 List cardResponses = parties.stream() .map(party -> { - if (request.getSortType() == PartySortType.DISTANCE) { + // 위치 정보가 있으면 항상 거리 계산 + if (request.getUserLat() != null && request.getUserLon() != null + && party.getPickupLocation() != null) { double distance = DistanceCalculator.calculateDistance( request.getUserLat(), request.getUserLon(), @@ -155,21 +162,22 @@ public PartyListResponse getPartyList(Long userId, PartyListRequest request) { ); return convertToCardResponseWithDistance(party, distance); } - return convertToCardResponse(party,party.getCreatedAt()); + return convertToCardResponse(party, party.getCreatedAt()); }) .toList(); + // 진행 중 파티 정렬 List activeParties = cardResponses.stream() .filter(p -> !p.getIsClosed()) .sorted(getComparator(request.getSortType())) - .collect(Collectors.toList()); + .toList(); // 마감된 파티 정렬 List closedParties = cardResponses.stream() .filter(PartyCardResponse::getIsClosed) .sorted(getComparator(request.getSortType())) - .collect(Collectors.toList()); + .toList(); // 진행 중 + 마감된 파티 합치기 (진행 중이 먼저) List allParties = new ArrayList<>(); @@ -380,17 +388,12 @@ private PartyDetailResponse convertToDetailResponse(Party party, double distance int currentCount = party.getCurrentParticipants(); int pricePerPerson = party.getPrice() / party.getMaxParticipants(); - // 이미지 파싱 -// List images = new ArrayList<>(); -// if (party.getImages() != null && !party.getImages().isEmpty()) { -// images = List.of(party.getImages()); -// } - return PartyDetailResponse.builder() .partyId(party.getId()) .title(party.getTitle()) .category(party.getCategory()) .timeAgo(party.getTimeAgo()) + .town(party.getTown()) .host(HostInfo.builder() .userId(party.getHost().getUserId()) .nickname(party.getHost().getNickname()) @@ -805,7 +808,7 @@ private String formatDistanceIfExists(Double distance) { } //카테고리에 따라 파티 조회 - private List fetchPartiesByLocation(User user, PartyListRequest request) { + private List fetchPartiesByTown(User user, PartyListRequest request,String myTown) { if (user == null || user.getLocation() == null) { return List.of(); } @@ -814,9 +817,9 @@ private List fetchPartiesByLocation(User user, PartyListRequest request) PartyCategory category = request.getCategory(); if (category == PartyCategory.ALL) { - return partyRepository.findByPickupLocation_Place(location); + return partyRepository.findByTown(location); } else { - return partyRepository.findByPickupLocation_PlaceAndCategory(location, category); + return partyRepository.findByTownAndCategory(location, category); } } @@ -839,5 +842,9 @@ private PartyCardResponse convertToCardResponseWithDistance( response.addDistanceKm(distance); return response; } + + private String getMyTown(Double pickupLatitude, Double pickupLongitude) { + return locationService.getLocation(Double.toString(pickupLatitude), Double.toString(pickupLongitude)); + } } diff --git a/src/main/java/ita/tinybite/global/exception/errorcode/ChatRoomErrorCode.java b/src/main/java/ita/tinybite/global/exception/errorcode/ChatRoomErrorCode.java new file mode 100644 index 0000000..fd5b811 --- /dev/null +++ b/src/main/java/ita/tinybite/global/exception/errorcode/ChatRoomErrorCode.java @@ -0,0 +1,23 @@ +package ita.tinybite.global.exception.errorcode; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum ChatRoomErrorCode implements ErrorCode { + + NOT_ONE_TO_ONE(HttpStatus.BAD_REQUEST, "NOT_ONE_TO_ONE", "일대일 채팅이 아닙니다."), + NOT_GROUP(HttpStatus.BAD_REQUEST, "NOT_GROUP", "그룹 채팅이 아닙니다."), + + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + ChatRoomErrorCode(HttpStatus httpStatus, String code, String message) { + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } +}