From 3ddbef0920017981ab56f6c08940f0f472fa159e Mon Sep 17 00:00:00 2001 From: ywonchae1 Date: Wed, 6 Nov 2024 16:37:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20Redis=20=ED=8F=AC=ED=8A=B8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD=20(#93)=20(KAN-152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ioteatime/meonghanyangserver/config/RedisConfig.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ioteatime/meonghanyangserver/config/RedisConfig.java b/src/main/java/org/ioteatime/meonghanyangserver/config/RedisConfig.java index f708a16a..4d5fa606 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/config/RedisConfig.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/config/RedisConfig.java @@ -19,11 +19,14 @@ public class RedisConfig { @Value("${spring.data.redis.password}") private String password; + @Value("${spring.data.redis.port}") + private String port; + @Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(); redisConfiguration.setHostName(host); - redisConfiguration.setPort(6379); + redisConfiguration.setPort(Integer.parseInt(port)); redisConfiguration.setPassword(password); return new LettuceConnectionFactory(redisConfiguration); } From ee0170dd07afab5768d1787c089b209a91b6c0ee Mon Sep 17 00:00:00 2001 From: ywonchae1 Date: Wed, 6 Nov 2024 16:38:34 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20CCTV=20=ED=87=B4=EC=B6=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#9?= =?UTF-8?q?3)=20(KAN-152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cctv/controller/CctvApi.java | 4 +- .../cctv/controller/CctvController.java | 6 +- .../cctv/repository/CctvRepository.java | 5 +- .../cctv/repository/CctvRepositoryImpl.java | 21 +---- .../cctv/service/CctvService.java | 20 ++--- .../repository/GroupMemberRepository.java | 6 +- .../repository/GroupMemberRepositoryImpl.java | 33 +++---- .../service/GroupMemberService.java | 2 +- .../cctv/controller/CctvControllerTest.java | 87 +++++++++++-------- 9 files changed, 87 insertions(+), 97 deletions(-) diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvApi.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvApi.java index 04b157fe..eab4a5b0 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvApi.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvApi.java @@ -8,6 +8,6 @@ @Tag(name = "CCTV Api", description = "CCTV 관련 API 목록입니다.") public interface CctvApi { - @Operation(summary = "CCTV 삭제(퇴출)", description = "담당자: 양원채") - Api delete(@LoginMember Long userId, @PathVariable Long cctvId); + @Operation(summary = "MASTER 회원이 CCTV 퇴출", description = "담당자: 양원채") + Api out(@LoginMember Long userId, @PathVariable Long cctvId); } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvController.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvController.java index c0cfb4ad..f042f176 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvController.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvController.java @@ -16,9 +16,9 @@ public class CctvController implements CctvApi { private final CctvService cctvService; - @DeleteMapping("/{cctvId}") - public Api delete(@LoginMember Long userId, @PathVariable("cctvId") Long cctvId) { - cctvService.deleteById(userId, cctvId); + @DeleteMapping("/{cctvId}/out") + public Api out(@LoginMember Long userId, @PathVariable("cctvId") Long cctvId) { + cctvService.outById(userId, cctvId); return Api.success(CctvSuccessType.DELETE_CCTV); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepository.java index fbca3974..6f71ada4 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepository.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepository.java @@ -2,14 +2,13 @@ import java.util.Optional; import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; -import org.ioteatime.meonghanyangserver.cctv.dto.db.CctvWithDeviceId; public interface CctvRepository { boolean existsByKvsChannelName(String kvsChannelName); void deleteById(Long cctvId); - Optional findByIdWithDeviceId(Long cctvId); - CctvEntity save(CctvEntity cctv); + + Optional findById(Long cctvId); } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepositoryImpl.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepositoryImpl.java index f86bb5d5..9e1319ca 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepositoryImpl.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/repository/CctvRepositoryImpl.java @@ -4,7 +4,6 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; -import org.ioteatime.meonghanyangserver.cctv.dto.db.CctvWithDeviceId; import org.springframework.stereotype.Repository; @Repository @@ -23,24 +22,12 @@ public void deleteById(Long cctvId) { } @Override - public Optional findByIdWithDeviceId(Long cctvId) { - // return Optional.ofNullable( - // jpaQueryFactory - // .select( - // Projections.constructor( - // CctvWithDeviceId.class, - // cctvEntity.id.as("cctvId"), - // cctvEntity.kvsChannelName.as("kvsChannelName"), - // cctvEntity.device.id.as("deviceId"))) - // .from(cctvEntity) - // .join(cctvEntity.device, deviceEntity) - // .where(cctvEntity.id.eq(cctvId)) - // .fetchOne()); - return null; + public CctvEntity save(CctvEntity cctv) { + return jpaCctvRepository.save(cctv); } @Override - public CctvEntity save(CctvEntity cctv) { - return jpaCctvRepository.save(cctv); + public Optional findById(Long cctvId) { + return jpaCctvRepository.findById(cctvId); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/service/CctvService.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/service/CctvService.java index 6e4018c5..36a8236f 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/cctv/service/CctvService.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/service/CctvService.java @@ -2,7 +2,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.ioteatime.meonghanyangserver.cctv.dto.db.CctvWithDeviceId; +import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; import org.ioteatime.meonghanyangserver.cctv.repository.CctvRepository; import org.ioteatime.meonghanyangserver.clients.kvs.KvsClient; import org.ioteatime.meonghanyangserver.common.exception.BadRequestException; @@ -16,25 +16,21 @@ public class CctvService { private final KvsClient kvsClient; private final CctvRepository cctvRepository; - private final GroupMemberRepository deviceRepository; + private final GroupMemberRepository groupMemberRepository; @Transactional - public void deleteById(Long userId, Long cctvId) { - // Device 테이블에서 userId를 조회하여 role 을 확인 - if (deviceRepository.isParcitipantUserId(userId)) { - // PARTICIPANT 이면 CCTV 삭제 실패 + public void outById(Long memberId, Long cctvId) { + // Master 권한을 가진 회원만 cctv 퇴출 가능 + if (!groupMemberRepository.isMasterMember(memberId)) { throw new BadRequestException(CctvErrorType.ONLY_MASTER_CAN_DELETE); } - // MASTER 이거나, CCTV(자기자신)이면 CCTV 퇴출(나가기) 가능 - CctvWithDeviceId cctv = + CctvEntity cctv = cctvRepository - .findByIdWithDeviceId(cctvId) + .findById(cctvId) .orElseThrow(() -> new NotFoundException(CctvErrorType.NOT_FOUND)); // 1. KVS 시그널링 채널 삭제 - kvsClient.deleteSignalingChannel(cctv.kvsChannelName()); + kvsClient.deleteSignalingChannel(cctv.getKvsChannelName()); // 2. CCTV 테이블에서 삭제 cctvRepository.deleteById(cctvId); - // 3. Device 테이블에서 삭제 - deviceRepository.deleteById(cctv.deviceId()); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepository.java index df14d258..2d671fcd 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepository.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepository.java @@ -5,7 +5,7 @@ import org.ioteatime.meonghanyangserver.groupmember.doamin.GroupMemberEntity; public interface GroupMemberRepository { - GroupMemberEntity createGroupMember(GroupMemberEntity groupMemberEntity); + GroupMemberEntity save(GroupMemberEntity groupMemberEntity); boolean existsGroupMember(Long memberId); @@ -13,9 +13,7 @@ public interface GroupMemberRepository { GroupEntity findGroupMember(Long memberId); - boolean isParcitipantUserId(Long userId); - void deleteById(Long id); - GroupMemberEntity save(GroupMemberEntity device); + boolean isMasterMember(Long memberId); } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepositoryImpl.java b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepositoryImpl.java index d0333bd9..abdf3b3d 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepositoryImpl.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/repository/GroupMemberRepositoryImpl.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; import org.ioteatime.meonghanyangserver.groupmember.doamin.GroupMemberEntity; +import org.ioteatime.meonghanyangserver.groupmember.doamin.enums.GroupMemberRole; import org.springframework.stereotype.Repository; @Repository @@ -17,7 +18,7 @@ public class GroupMemberRepositoryImpl implements GroupMemberRepository { private final JPAQueryFactory jpaQueryFactory; @Override - public GroupMemberEntity createGroupMember(GroupMemberEntity groupMemberEntity) { + public GroupMemberEntity save(GroupMemberEntity groupMemberEntity) { return jpaGroupMemberRepository.save(groupMemberEntity); } @@ -40,29 +41,23 @@ public GroupEntity findGroupMember(Long memberId) { .fetchOne(); } - @Override - public boolean isParcitipantUserId(Long userId) { - // return !jpaQueryFactory - // .select(deviceEntity.role) - // .from(deviceEntity) - // .where( - // deviceEntity - // .member - // .id - // .eq(userId) - // .and(deviceEntity.role.eq(DeviceRole.ROLE_PARTICIPANT))) - // .fetch() - // .isEmpty(); - return true; - } - @Override public void deleteById(Long id) { jpaGroupMemberRepository.deleteById(id); } @Override - public GroupMemberEntity save(GroupMemberEntity device) { - return jpaGroupMemberRepository.save(device); + public boolean isMasterMember(Long memberId) { + return !jpaQueryFactory + .select(groupMemberEntity.role) + .from(groupMemberEntity) + .where( + groupMemberEntity + .member + .id + .eq(memberId) + .and(groupMemberEntity.role.eq(GroupMemberRole.ROLE_MASTER))) + .fetch() + .isEmpty(); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/service/GroupMemberService.java b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/service/GroupMemberService.java index 56170d09..ad36da43 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/groupmember/service/GroupMemberService.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/groupmember/service/GroupMemberService.java @@ -29,7 +29,7 @@ public void createGroupMember( String thingId) { GroupMemberEntity groupMember = GroupMemberEntityMapper.from(groupEntity, memberEntity, groupMemberRole, thingId); - groupMemberRepository.createGroupMember(groupMember); + groupMemberRepository.save(groupMember); } public boolean existsGroupMember(Long memberId) { diff --git a/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java b/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java index 1535ac5c..893cdf26 100644 --- a/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java +++ b/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java @@ -1,11 +1,20 @@ package org.ioteatime.meonghanyangserver.cctv.controller; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.assertj.core.api.Assertions; +import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; import org.ioteatime.meonghanyangserver.cctv.repository.CctvRepository; import org.ioteatime.meonghanyangserver.common.utils.JwtUtils; import org.ioteatime.meonghanyangserver.config.ControllerTestConfig; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; import org.ioteatime.meonghanyangserver.group.repository.GroupRepository; +import org.ioteatime.meonghanyangserver.group.service.GroupService; +import org.ioteatime.meonghanyangserver.groupmember.doamin.GroupMemberEntity; +import org.ioteatime.meonghanyangserver.groupmember.doamin.enums.GroupMemberRole; import org.ioteatime.meonghanyangserver.groupmember.repository.GroupMemberRepository; import org.ioteatime.meonghanyangserver.member.domain.MemberEntity; import org.ioteatime.meonghanyangserver.member.repository.MemberRepository; @@ -13,6 +22,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; @SpringBootTest class CctvControllerTest extends ControllerTestConfig { @@ -20,7 +31,9 @@ class CctvControllerTest extends ControllerTestConfig { @Autowired private MemberRepository memberRepository; @Autowired private CctvRepository cctvRepository; @Autowired private GroupRepository groupRepository; - @Autowired private GroupMemberRepository deviceRepository; + @Autowired private GroupMemberRepository groupMemberRepository; + + @Autowired private GroupService groupService; private String accessToken; private MemberEntity member; @@ -39,39 +52,41 @@ public void beforeEach() { @Test void CCTV를_삭제합니다() throws Exception { - // GroupEntity group = GroupEntity.builder().groupName("testgroup").build(); - // groupRepository.save(group); - // - // DeviceEntity device2 = - // DeviceEntity.builder() - // .member(member) - // .group(group) - // .deviceUuid("test2") - // .role(DeviceRole.ROLE_CCTV) - // .build(); - // device2 = deviceRepository.save(device2); - // - // String kvsChannelName = "delete-test-channel"; // KVS에 채널이 존재해야 함 - // CctvEntity cctv = - // CctvEntity.builder() - // .cctvNickname("delcctv") - // .device(device2) - // .kvsChannelName(kvsChannelName) - // .build(); - // cctvRepository.save(cctv); - // - // mockMvc.perform( - // delete("/api/cctv/{cctvId}", cctv.getId()) - // .header(HttpHeaders.AUTHORIZATION, accessToken) - // .contentType(MediaType.APPLICATION_JSON)) - // .andExpect(status().isOk()) - // .andExpect(jsonPath("$.result.message").value("OK")) - // .andExpect(jsonPath("$.result.description").value("CCTV 삭제(퇴출)에 - // 성공하였습니다.")) - // .andDo(print()); - // - // Assertions.assertThat(cctvRepository.existsByKvsChannelName(kvsChannelName)) - // .isEqualTo(false); - // Assertions.assertThat(deviceRepository.findByDeviceId(device2.getId())).isEmpty(); + GroupEntity group = GroupEntity.builder().groupName("testgroup").build(); + groupRepository.save(group); + + GroupMemberEntity groupMember = + GroupMemberEntity.builder() + .group(group) + .member(member) + .thingId("master-thing-id") + .role(GroupMemberRole.ROLE_MASTER) + .build(); + groupMemberRepository.save(groupMember); + + CctvEntity cctv = + CctvEntity.builder() + .group(group) + .cctvNickname("test") + .kvsChannelName("test-delete-channel") // KVS에 채널이 존재해야 함 + .thingId("test-thing-id") + .build(); + + cctvRepository.save(cctv); + + String kvsChannelName = cctv.getKvsChannelName(); // KVS에 채널이 존재해야 함 + + mockMvc.perform( + delete("/api/cctv/{cctvId}/out", cctv.getId()) + .header(HttpHeaders.AUTHORIZATION, accessToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.message").value("OK")) + .andExpect(jsonPath("$.result.description").value("CCTV 삭제(퇴출)에 성공하였습니다.")) + .andDo(print()); + + Assertions.assertThat(cctvRepository.existsByKvsChannelName(kvsChannelName)) + .isEqualTo(false); + Assertions.assertThat(cctvRepository.findById(cctv.getId())).isEmpty(); } }