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 fbb9974..a03256b 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java @@ -6,6 +6,7 @@ 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.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -24,4 +25,23 @@ public interface PartyParticipantRepository extends JpaRepository findByPartyAndStatus(Party party, ParticipantStatus status); boolean existsByPartyAndUserAndStatus(Party party, User user, ParticipantStatus status); + + @Query("SELECT pp FROM PartyParticipant pp " + + "WHERE pp.user.id = :userId " + + "AND pp.party.status =:partyStatus " + + "AND pp.status = :participantStatus") + List findActivePartiesByUserId( + @Param("userId") Long userId, + @Param("partyStatuses") PartyStatus partyStatus, + @Param("participantStatus") ParticipantStatus participantStatus + ); + + @Query("SELECT COUNT(pp) FROM PartyParticipant pp " + + "WHERE pp.party.id = :partyId " + + "AND pp.status = :status") + int countByPartyIdAndStatus( + @Param("partyId") Long partyId, + @Param("status") ParticipantStatus status + ); + } \ 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 d1d72a6..af7f0b5 100644 --- a/src/main/java/ita/tinybite/domain/user/controller/UserController.java +++ b/src/main/java/ita/tinybite/domain/user/controller/UserController.java @@ -1,11 +1,24 @@ package ita.tinybite.domain.user.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +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.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; import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.List; + import static ita.tinybite.global.response.APIResponse.success; @RestController @@ -18,17 +31,36 @@ public UserController(UserService userService) { this.userService = userService; } + @Operation(summary = "내 정보 조회", description = "현재 로그인한 사용자의 정보를 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공", + content = @Content(schema = @Schema(implementation = UserResDto.class))), + @ApiResponse(responseCode = "401", description = "인증 실패", + content = @Content(schema = @Schema(implementation = APIResponse.class))) + }) @GetMapping("/me") - public APIResponse getUser() { + public APIResponse getUser() { return success(userService.getUser()); } + @Operation(summary = "내 정보 수정", description = "현재 로그인한 사용자의 정보를 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content(schema = @Schema(implementation = APIResponse.class))), + @ApiResponse(responseCode = "401", description = "인증 실패") + }) @PatchMapping("/me") public APIResponse updateUser(@Valid @RequestBody UpdateUserReqDto req) { userService.updateUser(req); return success(); } + @Operation(summary = "위치 정보 수정", description = "사용자의 현재 위치(위도, 경도)를 업데이트합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "위치 업데이트 성공"), + @ApiResponse(responseCode = "401", description = "인증 실패") + }) @PatchMapping("/me/location") public APIResponse updateLocation(@RequestParam(defaultValue = "37.3623504988728") String latitude, @RequestParam(defaultValue = "127.117057453619") String longitude) { @@ -36,12 +68,36 @@ public APIResponse updateLocation(@RequestParam(defaultValue = "37.3623504988 return success(); } + @Operation(summary = "회원 탈퇴", description = "현재 로그인한 사용자를 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "탈퇴 성공"), + @ApiResponse(responseCode = "401", description = "인증 실패") + }) @DeleteMapping("/me") public APIResponse deleteUser() { userService.deleteUser(); return success(); } + @Operation(summary = "활성 파티 목록 조회", description = "사용자가 참여 중인 활성 파티 목록을 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = PartyResponse.class)))), + @ApiResponse(responseCode = "401", description = "인증 실패") + }) + @GetMapping("/parties/active") + public ResponseEntity> getActiveParties( + @AuthenticationPrincipal Long userId) { + List response = userService.getActiveParties(userId); + return ResponseEntity.ok(response); + } + + @Operation(summary = "닉네임 중복 확인", description = "닉네임 사용 가능 여부를 확인합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "사용 가능한 닉네임"), + @ApiResponse(responseCode = "400", description = "이미 사용 중인 닉네임", + content = @Content(schema = @Schema(implementation = APIResponse.class))) + }) @GetMapping("/nickname/check") public APIResponse validateNickname(@RequestParam String nickname) { userService.validateNickname(nickname); 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 new file mode 100644 index 0000000..6fd56df --- /dev/null +++ b/src/main/java/ita/tinybite/domain/user/dto/res/PartyResponse.java @@ -0,0 +1,45 @@ +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 11ccd54..95ff669 100644 --- a/src/main/java/ita/tinybite/domain/user/service/UserService.java +++ b/src/main/java/ita/tinybite/domain/user/service/UserService.java @@ -1,8 +1,14 @@ package ita.tinybite.domain.user.service; import ita.tinybite.domain.auth.service.SecurityProvider; +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; @@ -11,19 +17,25 @@ import ita.tinybite.global.location.LocationService; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + @Service public class UserService { private final SecurityProvider securityProvider; private final UserRepository userRepository; private final LocationService locationService; + private final PartyParticipantRepository participantRepository; public UserService(SecurityProvider securityProvider, UserRepository userRepository, - LocationService locationService) { + LocationService locationService, + PartyParticipantRepository participantRepository) { this.securityProvider = securityProvider; this.userRepository = userRepository; this.locationService = locationService; + this.participantRepository = participantRepository; } public UserResDto getUser() { @@ -50,4 +62,23 @@ public void validateNickname(String nickname) { if(userRepository.existsByNickname(nickname)) throw BusinessException.of(AuthErrorCode.DUPLICATED_NICKNAME); } + + public List getActiveParties(Long userId) { + List participants = participantRepository + .findActivePartiesByUserId( + userId, + PartyStatus.RECRUITING, + ParticipantStatus.APPROVED + ); + + return participants.stream() + .map(pp -> { + Party party = pp.getParty(); + int currentParticipants = participantRepository + .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); + boolean isHost = party.getHost().getUserId().equals(userId); + return PartyResponse.from(party, currentParticipants, isHost,pp.getStatus()); + }) + .collect(Collectors.toList()); + } } diff --git a/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java index b6bfdea..7a05fba 100644 --- a/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java +++ b/src/test/java/ita/tinybite/domain/user/service/UserServiceTest.java @@ -1,6 +1,8 @@ package ita.tinybite.domain.user.service; import ita.tinybite.domain.auth.service.AuthService; +import ita.tinybite.domain.party.entity.Party; +import ita.tinybite.domain.party.repository.PartyParticipantRepository; import ita.tinybite.domain.user.constant.LoginType; import ita.tinybite.domain.user.constant.UserStatus; import ita.tinybite.domain.user.dto.req.UpdateUserReqDto; @@ -23,6 +25,8 @@ class UserServiceTest { @Autowired private UserRepository userRepository; + private PartyParticipantRepository participantRepository; + @Autowired private AuthService authService; @@ -37,7 +41,7 @@ class UserServiceTest { void setUp() { securityProvider = new FakeSecurityProvider(userRepository); locationService = new FakeLocationService(); - userService = new UserService(securityProvider, userRepository, locationService); + userService = new UserService(securityProvider, userRepository, locationService,participantRepository); User user = User.builder() .email("yyytir777@gmail.com")