diff --git a/src/main/java/stackpot/mongo/ChatRepository.java b/src/main/java/stackpot/mongo/ChatRepository.java index 09d6c560..6428038b 100644 --- a/src/main/java/stackpot/mongo/ChatRepository.java +++ b/src/main/java/stackpot/mongo/ChatRepository.java @@ -1,6 +1,7 @@ package stackpot.mongo; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @@ -27,4 +28,6 @@ public interface ChatRepository extends MongoRepository { int countByChatRoomIdAndIdGreaterThan(Long chatRoomId, Long lastReadChatId); void deleteByUserIdAndChatRoomId(Long userId, Long chatRoomId); + + } diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java index 7dd2f606..f5b258ee 100755 --- a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java @@ -40,6 +40,7 @@ public enum ErrorStatus implements BaseErrorCode { // Pot 관련 에러 POT_NOT_FOUND(HttpStatus.NOT_FOUND, "POT4004", "팟이 존재하지 않습니다."), POT_FORBIDDEN(HttpStatus.FORBIDDEN, "POT4003", "팟 생성자가 아닙니다."), + POT_OWNERSHIP_TRANSFER_REQUIRED(HttpStatus.CONFLICT, "POT4005", "권한을 위임해 주세요."), // Pot Comment 관련 에러 POT_COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "POTCOMMENT4001", "Pot Comment를 찾을 수 없습니다"), diff --git a/src/main/java/stackpot/stackpot/chat/repository/ChatRoomInfoRepository.java b/src/main/java/stackpot/stackpot/chat/repository/ChatRoomInfoRepository.java index 7ceffb56..b36cb0d0 100644 --- a/src/main/java/stackpot/stackpot/chat/repository/ChatRoomInfoRepository.java +++ b/src/main/java/stackpot/stackpot/chat/repository/ChatRoomInfoRepository.java @@ -1,9 +1,10 @@ package stackpot.stackpot.chat.repository; -import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.repository.query.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; import stackpot.stackpot.chat.entity.ChatRoomInfo; import java.util.List; @@ -27,4 +28,10 @@ public interface ChatRoomInfoRepository extends JpaRepository potMemberIds); + + @Modifying + @Transactional + @Query("DELETE FROM ChatRoomInfo cri WHERE cri.potMember.potMemberId = :potMemberId AND cri.chatRoom.id IN :chatRoomIds") + void deleteByPotMemberIdAndChatRoomIds(@Param("potMemberId") Long potMemberId, @Param("chatRoomIds") List chatRoomIds); + } diff --git a/src/main/java/stackpot/stackpot/chat/repository/ChatRoomRepository.java b/src/main/java/stackpot/stackpot/chat/repository/ChatRoomRepository.java index 46209eeb..7d350b95 100644 --- a/src/main/java/stackpot/stackpot/chat/repository/ChatRoomRepository.java +++ b/src/main/java/stackpot/stackpot/chat/repository/ChatRoomRepository.java @@ -6,7 +6,9 @@ import org.springframework.data.jpa.repository.Query; import stackpot.stackpot.chat.dto.ChatRoomDto; import stackpot.stackpot.chat.entity.ChatRoom; +import stackpot.stackpot.user.entity.User; +import java.util.List; import java.util.Optional; public interface ChatRoomRepository extends JpaRepository { @@ -26,4 +28,11 @@ public interface ChatRoomRepository extends JpaRepository { @Modifying @Query("delete from ChatRoom c where c.pot.potId = :potId") void deleteByPotId(@Param("potId") Long potId); + + + + @Query("select cr.id from ChatRoom cr where cr.pot.potId in :potIds") + List findIdsByPotIdIn(@Param("potIds") List potIds); + + } diff --git a/src/main/java/stackpot/stackpot/chat/service/chat/ChatSendService.java b/src/main/java/stackpot/stackpot/chat/service/chat/ChatSendService.java index 99087f23..f729b654 100644 --- a/src/main/java/stackpot/stackpot/chat/service/chat/ChatSendService.java +++ b/src/main/java/stackpot/stackpot/chat/service/chat/ChatSendService.java @@ -18,4 +18,7 @@ public class ChatSendService { public void sendMessage(Chat chat, Long chatRoomId) { messagingTemplate.convertAndSend(CHAT_SUB_URL + chatRoomId, chatConverter.toChatDto(chat)); } + public void deleteMessage(Long chatRoomId, Long chatId) { + messagingTemplate.convertAndSend(CHAT_SUB_URL + chatRoomId, "DELETE_" + chatId); // DELETE_ 형식으로 클라이언트에게 삭제 알림 + } } diff --git a/src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java index d64cf8f9..e2628e88 100644 --- a/src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java +++ b/src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java @@ -30,10 +30,12 @@ public class FeedConverter{ public FeedResponseDto.FeedDto feedDto(Feed feed, Boolean isOwner, Boolean isLiked, Boolean isSaved, long saveCount) { Long commentCount = feedCommentRepository.countByFeedId(feed.getFeedId()); + String writerNickname = getWriterNickname(feed.getUser()); + return FeedResponseDto.FeedDto.builder() .feedId(feed.getFeedId()) .writerId(feed.getUser().getId()) - .writer(feed.getUser().getNickname() + " 새싹") + .writer(writerNickname) .writerRole(feed.getUser().getRole()) .title(feed.getTitle()) .content(feed.getContent()) @@ -55,6 +57,8 @@ public FeedResponseDto.CreatedFeedDto createFeedDto(Feed feed) { "comment", feed.getSeries().getComment() ); } + Long commentCount = feedCommentRepository.countByFeedId(feed.getFeedId()); + return FeedResponseDto.CreatedFeedDto.builder() .feedId(feed.getFeedId()) @@ -106,11 +110,11 @@ public FeedResponseDto.AuthorizedFeedDto toAuthorizedFeedDto(Feed feed, boolean "comment", feed.getSeries().getComment() ); } - + String writerNickname = getWriterNickname(feed.getUser()); FeedResponseDto.CreatedFeedDto createdDto = FeedResponseDto.CreatedFeedDto.builder() .feedId(feed.getFeedId()) .writerId(feed.getUser().getId()) - .writer(feed.getUser().getNickname()+" 새싹") + .writer(writerNickname) .writerRole(feed.getUser().getRole()) .title(feed.getTitle()) .content(feed.getContent()) @@ -163,6 +167,18 @@ public FeedCacheDto toFeedCacheDto(Feed feed) { .createdAt(feed.getCreatedAt().toString()) .build(); } + public String getWriterNickname(User user) { + String writerNickname = user.getNickname(); + + // 사용자가 탈퇴한 경우 "새싹"을 제거 + if (user.isDeleted()) { + writerNickname = writerNickname; // 탈퇴한 경우 "새싹" 제거 + } else { + writerNickname += " 새싹"; // 탈퇴하지 않은 경우 "새싹" 추가 + } + + return writerNickname; + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/pot/entity/Pot.java b/src/main/java/stackpot/stackpot/pot/entity/Pot.java index 9b3e5c6f..41ff9dd0 100644 --- a/src/main/java/stackpot/stackpot/pot/entity/Pot.java +++ b/src/main/java/stackpot/stackpot/pot/entity/Pot.java @@ -29,7 +29,7 @@ public class Pot extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private User user; - @OneToMany(mappedBy = "pot") + @OneToMany(mappedBy = "pot",cascade = CascadeType.ALL, orphanRemoval = true) private List recruitmentDetails; @OneToMany(mappedBy = "pot", cascade = CascadeType.REMOVE, orphanRemoval = true) diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotApplicationRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotApplicationRepository.java index c59d35b5..a8937b64 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotApplicationRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotApplicationRepository.java @@ -1,8 +1,10 @@ package stackpot.stackpot.pot.repository; +import jakarta.persistence.QueryHint; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.query.Param; import stackpot.stackpot.pot.entity.mapping.PotApplication; @@ -22,4 +24,10 @@ public interface PotApplicationRepository extends JpaRepository potIds); + + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java index fd200dae..9d935557 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java @@ -39,7 +39,7 @@ public interface PotMemberRepository extends JpaRepository { boolean existsByPotAndUser(Pot pot, User user); - // ✅ 특정 Pot에 속한 사용자(userId)의 역할(Role) 찾기 + // 특정 Pot에 속한 사용자(userId)의 역할(Role) 찾기 @Query("SELECT pm.roleName FROM PotMember pm WHERE pm.pot.potId = :potId AND pm.user.id = :userId") Optional findRoleByUserId(@Param("potId") Long potId, @Param("userId") Long userId); @@ -89,4 +89,9 @@ public interface PotMemberRepository extends JpaRepository { @Query("SELECT pm.pot.potId FROM PotMember pm WHERE pm.user.id = :userId AND pm.pot.potId IN :potIds") Set findPotIdsByUserIdAndPotIds(@Param("userId") Long userId, @Param("potIds") List potIds); + + + @Modifying + @Query("DELETE FROM PotMember pm WHERE pm.pot.potId IN :potIds AND pm.user.id = :userId") + void deleteByUserIdAndPotIdIn(@Param("userId") Long userId, @Param("potIds") List potIds); } diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotRecruitmentDetailsRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotRecruitmentDetailsRepository.java index 818d872e..392a4ceb 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotRecruitmentDetailsRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotRecruitmentDetailsRepository.java @@ -7,9 +7,17 @@ import org.springframework.data.repository.query.Param; import stackpot.stackpot.pot.entity.PotRecruitmentDetails; +import java.util.List; + public interface PotRecruitmentDetailsRepository extends JpaRepository { @Modifying @Transactional @Query("DELETE FROM PotRecruitmentDetails r WHERE r.pot.potId = :potId") void deleteByPot_PotId(@Param("potId") Long potId); + + + @Modifying + @Transactional + @Query("DELETE FROM PotRecruitmentDetails prd WHERE prd.pot.potId IN :potIds") + void deleteByPotIds(@Param("potIds") List potIds); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotRepository.java index 5dfea427..d621b051 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotRepository.java @@ -56,7 +56,9 @@ public interface PotRepository extends JpaRepository { @Modifying @Query("DELETE FROM Pot f WHERE f.user.id = :userId") void deleteByUserId(@Param("userId") Long userId); - + @Modifying + @Query("DELETE FROM Pot p WHERE p.user.id = :userId AND p.potId IN :potIds AND p.potStatus = 'RECRUITING'") + void deleteByUserIdAndPotIds(@Param("userId") Long userId, @Param("potIds") List potIds); boolean existsByUserId(Long userId); // 지원자 수 기준으로 모든 Pot 정렬 @@ -76,5 +78,11 @@ public interface PotRepository extends JpaRepository { List findByPotMembers_UserIdOrderByCreatedAtDesc(Long userId); + @Query("select p.potId from Pot p where p.user.id = :userId and p.potStatus = :status") + List findIdsByUserIdAndStatus(@Param("userId") Long userId, + @Param("status") String status); + @Query("select p.potId from Pot p where p.user.id = :userId and p.potStatus not in :statuses") + List findIdsByUserIdAndStatusNotIn(@Param("userId") Long userId, + @Param("statuses") List statuses); } diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotSaveRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotSaveRepository.java index 4424ecf8..e2c94e0b 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotSaveRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotSaveRepository.java @@ -8,7 +8,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import stackpot.stackpot.pot.entity.Pot; -import stackpot.stackpot.pot.entity.PotRecruitmentDetails; import stackpot.stackpot.pot.entity.mapping.PotSave; import stackpot.stackpot.user.entity.User; @@ -47,4 +46,12 @@ default Map countSavesByPotIds(List potIds) { @Query("SELECT ps.pot FROM PotSave ps WHERE ps.user.id = :userId") Page findSavedPotsByUserId(@Param("userId") Long userId, Pageable pageable); + + @Modifying + @Transactional + @Query("DELETE FROM PotSave ps WHERE ps.user = :user AND ps.pot IN :pots") + void deleteAllByUserAndPots(@Param("user") User user, @Param("pots") List pots); + + List findByUser(User user); + void deleteByUser(User user); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/user/entity/User.java b/src/main/java/stackpot/stackpot/user/entity/User.java index fb4da53b..75d646aa 100644 --- a/src/main/java/stackpot/stackpot/user/entity/User.java +++ b/src/main/java/stackpot/stackpot/user/entity/User.java @@ -111,6 +111,9 @@ public void deleteUser() { } this.interests = new ArrayList<>(); // 새로운 관심사 목록 생성 this.interests.add("UNKNOWN"); // "UNKNOWN"을 관심사 목록에 추가 + if (this.seriesList != null) { + this.seriesList.clear(); // 연관된 시리즈 비우기 + } this.userTemperature = null; this.email = null; diff --git a/src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java index 9ec4cd5d..61ab1928 100644 --- a/src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java @@ -3,15 +3,26 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.apiPayload.code.status.ErrorStatus; import stackpot.stackpot.apiPayload.exception.GeneralException; +import stackpot.stackpot.apiPayload.exception.handler.PotHandler; import stackpot.stackpot.apiPayload.exception.handler.TokenHandler; import stackpot.stackpot.apiPayload.exception.handler.UserHandler; +import stackpot.stackpot.chat.repository.ChatRoomInfoRepository; +import stackpot.stackpot.chat.repository.ChatRoomRepository; +import stackpot.stackpot.chat.service.chat.ChatCommandService; +import stackpot.stackpot.chat.service.chatroom.ChatRoomQueryService; +import stackpot.stackpot.chat.service.chatroominfo.ChatRoomInfoCommandService; +import stackpot.stackpot.chat.service.chatroominfo.ChatRoomInfoQueryService; import stackpot.stackpot.common.util.AuthService; import stackpot.stackpot.config.security.JwtTokenProvider; +import stackpot.stackpot.pot.dto.UserMemberIdDto; +import stackpot.stackpot.pot.entity.mapping.PotSave; +import stackpot.stackpot.pot.repository.*; import stackpot.stackpot.task.repository.TaskRepository; import stackpot.stackpot.task.repository.TaskboardRepository; import stackpot.stackpot.user.converter.UserConverter; @@ -29,14 +40,10 @@ import stackpot.stackpot.user.entity.enums.Provider; import stackpot.stackpot.user.entity.enums.Role; import stackpot.stackpot.pot.entity.mapping.PotMember; -import stackpot.stackpot.pot.repository.PotMemberRepository; import stackpot.stackpot.pot.service.pot.PotSummarizationService; import stackpot.stackpot.badge.repository.PotMemberBadgeRepository; import stackpot.stackpot.todo.repository.UserTodoRepository; import stackpot.stackpot.feed.repository.FeedRepository; -import stackpot.stackpot.pot.repository.PotApplicationRepository; -import stackpot.stackpot.pot.repository.PotRecruitmentDetailsRepository; -import stackpot.stackpot.pot.repository.PotRepository; import stackpot.stackpot.user.entity.enums.UserType; import stackpot.stackpot.user.repository.BlacklistRepository; import stackpot.stackpot.user.repository.RefreshTokenRepository; @@ -64,7 +71,7 @@ public class UserCommandServiceImpl implements UserCommandService { private final TaskboardRepository taskboardRepository; private final PotRecruitmentDetailsRepository potRecruitmentDetailsRepository; private final PotMemberBadgeRepository potMemberBadgeRepository; - + private final ChatRoomInfoRepository chatRoominfoRepository; private final UserMypageConverter userMypageConverter; private final TempUserRepository tempUserRepository; private final PotSummarizationService potSummarizationService; @@ -72,8 +79,11 @@ public class UserCommandServiceImpl implements UserCommandService { private final RefreshTokenRepository refreshTokenRepository; private final BlacklistRepository blacklistRepository; private final AuthService authService; - + private final PotSaveRepository potSaveRepository; private final EmailService emailService; + private final ChatRoomRepository chatRoomRepository; + private final ChatCommandService chatCommandService; + @Override @Transactional @@ -311,7 +321,7 @@ public String deleteUser(String accessToken) { blacklistRepository.addToBlacklist(token, jwtTokenProvider.getExpiration(token)); // Feed 관련 데이터 삭제 - deleteFeedRelatedData(user.getId()); +// deleteFeedRelatedData(user.getId()); // Todo 데이터 삭제 userTodoRepository.deleteByUserId(user.getId()); @@ -319,6 +329,9 @@ public String deleteUser(String accessToken) { // Task 및 Taskboard 관련 데이터 삭제 deleteTaskRelatedData(user.getId()); + // 사용자가 저장한 Pot 삭제 + potSaveRepository.deleteByUser(user); + // Pot 관련 데이터 삭제 boolean isCreator = potRepository.existsByUserId(user.getId()); if (isCreator) { @@ -339,7 +352,7 @@ private void deleteFeedRelatedData(Long userId) { feedLikeRepository.deleteByUserId(userId); // Feed 삭제 - feedRepository.deleteByUserId(userId); +// feedRepository.deleteByUserId(userId); } private void deleteTaskRelatedData(Long userId) { @@ -372,25 +385,50 @@ private void deleteTaskRelatedData(Long userId) { } } - private void handleCreatorPotDeletion(User user) { - List userPots = potRepository.findByUserId(user.getId()); + @Transactional + public void handleCreatorPotDeletion(User user) { + Long userId = user.getId(); + + + List completedPotIds = potRepository.findIdsByUserIdAndStatus(userId, "COMPLETED"); + List recruitingPotIds = potRepository.findIdsByUserIdAndStatus(userId, "RECRUITING"); + // 완료/모집중을 제외한 나머지(진행중 등) + List otherPotIds = potRepository.findIdsByUserIdAndStatusNotIn( + userId, List.of("COMPLETED", "RECRUITING") + ); + + // 2) 완료된 Pot → 현재 유저의 PotMember만 일괄 소프트 딜리트 (배치 UPDATE) +// if (!completedPotIds.isEmpty()) { +// potMemberRepository.softDeleteByPotIdsAndUserId(completedPotIds, userId); +// } + + // 3) 모집중 Pot → 연관 정리 후 배치 삭제 + if (!recruitingPotIds.isEmpty()) { + potRecruitmentDetailsRepository.deleteByPotIds(recruitingPotIds); + // PotApplication 삭제 + potApplicationRepository.deleteByPotIds(recruitingPotIds); + potRepository.deleteByUserIdAndPotIds(userId, recruitingPotIds); + } - for (Pot pot : userPots) { - if (pot.getPotStatus().equals("COMPLETED")) { - // 완료된 Pot의 경우 PotMember만 소프트 딜리트 - PotMember potMember = potMemberRepository.findByPotIdAndUserId(pot.getPotId(), user.getId()); - potMember.deletePotMember(); - potMemberRepository.save(potMember); - } else { - // 진행 중인 Pot 처리 - deletePotAndRelatedData(pot); - } + + // 4) 진행 중(기타 상태) → 권한 위임 요구 에러 + if (!otherPotIds.isEmpty()) { + throw new PotHandler(ErrorStatus.POT_OWNERSHIP_TRANSFER_REQUIRED); } - user.deleteUser(); // 소프트 딜리트 + // 5) 유저 소프트 딜리트 + user.deleteUser(); userRepository.save(user); } + @Transactional + public void deletePotAndRelatedData(List potIds) { + List pots = potRepository.findAllById(potIds); + for (Pot pot : pots) { + deletePotAndRelatedData(pot); + } + } + @Transactional public void deletePotAndRelatedData(Pot pot) { @@ -456,10 +494,27 @@ private void sendDeletionNotifications(List potMembers, Pot pot) { }); } - private void handleNormalUserPotDeletion(User user) { - potMemberRepository.deleteByUserId(user.getId()); - potApplicationRepository.deleteByUserId(user.getId()); - userRepository.delete(user); + @Transactional + public void handleNormalUserPotDeletion(User user) { + Long userId = user.getId(); + // 1. 진행 중인 팟 IDs 조회 + List ongoingPotIds = potRepository.findIdsByUserIdAndStatus(userId, "ONGOING"); + + // 2. 진행 중인 팟에서 PotMember 삭제 + if (!ongoingPotIds.isEmpty()) { + potMemberRepository.deleteByUserIdAndPotIdIn(userId, ongoingPotIds); + + // 3. 진행 중인 팟에 해당하는 채팅방 ID들 조회 + List chatRoomIds = chatRoomRepository.findIdsByPotIdIn(ongoingPotIds); + + // 4. 각 채팅방에 대해 해당 유저의 채팅 메시지 삭제 + for (Long chatRoomId : chatRoomIds) { + chatCommandService.deleteChatMessage(userId, chatRoomId); + } + } + // 4. PotApplication 삭제 + potApplicationRepository.deleteByUserId(userId); + } @Override