diff --git a/src/main/java/ita/tinybite/domain/party/dto/response/PartyCardResponse.java b/src/main/java/ita/tinybite/domain/party/dto/response/PartyCardResponse.java index c05ed69..b3a28aa 100644 --- a/src/main/java/ita/tinybite/domain/party/dto/response/PartyCardResponse.java +++ b/src/main/java/ita/tinybite/domain/party/dto/response/PartyCardResponse.java @@ -1,8 +1,13 @@ package ita.tinybite.domain.party.dto.response; +import ita.tinybite.domain.party.entity.Party; +import ita.tinybite.domain.party.enums.ParticipantStatus; import ita.tinybite.domain.party.enums.PartyCategory; +import ita.tinybite.domain.party.enums.PartyStatus; import lombok.*; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; @Getter @NoArgsConstructor @@ -20,4 +25,88 @@ public class PartyCardResponse { private Boolean isClosed; // 마감 여부 private PartyCategory category; private LocalDateTime createdAt; + + public static PartyCardResponse from(Party party, int currentParticipants, boolean isHost, ParticipantStatus status) { + return PartyCardResponse.builder() + .partyId(party.getId()) + .thumbnailImage(party.getThumbnailImage()) + .title(party.getTitle()) + .pricePerPerson(calculatePricePerPerson(party, currentParticipants)) + .participantStatus(formatParticipantStatus(currentParticipants, party.getMaxParticipants())) + .distance(null) // 거리 계산은 별도 처리 필요 + .distanceKm(null) // 거리 계산은 별도 처리 필요 + .timeAgo(calculateTimeAgo(party.getCreatedAt())) + .isClosed(checkIfClosed(party, currentParticipants)) + .category(party.getCategory()) + .createdAt(party.getCreatedAt()) + .build(); + } + private static String getThumbnailImage(Party party) { + if (party.getImage() != null && !party.getImage().isEmpty()) { + return party.getImage(); + } + return "/images/default-party-thumbnail.jpg"; // 기본 이미지 + } + + /** + * 1/N 가격 계산 + */ + private static Integer calculatePricePerPerson(Party party, int currentParticipants) { + if (party.getPrice() == null || currentParticipants == 0) { + return null; + } + return party.getPrice() / currentParticipants; + } + + /** + * 참가자 상태 포맷팅 "1/4명" + */ + private static String formatParticipantStatus(int current, int max) { + return String.format("%d/%d명", current, max); + } + + /** + * 시간 경과 계산 "10분 전", "3시간 전" + */ + private static String calculateTimeAgo(LocalDateTime createdAt) { + if (createdAt == null) { + return ""; + } + + LocalDateTime now = LocalDateTime.now(); + Duration duration = Duration.between(createdAt, now); + + long minutes = duration.toMinutes(); + long hours = duration.toHours(); + long days = duration.toDays(); + + if (minutes < 1) { + return "방금 전"; + } else if (minutes < 60) { + return minutes + "분 전"; + } else if (hours < 24) { + return hours + "시간 전"; + } else if (days < 7) { + return days + "일 전"; + } else { + return createdAt.format(DateTimeFormatter.ofPattern("MM.dd")); + } + } + + /** + * 마감 여부 확인 + */ + private static Boolean checkIfClosed(Party party, int currentParticipants) { + // 1. 파티 상태가 CLOSED인 경우 + if (party.getStatus() == PartyStatus.CLOSED) { + return true; + } + + // 2. 정원이 다 찬 경우 + if (currentParticipants >= party.getMaxParticipants()) { + return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/main/java/ita/tinybite/domain/user/controller/UserController.java b/src/main/java/ita/tinybite/domain/user/controller/UserController.java index af7f0b5..ee2df3c 100644 --- a/src/main/java/ita/tinybite/domain/user/controller/UserController.java +++ b/src/main/java/ita/tinybite/domain/user/controller/UserController.java @@ -6,9 +6,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import ita.tinybite.domain.auth.dto.response.UserDto; +import ita.tinybite.domain.party.dto.response.PartyCardResponse; import ita.tinybite.domain.user.dto.req.UpdateUserReqDto; -import ita.tinybite.domain.user.dto.res.PartyResponse; import ita.tinybite.domain.user.dto.res.UserResDto; import ita.tinybite.domain.user.service.UserService; import ita.tinybite.global.response.APIResponse; @@ -82,13 +81,13 @@ public APIResponse deleteUser() { @Operation(summary = "활성 파티 목록 조회", description = "사용자가 참여 중인 활성 파티 목록을 조회합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "조회 성공", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = PartyResponse.class)))), + content = @Content(array = @ArraySchema(schema = @Schema(implementation = PartyCardResponse.class)))), @ApiResponse(responseCode = "401", description = "인증 실패") }) @GetMapping("/parties/active") - public ResponseEntity> getActiveParties( + public ResponseEntity> getActiveParties( @AuthenticationPrincipal Long userId) { - List response = userService.getActiveParties(userId); + List response = userService.getActiveParties(userId); return ResponseEntity.ok(response); } diff --git a/src/main/java/ita/tinybite/domain/user/dto/res/PartyResponse.java b/src/main/java/ita/tinybite/domain/user/dto/res/PartyResponse.java deleted file mode 100644 index 6fd56df..0000000 --- a/src/main/java/ita/tinybite/domain/user/dto/res/PartyResponse.java +++ /dev/null @@ -1,45 +0,0 @@ -package ita.tinybite.domain.user.dto.res; - -import ita.tinybite.domain.party.entity.Party; -import ita.tinybite.domain.party.enums.ParticipantStatus; -import ita.tinybite.domain.party.enums.PartyStatus; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@AllArgsConstructor -@Builder -public class PartyResponse { - private Long id; - private String title; - private String description; - private Integer maxParticipants; - private Integer currentParticipants; - private PartyStatus status; - private String hostUsername; - private LocalDateTime startDate; - private LocalDateTime endDate; - private LocalDateTime createdAt; - private boolean isHost; - private ParticipantStatus participantStatus; - - public static PartyResponse from(Party party, int currentParticipants, boolean isHost, ParticipantStatus participantStatus) { - return PartyResponse.builder() - .id(party.getId()) - .title(party.getTitle()) - .description(party.getDescription()) - .maxParticipants(party.getMaxParticipants()) - .currentParticipants(currentParticipants) - .status(party.getStatus()) - .hostUsername(party.getHost().getNickname()) - .startDate(party.getCreatedAt()) - .endDate(party.getClosedAt()) - .createdAt(party.getCreatedAt()) - .isHost(isHost) - .participantStatus(participantStatus) - .build(); - } -} diff --git a/src/main/java/ita/tinybite/domain/user/service/UserService.java b/src/main/java/ita/tinybite/domain/user/service/UserService.java index 95ff669..c2ac755 100644 --- a/src/main/java/ita/tinybite/domain/user/service/UserService.java +++ b/src/main/java/ita/tinybite/domain/user/service/UserService.java @@ -1,14 +1,13 @@ package ita.tinybite.domain.user.service; import ita.tinybite.domain.auth.service.SecurityProvider; +import ita.tinybite.domain.party.dto.response.PartyCardResponse; import ita.tinybite.domain.party.entity.Party; import ita.tinybite.domain.party.entity.PartyParticipant; import ita.tinybite.domain.party.enums.ParticipantStatus; import ita.tinybite.domain.party.enums.PartyStatus; import ita.tinybite.domain.party.repository.PartyParticipantRepository; -import ita.tinybite.domain.user.constant.UserStatus; import ita.tinybite.domain.user.dto.req.UpdateUserReqDto; -import ita.tinybite.domain.user.dto.res.PartyResponse; import ita.tinybite.domain.user.dto.res.UserResDto; import ita.tinybite.domain.user.entity.User; import ita.tinybite.domain.user.repository.UserRepository; @@ -16,11 +15,13 @@ import ita.tinybite.global.exception.errorcode.AuthErrorCode; import ita.tinybite.global.location.LocationService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Service +@Transactional(readOnly = true) public class UserService { private final SecurityProvider securityProvider; @@ -43,17 +44,20 @@ public UserResDto getUser() { return UserResDto.of(user); } + @Transactional public void updateUser(UpdateUserReqDto req) { User user = securityProvider.getCurrentUser(); user.update(req); } + @Transactional public void updateLocation(String latitude, String longitude) { User user = securityProvider.getCurrentUser(); String location = locationService.getLocation(latitude, longitude); user.updateLocation(location); } + @Transactional public void deleteUser() { userRepository.delete(securityProvider.getCurrentUser()); } @@ -63,7 +67,7 @@ public void validateNickname(String nickname) { throw BusinessException.of(AuthErrorCode.DUPLICATED_NICKNAME); } - public List getActiveParties(Long userId) { + public List getActiveParties(Long userId) { List participants = participantRepository .findActivePartiesByUserId( userId, @@ -77,7 +81,7 @@ public List getActiveParties(Long userId) { int currentParticipants = participantRepository .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); boolean isHost = party.getHost().getUserId().equals(userId); - return PartyResponse.from(party, currentParticipants, isHost,pp.getStatus()); + return PartyCardResponse.from(party, currentParticipants, isHost,pp.getStatus()); }) .collect(Collectors.toList()); }