diff --git a/.gitignore b/.gitignore index ca66177..f1e79dc 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ out/ .vscode/ ### env ### -.env \ No newline at end of file +.env +application-test.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index de354e3..19884ab 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,11 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testAnnotationProcessor('org.projectlombok:lombok') + testImplementation('org.projectlombok:lombok') // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' diff --git a/src/main/java/gdsc/comunity/controller/ChannelController.java b/src/main/java/gdsc/comunity/controller/ChannelController.java new file mode 100644 index 0000000..7d6d7f3 --- /dev/null +++ b/src/main/java/gdsc/comunity/controller/ChannelController.java @@ -0,0 +1,73 @@ +package gdsc.comunity.controller; + +import gdsc.comunity.annotation.UserId; +import gdsc.comunity.dto.channel.*; +import gdsc.comunity.service.channel.ChannelServiceImpl; +import gdsc.comunity.util.ListWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/channel") +@RequiredArgsConstructor +public class ChannelController { + private final ChannelServiceImpl channelServiceImpl; + + @PostMapping + ResponseEntity createChannel(@RequestBody ChannelCreateDto channelCreateDto, @UserId Long id) { + String channelName = channelCreateDto.getChannelName(); + String nickname = channelCreateDto.getNickname(); + channelServiceImpl.createChannel(id, channelName, nickname); + return new ResponseEntity<>("Channel created.", HttpStatus.CREATED); + } + + @GetMapping("/{channelId}") + ResponseEntity searchChannel(@PathVariable Long channelId, @UserId Long id) { + ChannelInfoDto channelInfoDto = channelServiceImpl.searchChannel(channelId); + return new ResponseEntity<>(channelInfoDto, HttpStatus.OK); + } + + @GetMapping("/join/{channelId}") + ResponseEntity>> searchJoinRequest(@PathVariable Long channelId, @UserId Long id) { + List userList = channelServiceImpl.searchJoinRequest(id, channelId); + return new ResponseEntity<>(new ListWrapper<>(userList), HttpStatus.OK); + } + + @PostMapping("/join/{channelId}") + ResponseEntity sendJoinRequest(@RequestBody String nickname, @PathVariable Long channelId, @UserId Long id) { + channelServiceImpl.sendJoinRequest(nickname, id, channelId); + return new ResponseEntity<>("Channel joined.", HttpStatus.OK); + } + + @PutMapping("/join/{channelId}") + ResponseEntity leaveChannel(@PathVariable Long channelId, @UserId Long id) { + channelServiceImpl.leaveChannel(id, channelId); + return new ResponseEntity<>("Channel left.", HttpStatus.OK); + } + + @DeleteMapping("/join/{channelId}") + ResponseEntity deleteChannel(@PathVariable Long channelId, @UserId Long id) { + channelServiceImpl.deleteChannel(id, channelId); + return new ResponseEntity<>("Channel deleted.", HttpStatus.OK); + } + + @PutMapping("/approve") + ResponseEntity approveJoinChannel(@RequestBody ApproveJoinChannelDto approveJoinChannelDto, @UserId Long userId) { + Long targetUserId = approveJoinChannelDto.getUserId(); + Long channelId = approveJoinChannelDto.getChannelId(); + channelServiceImpl.approveJoinChannel(userId, targetUserId, channelId); + return new ResponseEntity<>("Channel joined.", HttpStatus.OK); + } + + @PutMapping("/nickname") + ResponseEntity changeNickname(@RequestBody ChannelNicknameDto channelNicknameDto, @UserId Long id) { + String nickname = channelNicknameDto.getNickname(); + Long channelId = channelNicknameDto.getChannelId(); + channelServiceImpl.changeNickname(id, channelId, nickname); + return new ResponseEntity<>("Nickname changed.", HttpStatus.OK); + } +} diff --git a/src/main/java/gdsc/comunity/controller/OAuthController.java b/src/main/java/gdsc/comunity/controller/OAuthController.java index a80d47c..e2df645 100644 --- a/src/main/java/gdsc/comunity/controller/OAuthController.java +++ b/src/main/java/gdsc/comunity/controller/OAuthController.java @@ -1,6 +1,5 @@ package gdsc.comunity.controller; -import gdsc.comunity.dto.GoogleUserInfo; import gdsc.comunity.dto.JwtTokensDto; import java.util.Map; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/gdsc/comunity/dto/channel/ApproveJoinChannelDto.java b/src/main/java/gdsc/comunity/dto/channel/ApproveJoinChannelDto.java new file mode 100644 index 0000000..916916c --- /dev/null +++ b/src/main/java/gdsc/comunity/dto/channel/ApproveJoinChannelDto.java @@ -0,0 +1,14 @@ +package gdsc.comunity.dto.channel; + +import lombok.Getter; + +@Getter +public class ApproveJoinChannelDto { + private Long userId; + private Long channelId; + + public ApproveJoinChannelDto(Long userId, Long channelId) { + this.userId = userId; + this.channelId = channelId; + } +} diff --git a/src/main/java/gdsc/comunity/dto/channel/ChannelCreateDto.java b/src/main/java/gdsc/comunity/dto/channel/ChannelCreateDto.java new file mode 100644 index 0000000..ba912ca --- /dev/null +++ b/src/main/java/gdsc/comunity/dto/channel/ChannelCreateDto.java @@ -0,0 +1,14 @@ +package gdsc.comunity.dto.channel; + +import lombok.Getter; + +@Getter +public class ChannelCreateDto { + private String channelName; + private String nickname; + + public ChannelCreateDto(String channelName, String nickname) { + this.channelName = channelName; + this.nickname = nickname; + } +} diff --git a/src/main/java/gdsc/comunity/dto/channel/ChannelInfoDto.java b/src/main/java/gdsc/comunity/dto/channel/ChannelInfoDto.java new file mode 100644 index 0000000..0fd1c3d --- /dev/null +++ b/src/main/java/gdsc/comunity/dto/channel/ChannelInfoDto.java @@ -0,0 +1,45 @@ +package gdsc.comunity.dto.channel; + +import gdsc.comunity.entity.user.User; +import gdsc.comunity.entity.userchannel.UserChannel; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class ChannelInfoDto { + + @Getter + public static class UserDto { + private Long id; + private String email; + private String nickname; + private String profileImageUrl; + + public UserDto(String nickname, User user) { + this.id = user.getId(); + this.email = user.getEmail(); + this.nickname = nickname; + this.profileImageUrl = user.getProfileImageUrl(); + } + } + + private String channelName; + private String openDate; + private String managerNickname; + private List channelUsers; + + public ChannelInfoDto(String channelName, String openDate, String managerNickname, List userChannelList, List userList) { + this.channelName = channelName; + this.openDate = openDate; + this.managerNickname = managerNickname; + this.channelUsers = new ArrayList<>(); + + for (UserChannel userChannel : userChannelList) { + for (User user : userList) { + channelUsers.add(new UserDto(userChannel.getNickname(), user)); + } + } + } +} diff --git a/src/main/java/gdsc/comunity/dto/channel/ChannelJoinRequestDto.java b/src/main/java/gdsc/comunity/dto/channel/ChannelJoinRequestDto.java new file mode 100644 index 0000000..f1c36db --- /dev/null +++ b/src/main/java/gdsc/comunity/dto/channel/ChannelJoinRequestDto.java @@ -0,0 +1,18 @@ +package gdsc.comunity.dto.channel; + +import lombok.Getter; + +@Getter +public class ChannelJoinRequestDto { + Long id; + Long userId; + Long channelId; + String nickname; + + public ChannelJoinRequestDto(Long id, Long userId, Long channelId, String nickname) { + this.id = id; + this.userId = userId; + this.channelId = channelId; + this.nickname = nickname; + } +} diff --git a/src/main/java/gdsc/comunity/dto/channel/ChannelNicknameDto.java b/src/main/java/gdsc/comunity/dto/channel/ChannelNicknameDto.java new file mode 100644 index 0000000..8f37d2c --- /dev/null +++ b/src/main/java/gdsc/comunity/dto/channel/ChannelNicknameDto.java @@ -0,0 +1,14 @@ +package gdsc.comunity.dto.channel; + +import lombok.Getter; + +@Getter +public class ChannelNicknameDto { + private String nickname; + private Long channelId; + + public ChannelNicknameDto(String nickname, Long channelId) { + this.nickname = nickname; + this.channelId = channelId; + } +} diff --git a/src/main/java/gdsc/comunity/dto/chat/Chatting.java b/src/main/java/gdsc/comunity/dto/chat/Chatting.java index 3008adc..1af009e 100644 --- a/src/main/java/gdsc/comunity/dto/chat/Chatting.java +++ b/src/main/java/gdsc/comunity/dto/chat/Chatting.java @@ -2,11 +2,9 @@ import gdsc.comunity.entity.channel.Channel; import gdsc.comunity.entity.chat.ChatLog; -import lombok.AllArgsConstructor; import java.time.LocalDateTime; -@AllArgsConstructor public class Chatting { public final String message; public final String senderNickname; @@ -20,4 +18,10 @@ public ChatLog toEntity(Channel channel, Long senderId) { .sendTime(time) .build(); } + + public Chatting(String message, String senderNickname, LocalDateTime time) { + this.message = message; + this.senderNickname = senderNickname; + this.time = time; + } } diff --git a/src/main/java/gdsc/comunity/entity/channel/Channel.java b/src/main/java/gdsc/comunity/entity/channel/Channel.java index 814d2eb..36be96b 100644 --- a/src/main/java/gdsc/comunity/entity/channel/Channel.java +++ b/src/main/java/gdsc/comunity/entity/channel/Channel.java @@ -3,19 +3,16 @@ import gdsc.comunity.entity.common.BaseTimeEntity; import gdsc.comunity.entity.user.User; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import gdsc.comunity.entity.userchannel.UserChannel; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.HashSet; +import java.util.Set; + @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @@ -40,9 +37,29 @@ public class Channel extends BaseTimeEntity { @JoinColumn(name = "manager_id") private User manager; + private String channelName; + + @OneToMany(mappedBy = "channel", cascade = CascadeType.ALL, orphanRemoval = true) + private Set userChannels; + @Builder - private Channel(User manager) { + private Channel(User manager, String channelName) { this.manager = manager; + this.channelName = channelName; + } + + public void updateManager(User newManager) { + this.manager = newManager; + } + + public void addUserChannel(UserChannel userChannel) { + if (userChannels == null) { + userChannels = new HashSet<>(); + } + userChannels.add(userChannel); } + public void removeUserChannel(UserChannel userChannel) { + userChannels.remove(userChannel); + } } diff --git a/src/main/java/gdsc/comunity/entity/channel/ChannelJoinRequest.java b/src/main/java/gdsc/comunity/entity/channel/ChannelJoinRequest.java new file mode 100644 index 0000000..a0dff24 --- /dev/null +++ b/src/main/java/gdsc/comunity/entity/channel/ChannelJoinRequest.java @@ -0,0 +1,37 @@ +package gdsc.comunity.entity.channel; + +import gdsc.comunity.entity.common.BaseTimeEntity; +import gdsc.comunity.entity.user.User; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class ChannelJoinRequest extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "channel_id") + private Channel channel; + + @Column(nullable = false) + String nickname; + + @Builder + private ChannelJoinRequest(User user, Channel channel, String nickname){ + this.user = user; + this.channel = channel; + this.nickname = nickname; + } + +} diff --git a/src/main/java/gdsc/comunity/entity/user/UserChannel.java b/src/main/java/gdsc/comunity/entity/userchannel/UserChannel.java similarity index 71% rename from src/main/java/gdsc/comunity/entity/user/UserChannel.java rename to src/main/java/gdsc/comunity/entity/userchannel/UserChannel.java index d00b556..d8f18e5 100644 --- a/src/main/java/gdsc/comunity/entity/user/UserChannel.java +++ b/src/main/java/gdsc/comunity/entity/userchannel/UserChannel.java @@ -1,15 +1,9 @@ -package gdsc.comunity.entity.user; +package gdsc.comunity.entity.userchannel; import gdsc.comunity.entity.channel.Channel; import gdsc.comunity.entity.common.BaseTimeEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import gdsc.comunity.entity.user.User; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -40,7 +34,8 @@ private UserChannel(String nickname, User user, Channel channel) { this.user = user; this.channel = channel; } -} - - + public void updateNickname(String nickname) { + this.nickname = nickname; + } +} \ No newline at end of file diff --git a/src/main/java/gdsc/comunity/repository/channel/ChannelJoinRequestRepository.java b/src/main/java/gdsc/comunity/repository/channel/ChannelJoinRequestRepository.java new file mode 100644 index 0000000..dfa2f06 --- /dev/null +++ b/src/main/java/gdsc/comunity/repository/channel/ChannelJoinRequestRepository.java @@ -0,0 +1,15 @@ +package gdsc.comunity.repository.channel; + +import gdsc.comunity.entity.channel.ChannelJoinRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ChannelJoinRequestRepository extends JpaRepository { + Optional findByUserIdAndChannelId(Long targetUserId, Long userId); + + List findAllByChannelId(Long channelId); +} diff --git a/src/main/java/gdsc/comunity/repository/channel/ChannelRepository.java b/src/main/java/gdsc/comunity/repository/channel/ChannelRepository.java index e28433b..ac450e5 100644 --- a/src/main/java/gdsc/comunity/repository/channel/ChannelRepository.java +++ b/src/main/java/gdsc/comunity/repository/channel/ChannelRepository.java @@ -2,6 +2,8 @@ import gdsc.comunity.entity.channel.Channel; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +@Repository public interface ChannelRepository extends JpaRepository { } diff --git a/src/main/java/gdsc/comunity/repository/user/UserChannelJpaRepository.java b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelJpaRepository.java similarity index 57% rename from src/main/java/gdsc/comunity/repository/user/UserChannelJpaRepository.java rename to src/main/java/gdsc/comunity/repository/userchannel/UserChannelJpaRepository.java index 300602d..8a099f8 100644 --- a/src/main/java/gdsc/comunity/repository/user/UserChannelJpaRepository.java +++ b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelJpaRepository.java @@ -1,15 +1,26 @@ -package gdsc.comunity.repository.user; +package gdsc.comunity.repository.userchannel; -import gdsc.comunity.entity.user.UserChannel; +import gdsc.comunity.entity.userchannel.UserChannel; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface UserChannelJpaRepository extends JpaRepository { @Query(value = "SELECT user_id FROM user_channel WHERE nickname = :senderNickname AND channel_id = :channelId", nativeQuery = true) Optional findUserIdByNicknameAndChannel(@Param("senderNickname") String senderNickname, @Param("channelId") Long channelId); + + List findTop2ByChannelIdOrderByCreatedDateAsc(Long channelId); + + Optional findByUserIdAndChannelId(Long userId, Long channelId); + + void deleteAllByChannelId(Long channelId); + + List findAllByChannelId(Long channelId); + + Optional findByUserId(Long userId); } diff --git a/src/main/java/gdsc/comunity/repository/user/UserChannelRepository.java b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepository.java similarity index 82% rename from src/main/java/gdsc/comunity/repository/user/UserChannelRepository.java rename to src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepository.java index 4681a1b..dcfd3ee 100644 --- a/src/main/java/gdsc/comunity/repository/user/UserChannelRepository.java +++ b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepository.java @@ -1,4 +1,4 @@ -package gdsc.comunity.repository.user; +package gdsc.comunity.repository.userchannel; import java.util.Map; diff --git a/src/main/java/gdsc/comunity/repository/user/UserChannelRepositoryImpl.java b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepositoryImpl.java similarity index 96% rename from src/main/java/gdsc/comunity/repository/user/UserChannelRepositoryImpl.java rename to src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepositoryImpl.java index 13145da..005a247 100644 --- a/src/main/java/gdsc/comunity/repository/user/UserChannelRepositoryImpl.java +++ b/src/main/java/gdsc/comunity/repository/userchannel/UserChannelRepositoryImpl.java @@ -1,4 +1,4 @@ -package gdsc.comunity.repository.user; +package gdsc.comunity.repository.userchannel; import lombok.RequiredArgsConstructor; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/src/main/java/gdsc/comunity/service/ChatService.java b/src/main/java/gdsc/comunity/service/ChatService.java index 8e2d701..1fc1c10 100644 --- a/src/main/java/gdsc/comunity/service/ChatService.java +++ b/src/main/java/gdsc/comunity/service/ChatService.java @@ -1,11 +1,11 @@ package gdsc.comunity.service; -import gdsc.comunity.dto.chat.PagingChatting; import gdsc.comunity.dto.chat.Chatting; +import gdsc.comunity.dto.chat.PagingChatting; import gdsc.comunity.entity.chat.ChatLog; import gdsc.comunity.repository.channel.ChannelRepository; import gdsc.comunity.repository.chat.ChatRepository; -import gdsc.comunity.repository.user.UserChannelRepository; +import gdsc.comunity.repository.userchannel.UserChannelRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.scheduling.annotation.Async; diff --git a/src/main/java/gdsc/comunity/service/OAuthGoogleService.java b/src/main/java/gdsc/comunity/service/OAuthGoogleService.java index 650878c..b1fb15b 100644 --- a/src/main/java/gdsc/comunity/service/OAuthGoogleService.java +++ b/src/main/java/gdsc/comunity/service/OAuthGoogleService.java @@ -1,6 +1,5 @@ package gdsc.comunity.service; -import gdsc.comunity.dto.GoogleUserInfo; import gdsc.comunity.dto.JwtTokensDto; import gdsc.comunity.entity.user.Provider; import gdsc.comunity.entity.user.User; diff --git a/src/main/java/gdsc/comunity/service/channel/ChannelService.java b/src/main/java/gdsc/comunity/service/channel/ChannelService.java new file mode 100644 index 0000000..b0345d0 --- /dev/null +++ b/src/main/java/gdsc/comunity/service/channel/ChannelService.java @@ -0,0 +1,34 @@ +package gdsc.comunity.service.channel; + +import gdsc.comunity.dto.channel.ChannelInfoDto; +import gdsc.comunity.dto.channel.ChannelJoinRequestDto; +import gdsc.comunity.entity.channel.Channel; +import gdsc.comunity.entity.user.User; + +import java.util.List; + +public interface ChannelService { + Channel createChannel(Long userId, String channelName, String nickname); + + void leaveChannel(Long userId, Long channelId); + + void deleteChannel(Long userId, Long channelId); + + ChannelInfoDto searchChannel(Long channelId); + + void approveJoinChannel(Long userId, Long targetUserId, Long channelId); + + void sendJoinRequest(String nickname, Long userId, Long channelId); + + List searchJoinRequest(Long userId, Long channelId); + + void changeNickname(Long userId, Long channelId, String nickname); + + void doubleCheckNicknameThrowException(Long channelId, String nickname); + + void checkManagerThrowException(Long userId, Long channelId); + + User findUserByIdThrowException(Long userId); + + Channel findChannelByIdThrowException(Long channelId); +} diff --git a/src/main/java/gdsc/comunity/service/channel/ChannelServiceImpl.java b/src/main/java/gdsc/comunity/service/channel/ChannelServiceImpl.java new file mode 100644 index 0000000..25355a7 --- /dev/null +++ b/src/main/java/gdsc/comunity/service/channel/ChannelServiceImpl.java @@ -0,0 +1,208 @@ +package gdsc.comunity.service.channel; + +import gdsc.comunity.dto.channel.ChannelInfoDto; +import gdsc.comunity.dto.channel.ChannelJoinRequestDto; +import gdsc.comunity.entity.channel.Channel; +import gdsc.comunity.entity.channel.ChannelJoinRequest; +import gdsc.comunity.entity.user.User; +import gdsc.comunity.entity.userchannel.UserChannel; +import gdsc.comunity.repository.channel.ChannelJoinRequestRepository; +import gdsc.comunity.repository.channel.ChannelRepository; +import gdsc.comunity.repository.user.UserRepository; +import gdsc.comunity.repository.userchannel.UserChannelJpaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +public class ChannelServiceImpl implements ChannelService { + private final ChannelRepository channelRepository; + private final UserChannelJpaRepository userChannelJpaRepository; + private final UserRepository userRepository; + private final ChannelJoinRequestRepository channelJoinRequestRepository; + + @Override + @Transactional + public Channel createChannel(Long userId, String channelName, String nickname) { + User user = findUserByIdThrowException(userId); + + Channel newChannel = Channel.builder() + .channelName(channelName) + .manager(user) + .build(); + + UserChannel userChannel = UserChannel.builder() + .channel(newChannel) + .user(user) + .nickname(nickname) + .build(); + + newChannel.addUserChannel(userChannel); + channelRepository.save(newChannel); + + return newChannel; + } + + @Override + @Transactional + public void leaveChannel(Long userId, Long channelId) { + User user = findUserByIdThrowException(userId); + Channel channel = findChannelByIdThrowException(channelId); + + // 대상이 매니저가 아닌 경우, UserChannel 삭제 + if (!(Objects.equals(channel.getManager().getId(), user.getId()))) { + UserChannel userChannel = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channelId).orElseThrow( + () -> new IllegalStateException("사용자 본인이 해당 채널에 속해있지 않습니다.") + ); + channel.removeUserChannel(userChannel); + channelRepository.save(channel); + userChannelJpaRepository.delete(userChannel); + return; + } + + // 대상이 매니저인 경우, 채널 매니저 변경 + List userChannelList = userChannelJpaRepository.findTop2ByChannelIdOrderByCreatedDateAsc(channelId); + // list의 size가 2이상인 경우 진행 아니면 exception + if (userChannelList.size() < 2) { + throw new IllegalStateException("채널에 다른 사용자가 없어 채널을 탈퇴할 수 없습니다. 채널 삭제를 진행해주세요."); + } + + UserChannel deleteUserChannel = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channelId).orElseThrow( + () -> new IllegalArgumentException("해당 사용자가 채널에 속해있지 않습니다.") + ); + userChannelJpaRepository.delete(deleteUserChannel); + + channel.updateManager(userChannelList.get(1).getUser()); + channel.removeUserChannel(deleteUserChannel); + channelRepository.save(channel); + } + + @Override + @Transactional + public void deleteChannel(Long userId, Long channelId) { + User user = findUserByIdThrowException(userId); + Channel channel = findChannelByIdThrowException(channelId); + + // 채널 매니저만 채널 삭제 가능 + if (!(Objects.equals(channel.getManager().getId(), user.getId()))) { + throw new IllegalStateException("채널 매니저만 채널을 삭제할 수 있습니다."); + } + + channelRepository.delete(channel); + } + + @Override + public ChannelInfoDto searchChannel(Long channelId) { + Channel channel = findChannelByIdThrowException(channelId); + List userChannels = userChannelJpaRepository.findAllByChannelId(channelId); + List channelUsers = userChannels.stream().map(UserChannel::getUser).toList(); + + UserChannel manager = userChannelJpaRepository.findByUserIdAndChannelId(channel.getManager().getId(), channelId).orElseThrow( + () -> new IllegalArgumentException("해당 채널의 매니저가 존재하지 않습니다.") + ); + + // 요청한 채널의 정보(채널 이름, 생성일, 매니저 닉네임, 채널 유저 리스트) 반환 + return new ChannelInfoDto( + channel.getChannelName(), + channel.getCreatedDate().toString(), + manager.getNickname(), + userChannels, + channelUsers + ); + } + + @Override + public void sendJoinRequest(String nickname, Long userId, Long channelId) { + doubleCheckNicknameThrowException(channelId, nickname); + + User user = findUserByIdThrowException(userId); + Channel channel = findChannelByIdThrowException(channelId); + + channelJoinRequestRepository.save(ChannelJoinRequest + .builder() + .channel(channel) + .user(user) + .nickname(nickname) + .build()); + } + + @Override + @Transactional + public void approveJoinChannel(Long userId, Long targetUserId, Long channelId) { + User user = findUserByIdThrowException(userId); + Channel channel = findChannelByIdThrowException(channelId); + + checkManagerThrowException(userId, channel.getManager().getId()); + + // 채널 가입 요청을 승인하고 UserChannel에 저장. 이후 ChannelJoinRequest 삭제 + ChannelJoinRequest channelJoinRequest = channelJoinRequestRepository.findByUserIdAndChannelId(targetUserId, channelId).orElseThrow( + () -> new IllegalArgumentException("해당 사용자의 채널 가입 요청이 존재하지 않습니다.") + ); + + userChannelJpaRepository.save(UserChannel.builder() + .channel(channel) + .user(channelJoinRequest.getUser()) + .nickname(channelJoinRequest.getNickname()) + .build()); + channelJoinRequestRepository.delete(channelJoinRequest); + } + + @Override + public List searchJoinRequest(Long userId, Long channelId) { + Channel channel = findChannelByIdThrowException(channelId); + checkManagerThrowException(userId, channel.getManager().getId()); + + return channelJoinRequestRepository.findAllByChannelId(channelId).stream().map( + channelJoinRequest -> new ChannelJoinRequestDto( + channelJoinRequest.getId(), + channelJoinRequest.getUser().getId(), + channelJoinRequest.getChannel().getId(), + channelJoinRequest.getNickname() + ) + ).toList(); + } + + @Override + public void changeNickname(Long userId, Long channelId, String nickname) { + // 닉네임 중복 확인 후 변경. + doubleCheckNicknameThrowException(channelId, nickname); + + UserChannel userChannel = userChannelJpaRepository.findByUserId(userId).orElseThrow( + () -> new IllegalArgumentException("해당 사용자가 채널에 속해있지 않습니다.") + ); + userChannel.updateNickname(nickname); + userChannelJpaRepository.save(userChannel); + } + + @Override + public void doubleCheckNicknameThrowException(Long channelId, String nickname) { + // 닉네임 중복 시 throw Exception + userChannelJpaRepository.findAllByChannelId(channelId).stream() + .filter(userChannel -> userChannel.getNickname().equals(nickname)) + .findAny() + .ifPresent(userChannel -> { + throw new IllegalStateException("이미 사용 중인 닉네임입니다."); + }); + } + + @Override + public void checkManagerThrowException(Long userId, Long managerId) { + if (!Objects.equals(userId, managerId)) { + throw new IllegalArgumentException("해당 채널의 매니저가 아닙니다."); + } + } + + @Override + public User findUserByIdThrowException(Long userId) { + return userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("해당 사용자가 존재하지 않습니다.")); + } + + @Override + public Channel findChannelByIdThrowException(Long channelId) { + return channelRepository.findById(channelId).orElseThrow(() -> new IllegalArgumentException("해당 채널이 존재하지 않습니다.")); + } +} diff --git a/src/main/java/gdsc/comunity/util/ListWrapper.java b/src/main/java/gdsc/comunity/util/ListWrapper.java new file mode 100644 index 0000000..cc71e93 --- /dev/null +++ b/src/main/java/gdsc/comunity/util/ListWrapper.java @@ -0,0 +1,12 @@ +package gdsc.comunity.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ListWrapper { + private T data; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 22cf694..9425723 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: datasource: - url: "jdbc:h2:mem:community;MODE=PostgreSQL" + url: "jdbc:h2:mem:community;MODE=mysql" username: "sa" password: "" driver-class-name: org.h2.Driver diff --git a/src/test/java/gdsc/comunity/service/channel/ChannelServiceImplTest.java b/src/test/java/gdsc/comunity/service/channel/ChannelServiceImplTest.java new file mode 100644 index 0000000..46cb72a --- /dev/null +++ b/src/test/java/gdsc/comunity/service/channel/ChannelServiceImplTest.java @@ -0,0 +1,355 @@ +package gdsc.comunity.service.channel; + +import gdsc.comunity.dto.channel.ChannelInfoDto; +import gdsc.comunity.entity.channel.Channel; +import gdsc.comunity.entity.channel.ChannelJoinRequest; +import gdsc.comunity.entity.user.Provider; +import gdsc.comunity.entity.user.User; +import gdsc.comunity.entity.userchannel.UserChannel; +import gdsc.comunity.repository.channel.ChannelJoinRequestRepository; +import gdsc.comunity.repository.channel.ChannelRepository; +import gdsc.comunity.repository.user.UserRepository; +import gdsc.comunity.repository.userchannel.UserChannelJpaRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@SpringBootTest +@ActiveProfiles("test") +@Transactional +@Rollback +@Slf4j +@TestMethodOrder(MethodOrderer.MethodName.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ChannelServiceImplTest { + private final ChannelServiceImpl channelService; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + private final UserChannelJpaRepository userChannelJpaRepository; + private final ChannelJoinRequestRepository channelJoinRequestRepository; + + @Autowired + public ChannelServiceImplTest(ChannelServiceImpl channelService, UserRepository userRepository, ChannelRepository channelRepository, UserChannelJpaRepository userChannelJpaRepository, ChannelJoinRequestRepository channelJoinRequestRepository) { + this.channelService = channelService; + this.userRepository = userRepository; + this.channelRepository = channelRepository; + this.userChannelJpaRepository = userChannelJpaRepository; + this.channelJoinRequestRepository = channelJoinRequestRepository; + } + + @Test + @DisplayName("채널 생성 성공") + void createChannel_Success() { + // set env + User user = User.builder() + .email("email") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + // do + Channel channel = channelService.createChannel(user.getId(), "New Channel", "nickname"); + + // assert result + assertNotNull(channel.getId()); + assertEquals("New Channel", channel.getChannelName()); + assertEquals(user.getId(), channel.getManager().getId()); + + Optional userChannel = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(userChannel.isPresent()); + } + + @Test + @DisplayName("채널 가입 요청 성공") + void sendJoinRequest_Success() { + // set env + User user = User.builder() + .email("email") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + // do + channelService.sendJoinRequest("nickname2", user.getId(), channel.getId()); + + // assert result + Optional userChannel = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(userChannel.isEmpty()); + + Optional channelJoinRequest = channelJoinRequestRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(channelJoinRequest.isPresent()); + } + + @Test + @DisplayName("채널 가입 요청 승인 성공") + void approveJoinChannel_Success() { + // set env + User user = User.builder() + .email("email2") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + channelService.sendJoinRequest("nickname2", user.getId(), channel.getId()); + + // do + channelService.approveJoinChannel(manager.getId(), user.getId(), channel.getId()); + + // assert result + Optional userChannel = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(userChannel.isPresent()); + + Optional channelJoinRequest = channelJoinRequestRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(channelJoinRequest.isEmpty()); + } + + @Test + @DisplayName("채널 나가기 성공 - 매니저가 아닌 경우") + void leaveChannel_Success_WithNormalUser() { + // Arrange + User user = User.builder() + .email("email") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + channelService.sendJoinRequest("nickname2", user.getId(), channel.getId()); + channelService.approveJoinChannel(manager.getId(), user.getId(), channel.getId()); + + // Act + channelService.leaveChannel(user.getId(), channel.getId()); + + // Assert + Optional userChannelOptional = userChannelJpaRepository.findByUserIdAndChannelId(user.getId(), channel.getId()); + assertTrue(userChannelOptional.isEmpty()); + + Optional channelOptional = channelRepository.findById(channel.getId()); + assertTrue(channelOptional.isPresent()); + assertEquals(manager.getId(), channelOptional.get().getManager().getId()); + } + + @Test + @DisplayName("채널 나가기 실패 - 매니저인 경우, 매니저를 넘겨줄 사람이 없는 경우") + void leaveChannel_Failure_WithManager_NoMoreUser() { + // Arrange + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + // Act & Assert + Long managerId = manager.getId(); + Long channelId = channel.getId(); + + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + channelService.leaveChannel(managerId, channelId); + }); + } + + @Test + @DisplayName("UserChannel respository 메소드 테스트 - findTop2ByChannelIdOrderByCreatedDateDesc") + void userRepositoryFindTop2ByChannelIdOrderByCreatedDateDesc() { + // Arrange + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + User user1 = User.builder() + .email("email1") + .profileImageUrl("image1") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user1 = userRepository.save(user1); + + User user2 = User.builder() + .email("email2") + .profileImageUrl("image2") + .provider(Provider.GOOGLE) + .providerId("3") + .build(); + user2 = userRepository.save(user2); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + channelService.sendJoinRequest("nickname1", user1.getId(), channel.getId()); + channelService.sendJoinRequest("nickname2", user2.getId(), channel.getId()); + channelService.approveJoinChannel(manager.getId(), user1.getId(), channel.getId()); + channelService.approveJoinChannel(manager.getId(), user2.getId(), channel.getId()); + + // Act + List listUserChannel = userChannelJpaRepository.findTop2ByChannelIdOrderByCreatedDateAsc(channel.getId()); + + // Assert + assertEquals(2, listUserChannel.size()); + } + + @Test + @DisplayName("채널 나가기 성공 - 매니저인 경우, 매니저를 넘겨줄 사람이 있는 경우") + void leaveChannel_Success_WithManager_HasMoreUser() { + // Arrange + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + User user = User.builder() + .email("email") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + channelService.sendJoinRequest("nickname2", user.getId(), channel.getId()); + channelService.approveJoinChannel(manager.getId(), user.getId(), channel.getId()); + + // Act + channelService.leaveChannel(manager.getId(), channel.getId()); + + // Assert + Optional userChannel = userChannelJpaRepository.findByUserIdAndChannelId(manager.getId(), channel.getId()); + assertTrue(userChannel.isEmpty()); + + Optional channelOptional = channelRepository.findById(channel.getId()); + assertTrue(channelOptional.isPresent()); + assertEquals(user.getId(), channelOptional.get().getManager().getId()); + } + + @Test + @DisplayName("채널 삭제 실패 - 매니저가 아닌 경우") + void deleteChannel_Failure_NotManager() { + // Arrange + User user = User.builder() + .email("email") + .profileImageUrl("image") + .provider(Provider.GOOGLE) + .providerId("1") + .build(); + user = userRepository.save(user); + + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + Long userId = user.getId(); + Long channelId = channel.getId(); + + // Act && Assert + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + channelService.deleteChannel(userId, channelId); + }); + } + + @Test + @DisplayName("채널 삭제 성공") + void deleteChannel_Success() { + // Arrange + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + // Act + channelService.deleteChannel(manager.getId(), channel.getId()); + + // Assert + Optional channelOptional = channelRepository.findById(channel.getId()); + assertTrue(channelOptional.isEmpty()); + + Optional userChannelOptional = userChannelJpaRepository.findByUserIdAndChannelId(manager.getId(), channel.getId()); + assertTrue(userChannelOptional.isEmpty()); + } + + @Test + @DisplayName("채널 검색 성공") + void searchChannel_Success() { + // Arrange + User manager = User.builder() + .email("manager") + .profileImageUrl("manager") + .provider(Provider.GOOGLE) + .providerId("2") + .build(); + manager = userRepository.save(manager); + Channel channel = channelService.createChannel(manager.getId(), "New Channel", "nickname"); + + // Act + ChannelInfoDto result = channelService.searchChannel(channel.getId()); + + // Assert + assertThat(result.getChannelName()).isEqualTo("New Channel"); + } +} \ No newline at end of file