diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/group/domain/repository/GroupUserRepository.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/group/domain/repository/GroupUserRepository.java index dc4d7ccc..7872054d 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/group/domain/repository/GroupUserRepository.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/group/domain/repository/GroupUserRepository.java @@ -1,8 +1,14 @@ package io.github.depromeet.knockknockbackend.domain.group.domain.repository; +import io.github.depromeet.knockknockbackend.domain.group.domain.Group; import io.github.depromeet.knockknockbackend.domain.group.domain.GroupUser; +import io.github.depromeet.knockknockbackend.domain.user.domain.User; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface GroupUserRepository - extends JpaRepository, CustomGroupUserRepository {} + extends JpaRepository, CustomGroupUserRepository { + + Optional findByGroupAndUser(Group group, User user); +} diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/Reservation.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/Reservation.java index 18b0ac63..5fc82470 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/Reservation.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/Reservation.java @@ -14,7 +14,9 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicUpdate -@Table(name = "tbl_reservation") +@Table( + name = "tbl_reservation", + uniqueConstraints = {@UniqueConstraint(columnNames = {"group_id", "send_user_id"})}) @Entity public class Reservation extends BaseTimeEntity { diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/repository/ReservationRepository.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/repository/ReservationRepository.java index 0373eac1..32853b7f 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/repository/ReservationRepository.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/domain/repository/ReservationRepository.java @@ -6,13 +6,14 @@ import io.github.depromeet.knockknockbackend.domain.user.domain.User; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; public interface ReservationRepository extends CrudRepository { - List findByGroupAndSendUserOrderBySendAtAsc(Group group, User sendUser); + Optional findByGroupAndSendUser(Group group, User sendUser); List findBySendAtLessThan(LocalDateTime sendAt); diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/exception/ReservationAlreadyExistException.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/exception/ReservationAlreadyExistException.java new file mode 100644 index 00000000..8d79d513 --- /dev/null +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/exception/ReservationAlreadyExistException.java @@ -0,0 +1,13 @@ +package io.github.depromeet.knockknockbackend.domain.notification.exception; + + +import io.github.depromeet.knockknockbackend.global.error.exception.ErrorCode; +import io.github.depromeet.knockknockbackend.global.error.exception.KnockException; + +public class ReservationAlreadyExistException extends KnockException { + public static final KnockException EXCEPTION = new ReservationAlreadyExistException(); + + private ReservationAlreadyExistException() { + super(ErrorCode.RESERVATION_ALREADY_EXIST); + } +} diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/presentation/dto/response/QueryNotificationListResponse.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/presentation/dto/response/QueryNotificationListResponse.java index cacd8572..3c78a408 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/presentation/dto/response/QueryNotificationListResponse.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/presentation/dto/response/QueryNotificationListResponse.java @@ -1,7 +1,6 @@ package io.github.depromeet.knockknockbackend.domain.notification.presentation.dto.response; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.data.domain.Slice; @@ -10,6 +9,6 @@ @AllArgsConstructor public class QueryNotificationListResponse { - private final List reservations; + private final QueryReservationListResponseElement reservations; private final Slice notifications; } diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/NotificationService.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/NotificationService.java index d5558924..2f173639 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/NotificationService.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/NotificationService.java @@ -2,7 +2,11 @@ import io.github.depromeet.knockknockbackend.domain.group.domain.Group; +import io.github.depromeet.knockknockbackend.domain.group.domain.GroupType; +import io.github.depromeet.knockknockbackend.domain.group.domain.repository.GroupUserRepository; +import io.github.depromeet.knockknockbackend.domain.group.exception.NotMemberException; import io.github.depromeet.knockknockbackend.domain.group.presentation.dto.response.GroupInfoForNotificationDto; +import io.github.depromeet.knockknockbackend.domain.group.service.GroupService; import io.github.depromeet.knockknockbackend.domain.notification.domain.*; import io.github.depromeet.knockknockbackend.domain.notification.domain.Notification; import io.github.depromeet.knockknockbackend.domain.notification.domain.repository.DeviceTokenRepository; @@ -38,11 +42,13 @@ public class NotificationService implements NotificationUtils { private static final boolean CREATED_DELETED_STATUS = false; private final EntityManager entityManager; private final FcmService fcmService; + private final GroupService groupService; private final NotificationRepository notificationRepository; private final DeviceTokenRepository deviceTokenRepository; private final NotificationReactionRepository notificationReactionRepository; private final ReservationRepository reservationRepository; private final NotificationExperienceRepository notificationExperienceRepository; + private final GroupUserRepository groupUserRepository; @Transactional(readOnly = true) public QueryNotificationListLatestResponse queryListLatest() { @@ -79,11 +85,10 @@ public QueryNotificationListResponse queryListByGroupId(Pageable pageable, Long getQueryNotificationListResponseElements( notification, myNotificationReactions)); - List reservations = - reservationRepository.findByGroupAndSendUserOrderBySendAtAsc( - Group.of(groupId), User.of(SecurityUtils.getCurrentUserId())); - List queryReservationListResponseElements = - reservations.stream() + QueryReservationListResponseElement queryReservationListResponseElement = + reservationRepository + .findByGroupAndSendUser( + Group.of(groupId), User.of(SecurityUtils.getCurrentUserId())) .map( reservation -> QueryReservationListResponseElement.builder() @@ -93,10 +98,10 @@ public QueryNotificationListResponse queryListByGroupId(Pageable pageable, Long .imageUrl(reservation.getImageUrl()) .sendAt(reservation.getSendAt()) .build()) - .collect(Collectors.toList()); + .orElse(null); return new QueryNotificationListResponse( - queryReservationListResponseElements, queryNotificationListResponseElements); + queryReservationListResponseElement, queryNotificationListResponseElements); } public QueryNotificationListResponseElement getQueryNotificationListResponseElements( @@ -168,9 +173,10 @@ public void registerFcmToken(RegisterFcmTokenRequest request) { @Transactional public void sendInstance(SendInstanceRequest request) { - Long sendUserId = SecurityUtils.getCurrentUserId(); + Long currentUserId = SecurityUtils.getCurrentUserId(); + validateSendNotificationPermission(request.getGroupId(), currentUserId); - List deviceTokens = getDeviceTokens(request.getGroupId(), sendUserId); + List deviceTokens = getDeviceTokens(request.getGroupId(), currentUserId); List tokens = getFcmTokens(deviceTokens); if (tokens.isEmpty()) { return; @@ -185,7 +191,7 @@ public void sendInstance(SendInstanceRequest request) { request.getContent(), request.getImageUrl(), Group.of(request.getGroupId()), - User.of(sendUserId), + User.of(currentUserId), null); } @@ -241,6 +247,19 @@ private void validateDeletePermission(Notification notification) { } } + public void validateSendNotificationPermission(Long groupId, Long userId) { + Group group = groupService.queryGroup(groupId); + // 홀로외침방이면 방장인지 + if (GroupType.OPEN.equals(group.getGroupType())) group.validUserIsHost(userId); + // 친구방이면 그룹 소속원인지 + if (GroupType.FRIEND.equals(group.getGroupType())) { + // group.validUserIsMemberOfGroup(userId); //todo: 해당 부분 호춣시 불필요한 쿼리 발생. + groupUserRepository + .findByGroupAndUser(group, User.of(userId)) + .orElseThrow(() -> NotMemberException.EXCEPTION); + } + } + @Override public Notification queryNotificationById(Long notificationId) { return notificationRepository diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/ReservationService.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/ReservationService.java index 4825fe0d..d1d16c2c 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/ReservationService.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/notification/service/ReservationService.java @@ -5,6 +5,7 @@ import io.github.depromeet.knockknockbackend.domain.notification.domain.DeviceToken; import io.github.depromeet.knockknockbackend.domain.notification.domain.Reservation; import io.github.depromeet.knockknockbackend.domain.notification.domain.repository.ReservationRepository; +import io.github.depromeet.knockknockbackend.domain.notification.exception.ReservationAlreadyExistException; import io.github.depromeet.knockknockbackend.domain.notification.exception.ReservationForbiddenException; import io.github.depromeet.knockknockbackend.domain.notification.exception.ReservationNotFoundException; import io.github.depromeet.knockknockbackend.domain.notification.presentation.dto.request.ChangeSendAtReservationRequest; @@ -32,6 +33,16 @@ public class ReservationService { public void sendReservation(SendReservationRequest request) { Long currentUserId = SecurityUtils.getCurrentUserId(); + notificationService.validateSendNotificationPermission(request.getGroupId(), currentUserId); + Reservation reservation = + reservationRepository + .findByGroupAndSendUser( + Group.of(request.getGroupId()), User.of(currentUserId)) + .orElse(null); + + if (reservation != null) { + throw ReservationAlreadyExistException.EXCEPTION; + } reservationRepository.save( Reservation.of( diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepository.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepository.java index 3accd30f..b194bc4f 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepository.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepository.java @@ -12,4 +12,6 @@ Optional findRelationBySendUserIdAndReceiveUserId( Long sendUserId, Long receiveUserId); boolean isFriend(Long currentUserId, Long userId); + + Long getRelationIdByUserId(Long currentUserId, Long userId); } diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepositoryImpl.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepositoryImpl.java index 353a9a8b..f8457837 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepositoryImpl.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/domain/repository/CustomRelationRepositoryImpl.java @@ -55,6 +55,17 @@ public boolean isFriend(Long currentUserId, Long userId) { .fetchFirst(); } + @Override + public Long getRelationIdByUserId(Long currentUserId, Long userId) { + return queryFactory + .select(relation.id) + .from(relation) + .where( + friendPredicated(currentUserId, userId) + .or(friendPredicated(userId, currentUserId))) + .fetchFirst(); + } + private BooleanExpression friendPredicated(Long senderUserId, Long receiveUserId) { return relation.sendUser.id.eq(senderUserId).and(relation.receiveUser.id.eq(receiveUserId)); } diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/exception/NotFriendRelationException.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/exception/NotFriendRelationException.java new file mode 100644 index 00000000..cc446cc9 --- /dev/null +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/exception/NotFriendRelationException.java @@ -0,0 +1,14 @@ +package io.github.depromeet.knockknockbackend.domain.relation.exception; + + +import io.github.depromeet.knockknockbackend.global.error.exception.ErrorCode; +import io.github.depromeet.knockknockbackend.global.error.exception.KnockException; + +public class NotFriendRelationException extends KnockException { + + public static final KnockException EXCEPTION = new NotFriendRelationException(); + + private NotFriendRelationException() { + super(ErrorCode.NOT_FRIEND_RELATION); + } +} diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/presentation/RelationController.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/presentation/RelationController.java index 92508b55..ac770d9f 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/presentation/RelationController.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/presentation/RelationController.java @@ -40,6 +40,13 @@ public ResponseEntity sendUserRequest(@RequestBody @Valid FriendRequest re return new ResponseEntity<>(relationService.sendFriendRequest(request)); } + @Operation(summary = "친구를 삭제하는 Api입니다. - 친구목록") + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping + public void deleteRelation(@RequestBody @Valid FriendRequest request) { + relationService.deleteRelation(request); + } + @Operation(summary = "친구 요청을 수락하는 Api입니다. - 메인 알림") @ResponseStatus(HttpStatus.CREATED) @PostMapping("/requests") diff --git a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/service/RelationService.java b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/service/RelationService.java index 59a4e2c9..2a4649ee 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/service/RelationService.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/domain/relation/service/RelationService.java @@ -6,6 +6,7 @@ import io.github.depromeet.knockknockbackend.domain.relation.exception.AlreadyFriendException; import io.github.depromeet.knockknockbackend.domain.relation.exception.AlreadySendRequestException; import io.github.depromeet.knockknockbackend.domain.relation.exception.FriendRequestNotFoundException; +import io.github.depromeet.knockknockbackend.domain.relation.exception.NotFriendRelationException; import io.github.depromeet.knockknockbackend.domain.relation.presentation.dto.request.FriendRequest; import io.github.depromeet.knockknockbackend.domain.relation.presentation.dto.response.QueryFriendListResponse; import io.github.depromeet.knockknockbackend.domain.relation.presentation.dto.response.QueryFriendListResponseElement; @@ -77,6 +78,14 @@ public HttpStatus sendFriendRequest(FriendRequest request) { return HttpStatus.CREATED; } + public void deleteRelation(FriendRequest request) { + if (!getIsFriend(request.getUserId())) { + throw NotFriendRelationException.EXCEPTION; + } + + relationRepository.deleteById(getRelationId(request.getUserId())); + } + public void acceptRequest(FriendRequest request) { updateIsFriendWithValidate(request, true); } @@ -89,6 +98,10 @@ public boolean getIsFriend(Long userId) { return relationRepository.isFriend(SecurityUtils.getCurrentUserId(), userId); } + private Long getRelationId(Long userId) { + return relationRepository.getRelationIdByUserId(SecurityUtils.getCurrentUserId(), userId); + } + private void updateIsFriendWithValidate(FriendRequest request, boolean isFriend) { if (!getIsFriend(request.getUserId())) { throw AlreadyFriendException.EXCEPTION; diff --git a/src/main/java/io/github/depromeet/knockknockbackend/global/error/exception/ErrorCode.java b/src/main/java/io/github/depromeet/knockknockbackend/global/error/exception/ErrorCode.java index 7a68a42a..e24d7277 100644 --- a/src/main/java/io/github/depromeet/knockknockbackend/global/error/exception/ErrorCode.java +++ b/src/main/java/io/github/depromeet/knockknockbackend/global/error/exception/ErrorCode.java @@ -28,6 +28,7 @@ public enum ErrorCode { ALREADY_SEND_REQUEST(400, "RELATION-400-1", "Already Send Request."), ALREADY_FRIEND_REQUEST(400, "RELATION-400-1", "Already Friend Request."), FRIEND_REQUEST_NOT_FOUND(404, "RELATION-404-1", "Friend Request Not Found."), + NOT_FRIEND_RELATION(404, "RELATION-404-2", "Not Friend Relation."), OPTION_NOT_FOUND(404, "OPTION-404-1", "Option Not Found."), @@ -64,12 +65,16 @@ public enum ErrorCode { NOTIFICATION_FCM_FAIL_SEND( HttpStatus.INTERNAL_SERVER_ERROR.value(), "NOTIFICATION-500-1", "FCM ERROR"), - RESERVATION_NOT_FOUND( - HttpStatus.NOT_FOUND.value(), "RESERVATION-404-1", "Reservation Not Found"), RESERVATION_FORBIDDEN( HttpStatus.FORBIDDEN.value(), "RESERVATION-403-1", "The user has no access to the reservation"), + RESERVATION_ALREADY_EXIST( + HttpStatus.FORBIDDEN.value(), + "RESERVATION-403-2", + "The user already registered reservation"), + RESERVATION_NOT_FOUND( + HttpStatus.NOT_FOUND.value(), "RESERVATION-404-1", "Reservation Not Found"), STORAGE_FORBIDDEN( HttpStatus.FORBIDDEN.value(), "STORAGE-403-1", "The user has no access to the storage"),