diff --git a/src/main/java/net/teumteum/alert/app/AlertHandler.java b/src/main/java/net/teumteum/alert/app/AlertHandler.java index 56b6400c..b89d940d 100644 --- a/src/main/java/net/teumteum/alert/app/AlertHandler.java +++ b/src/main/java/net/teumteum/alert/app/AlertHandler.java @@ -64,7 +64,7 @@ private String toCommaString(List ids) { for (int i = 0; i < ids.size() - 1; i++) { stringBuilder.append(ids.get(i)).append(","); } - stringBuilder.append(ids.getLast()); + stringBuilder.append(ids.get(ids.size() - 1)); return stringBuilder.toString(); } diff --git a/src/main/java/net/teumteum/core/security/service/RedisService.java b/src/main/java/net/teumteum/core/security/service/RedisService.java index b1517c3e..f069fee8 100644 --- a/src/main/java/net/teumteum/core/security/service/RedisService.java +++ b/src/main/java/net/teumteum/core/security/service/RedisService.java @@ -55,7 +55,7 @@ public void setUserLocation(UserLocation userLocation, Long duration) { } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } - setDataWithExpiration(key,value,duration); + setDataWithExpiration(key, value, duration); } public Set getAllUserLocations() { @@ -69,4 +69,8 @@ public Set getAllUserLocations() { } }).collect(Collectors.toSet()); } + + public void deleteUserLocation(Long id) { + deleteData(HASH_KEY + id); + } } diff --git a/src/main/java/net/teumteum/meeting/controller/MeetingController.java b/src/main/java/net/teumteum/meeting/controller/MeetingController.java index 267150b1..c3eab0b8 100644 --- a/src/main/java/net/teumteum/meeting/controller/MeetingController.java +++ b/src/main/java/net/teumteum/meeting/controller/MeetingController.java @@ -9,6 +9,7 @@ import net.teumteum.meeting.domain.Topic; import net.teumteum.meeting.domain.request.CreateMeetingRequest; import net.teumteum.meeting.domain.request.UpdateMeetingRequest; +import net.teumteum.meeting.domain.response.MeetingParticipantsResponse; import net.teumteum.meeting.domain.response.MeetingResponse; import net.teumteum.meeting.domain.response.MeetingsResponse; import net.teumteum.meeting.model.PageDto; @@ -97,6 +98,12 @@ public void deleteParticipant(@PathVariable("meetingId") Long meetingId) { meetingService.cancelParticipant(meetingId, userId); } + @GetMapping("/{meetingId}/participants") + @ResponseStatus(HttpStatus.OK) + public List getParticipants(@PathVariable("meetingId") Long meetingId) { + return meetingService.getParticipants(meetingId); + } + @PostMapping("/{meetingId}/reports") @ResponseStatus(HttpStatus.CREATED) public void reportMeeting(@PathVariable("meetingId") Long meetingId) { diff --git a/src/main/java/net/teumteum/meeting/domain/response/MeetingParticipantsResponse.java b/src/main/java/net/teumteum/meeting/domain/response/MeetingParticipantsResponse.java new file mode 100644 index 00000000..10d9df9d --- /dev/null +++ b/src/main/java/net/teumteum/meeting/domain/response/MeetingParticipantsResponse.java @@ -0,0 +1,23 @@ +package net.teumteum.meeting.domain.response; + +import net.teumteum.user.domain.User; + +public record MeetingParticipantsResponse( + Long id, + Long characterId, + String name, + String job +) { + + public static MeetingParticipantsResponse of( + User user + ) { + return new MeetingParticipantsResponse( + user.getId(), + user.getCharacterId(), + user.getName(), + user.getJob().getDetailJobClass() + ); + } + +} diff --git a/src/main/java/net/teumteum/meeting/service/MeetingService.java b/src/main/java/net/teumteum/meeting/service/MeetingService.java index dca907d1..00f3975e 100644 --- a/src/main/java/net/teumteum/meeting/service/MeetingService.java +++ b/src/main/java/net/teumteum/meeting/service/MeetingService.java @@ -1,6 +1,7 @@ package net.teumteum.meeting.service; import java.util.List; +import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; import net.teumteum.meeting.domain.ImageUpload; @@ -11,9 +12,11 @@ import net.teumteum.meeting.domain.Topic; import net.teumteum.meeting.domain.request.CreateMeetingRequest; import net.teumteum.meeting.domain.request.UpdateMeetingRequest; +import net.teumteum.meeting.domain.response.MeetingParticipantsResponse; import net.teumteum.meeting.domain.response.MeetingResponse; import net.teumteum.meeting.domain.response.MeetingsResponse; import net.teumteum.meeting.model.PageDto; +import net.teumteum.user.domain.UserConnector; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; @@ -27,6 +30,7 @@ public class MeetingService { private final MeetingRepository meetingRepository; private final ImageUpload imageUpload; + private final UserConnector userConnector; @Transactional public MeetingResponse createMeeting(List images, CreateMeetingRequest meetingRequest, Long userId) { @@ -148,6 +152,17 @@ public void cancelParticipant(Long meetingId, Long userId) { existMeeting.cancelParticipant(userId); } + @Transactional(readOnly = true) + public List getParticipants(Long meetingId) { + var existMeeting = getMeeting(meetingId); + + return existMeeting.getParticipantUserIds().stream() + .map(userConnector::findUserById) + .flatMap(Optional::stream) + .map(MeetingParticipantsResponse::of) + .toList(); + } + @Transactional public void addBookmark(Long meetingId, Long userId) { var existMeeting = getMeeting(meetingId); diff --git a/src/main/java/net/teumteum/teum_teum/domain/response/UserAroundLocationsResponse.java b/src/main/java/net/teumteum/teum_teum/domain/response/UserAroundLocationsResponse.java index 0b093f63..243d5974 100644 --- a/src/main/java/net/teumteum/teum_teum/domain/response/UserAroundLocationsResponse.java +++ b/src/main/java/net/teumteum/teum_teum/domain/response/UserAroundLocationsResponse.java @@ -1,13 +1,14 @@ package net.teumteum.teum_teum.domain.response; import java.util.List; +import java.util.Set; import net.teumteum.teum_teum.domain.UserLocation; public record UserAroundLocationsResponse( List aroundUserLocations ) { - public static UserAroundLocationsResponse of(List userData) { + public static UserAroundLocationsResponse of(Set userData) { return new UserAroundLocationsResponse( userData.stream() .map(UserAroundLocationResponse::of) diff --git a/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java b/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java index 16ef457e..dd69880b 100644 --- a/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java +++ b/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java @@ -6,42 +6,43 @@ import static java.lang.Math.toRadians; import static java.util.Comparator.comparingDouble; -import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import net.teumteum.core.security.service.RedisService; import net.teumteum.teum_teum.domain.UserLocation; import net.teumteum.teum_teum.domain.request.UserLocationRequest; import net.teumteum.teum_teum.domain.response.UserAroundLocationsResponse; import org.springframework.stereotype.Service; -@Slf4j + @Service @RequiredArgsConstructor public class TeumTeumService { private static final int SEARCH_LIMIT = 6; + private static final long USER_LOCATION_DATA_DURATION = 10L; + private static final int AROUND_USER_LOCATION_DISTANCE = 100; private final RedisService redisService; public UserAroundLocationsResponse saveAndGetUserAroundLocations(UserLocationRequest request) { - redisService.setUserLocation(request.toUserLocation(), 60L); + redisService.setUserLocation(request.toUserLocation(), USER_LOCATION_DATA_DURATION); return getUserAroundLocations(request); } private UserAroundLocationsResponse getUserAroundLocations(UserLocationRequest request) { Set allUserLocations = redisService.getAllUserLocations(); - List aroundUserLocations = allUserLocations.stream() + Set aroundUserLocations = allUserLocations.stream() .filter(userLocation -> !userLocation.id().equals(request.id())) .filter(userLocation -> calculateDistance(request.latitude(), request.longitude(), - userLocation.latitude(), userLocation.longitude()) <= 100) + userLocation.latitude(), userLocation.longitude()) <= AROUND_USER_LOCATION_DISTANCE) .sorted(comparingDouble(userLocation -> calculateDistance(request.latitude(), request.longitude(), userLocation.latitude(), userLocation.longitude())) ).limit(SEARCH_LIMIT) - .toList(); + .collect(Collectors.toSet()); return UserAroundLocationsResponse.of(aroundUserLocations); } diff --git a/src/main/java/net/teumteum/user/service/UserService.java b/src/main/java/net/teumteum/user/service/UserService.java index 38f9ca02..5a4448bb 100644 --- a/src/main/java/net/teumteum/user/service/UserService.java +++ b/src/main/java/net/teumteum/user/service/UserService.java @@ -81,9 +81,10 @@ public void addFriends(Long myId, Long friendId) { public void withdraw(UserWithdrawRequest request, Long userId) { var existUser = getUser(userId); - userRepository.delete(existUser); redisService.deleteData(String.valueOf(userId)); + userRepository.delete(existUser); + withdrawReasonRepository.saveAll(request.toEntity()); } @@ -95,9 +96,9 @@ public UserRegisterResponse register(UserRegisterRequest request) { return UserRegisterResponse.of(savedUser.getId(), jwtService.createServiceToken(savedUser)); } - @Transactional public void logout(Long userId) { redisService.deleteData(String.valueOf(userId)); + SecurityService.clearSecurityContext(); } diff --git a/src/test/java/net/teumteum/integration/Api.java b/src/test/java/net/teumteum/integration/Api.java index 2b8d3b32..a65e3509 100644 --- a/src/test/java/net/teumteum/integration/Api.java +++ b/src/test/java/net/teumteum/integration/Api.java @@ -213,4 +213,12 @@ ResponseSpec registerUserReview(String accessToken, Long meetingId, ReviewRegist .bodyValue(request) .exchange(); } + + ResponseSpec getMeetingParticipants(String accessToken, Long meetingId) { + return webTestClient + .get() + .uri("/meetings/" + meetingId + "/participants") + .header(HttpHeaders.AUTHORIZATION, accessToken) + .exchange(); + } } diff --git a/src/test/java/net/teumteum/integration/MeetingIntegrationTest.java b/src/test/java/net/teumteum/integration/MeetingIntegrationTest.java index aea1c8ee..bec162c2 100644 --- a/src/test/java/net/teumteum/integration/MeetingIntegrationTest.java +++ b/src/test/java/net/teumteum/integration/MeetingIntegrationTest.java @@ -23,9 +23,9 @@ class MeetingIntegrationTest extends IntegrationTest { public static final int DEFAULT_QUERY_SIZE = 5; + public static final Pageable FIRST_PAGE_NATION = PageRequest.of(0, DEFAULT_QUERY_SIZE, Sort.Direction.DESC, "id"); private static final String VALID_TOKEN = "VALID_TOKEN"; private static final String INVALID_TOKEN = "IN_VALID_TOKEN"; - public static final Pageable FIRST_PAGE_NATION = PageRequest.of(0, DEFAULT_QUERY_SIZE, Sort.Direction.DESC, "id"); @Nested @DisplayName("단일 미팅 조회 API는") @@ -288,7 +288,6 @@ void Return_400_bad_request_if_already_joined_meeting_id_received() { securityContextSetting.set(me.getId()); - loginContext.setUserId(me.getId()); api.joinMeeting(VALID_TOKEN, meeting.getId()); // when var result = api.joinMeeting(VALID_TOKEN, meeting.getId()); @@ -472,4 +471,20 @@ void Return_400_bad_request_if_not_bookmarked_meeting_id_received() { .expectBody(ErrorResponse.class); } } + + @Nested + @DisplayName("미팅 참가자 조회 API는") + class Get_meeting_participants_api { + + @Test + @DisplayName("참여한 meeting id 가 주어지면, 참여한 참가자들의 정보가 주어진다.") + void Get_participants_if_exist_meeting_id_received() { + // given + var meeting = repository.saveAndGetOpenMeeting(); + // when + var result = api.getMeetingParticipants(VALID_TOKEN, meeting.getId()); + // then + result.expectStatus().isOk(); + } + } } diff --git a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java index 5870936e..9963a8c8 100644 --- a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java +++ b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java @@ -11,6 +11,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -220,4 +221,25 @@ void Get_user_reviews_with_200_ok() throws Exception { .andExpect(jsonPath("$[0].review").value("별로에요")); } } + + @Nested + @DisplayName("회원 로그아웃 API는") + class Logout_user_api_unit { + + @Test + @DisplayName("") + void Logout_user_with_200_ok() throws Exception { + // given + var userId = 1L; + + doNothing().when(userService).logout(anyLong()); + + // when && then + mockMvc.perform(post("/users/logouts") + .with(csrf()) + .header(AUTHORIZATION, VALID_ACCESS_TOKEN)) + .andDo(print()) + .andExpect(status().isOk()); + } + } } diff --git a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java index 7a7525b0..cda176b1 100644 --- a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java +++ b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java @@ -62,6 +62,7 @@ public class UserServiceTest { @Mock MeetingConnector meetingConnector; + private User user; @BeforeEach @@ -111,7 +112,26 @@ void If_user_already_exist_register_user_card_fail() { } @Nested - @DisplayName("유저 탈퇴 API는") + @DisplayName("회원 로그아웃 API는") + class Logout_user_api_unit { + + @Test + @DisplayName("정상적인 요청시, 회원 로그아웃을 진행하고 200 OK을 반환한다.") + void If_valid_user_logout_request_return_200_OK() { + // given + Long userId = 1L; + doNothing().when(redisService).deleteData(anyString()); + + // when + userService.logout(userId); + + // then + verify(redisService, times(1)).deleteData(anyString()); + } + } + + @Nested + @DisplayName("회원 탈퇴 API는") class Withdraw_user_api_unit { @Test