Skip to content

Commit

Permalink
Merge pull request #93 from dev-hooon/feat/#79/reservation-notification
Browse files Browse the repository at this point in the history
feat : 예약 알림 기능 추가
  • Loading branch information
dlswns2480 authored Jan 11, 2024
2 parents b224820 + ae82272 commit 1ae2f38
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package com.prgrms.catchtable.common.notification;

import java.util.function.Function;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum NotificationContent {
RESERVATION_COMPLETED("예약이 완료되었습니다"),
RESERVATION_ONE_HOUR_LEFT("예약 시간 1시간 전입니다."),
RESERVATION_TIME_TO_ENTER("예약시간이 되었습니다"),
WAITING_REGISTER_COMPLETED("웨이팅 등록이 완료되었습니다"),
WAITING_RANK_THIRD("웨이팅 순서가 3번째가 되었습니다"),
WAITING_TIME_TO_ENTER("웨이팅이 끝났습니다. 입장 부탁드립니다."),
WAITING_CANCELLED_AUTOMATICALLY("웨이팅이 자동으로 취소되었습니다.");
RESERVATION_COMPLETED(time -> time.concat(" 시간의 예약이 완료 되었습니다.")),
RESERVATION_CANCELLED(time -> time.concat(" 시간의 예약이 취소 되었습니다")),
RESERVATION_ONE_HOUR_LEFT(time -> time.concat(" 시간 예약까지 한시간 남았습니다")),
RESERVATION_TIME_OUT(time -> "예약 시간이 되었습니다. 입장해주세요.");

private final String message;

private final Function<String, String> expression;

public String getMessage(String time) {
return expression.apply(time);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT;

import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.notification.dto.request.SendMessageToMemberRequest;
import com.prgrms.catchtable.notification.dto.request.SendMessageToOwnerRequest;
import com.prgrms.catchtable.notification.service.NotificationService;
import com.prgrms.catchtable.owner.domain.Owner;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
Expand All @@ -19,12 +21,18 @@ public class NotificationEvent {
@Async
@TransactionalEventListener(phase = AFTER_COMMIT) // 호출한쪽의 트랜잭션이 커밋 된 후 이벤트 발생
public void sendMessage(SendMessageToMemberRequest request) {
notificationService.sendMessageAndSave(request.member(), request.content());
Member member = request.member();
if (member.isNotification_activated()) {
notificationService.sendMessageAndSave(member, request.content());
}
}

@Async
@TransactionalEventListener(phase = AFTER_COMMIT) // 호출한쪽의 트랜잭션이 커밋 된 후 이벤트 발생
public void sendMessage(SendMessageToOwnerRequest request) {
notificationService.sendMessageAndSave(request.owner(), request.content());
Owner owner = request.owner();
if (owner.isNotification_activated()) {
notificationService.sendMessageAndSave(request.owner(), request.content());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.prgrms.catchtable.notification.dto.request;

import com.prgrms.catchtable.common.notification.NotificationContent;
import com.prgrms.catchtable.member.domain.Member;
import lombok.Builder;

@Builder
public record SendMessageToMemberRequest(Member member,
NotificationContent content) {
String content) {

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.prgrms.catchtable.notification.dto.request;

import com.prgrms.catchtable.common.notification.NotificationContent;
import com.prgrms.catchtable.owner.domain.Owner;
import lombok.Builder;

@Builder
public record SendMessageToOwnerRequest(Owner owner,
NotificationContent content) {
String content) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.springframework.http.HttpMethod.POST;

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import com.prgrms.catchtable.common.notification.NotificationContent;
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.member.repository.MemberRepository;
import com.prgrms.catchtable.notification.domain.NotificationMember;
Expand Down Expand Up @@ -38,11 +37,11 @@ public class NotificationService {
private final OwnerRepository ownerRepository; // 추후 삭제 예정
private JSONObject jsonObject;

public void sendMessageAndSave(Member member, NotificationContent content) {
public void sendMessageAndSave(Member member, String content) {
String url = "https://slack.com/api/chat.postMessage"; // slack 메세지를 보내도록 요청하는 Slack API

String email = member.getEmail();
String message = content.getMessage();
String message = content;
String slackId = getSlackIdByEmail(email); // 이메일을 통해 사용자의 슬랙 고유 ID 추출

requestToSendMessage(slackId, message); // 알림 요청 보내는 함수 호출
Expand All @@ -56,11 +55,11 @@ public void sendMessageAndSave(Member member, NotificationContent content) {

}

public void sendMessageAndSave(Owner owner, NotificationContent content) {
public void sendMessageAndSave(Owner owner, String content) {
String url = "https://slack.com/api/chat.postMessage"; // slack 메세지를 보내도록 요청하는 Slack API

String email = owner.getEmail();
String message = content.getMessage();
String message = content;
String slackId = getSlackIdByEmail(email);

requestToSendMessage(slackId, message);
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/prgrms/catchtable/owner/domain/Owner.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public class Owner extends BaseEntity implements UserDetails {
@Column(name = "date_birth")
private LocalDate dateBirth;

@Column(name = "notification_activated")
private boolean notification_activated;

@OneToOne(fetch = LAZY)
@JoinColumn(name = "shop_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Shop shop;
Expand All @@ -75,6 +78,7 @@ public Owner(String name, String email, String password, String phoneNumber, Gen
this.gender = gender;
this.dateBirth = dateBirth;
this.role = Role.OWNER;
this.notification_activated = true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.prgrms.catchtable.owner.repository;

import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.shop.domain.Shop;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

Expand All @@ -10,4 +11,6 @@ public interface OwnerRepository extends JpaRepository<Owner, Long> {

Optional<Owner> findOwnerByEmail(String email);

Optional<Owner> findOwnerByShop(Shop shop);

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ public ResponseEntity<ModifyReservationResponse> modifyReservation(

@DeleteMapping("/{reservationId}")
public ResponseEntity<CancelReservationResponse> cancelReservation(
@LogIn Member member,
@PathVariable("reservationId") Long reservationId) {
return ResponseEntity.ok(memberReservationService.cancelReservation(reservationId));
return ResponseEntity.ok(memberReservationService.cancelReservation(member, reservationId));
}

@GetMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import static com.prgrms.catchtable.common.exception.ErrorCode.ALREADY_OCCUPIED_RESERVATION_TIME;
import static com.prgrms.catchtable.common.exception.ErrorCode.ALREADY_PREOCCUPIED_RESERVATION_TIME;
import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_OWNER;
import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_RESERVATION;
import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_TIME;
import static com.prgrms.catchtable.common.notification.NotificationContent.RESERVATION_CANCELLED;
import static com.prgrms.catchtable.common.notification.NotificationContent.RESERVATION_COMPLETED;
import static com.prgrms.catchtable.reservation.domain.ReservationStatus.CANCELLED;
import static com.prgrms.catchtable.reservation.domain.ReservationStatus.COMPLETED;
import static com.prgrms.catchtable.reservation.dto.mapper.ReservationMapper.toCancelReservationResponse;
Expand All @@ -13,7 +16,12 @@

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import com.prgrms.catchtable.common.exception.custom.NotFoundCustomException;
import com.prgrms.catchtable.common.notification.NotificationContent;
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.notification.dto.request.SendMessageToMemberRequest;
import com.prgrms.catchtable.notification.dto.request.SendMessageToOwnerRequest;
import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.owner.repository.OwnerRepository;
import com.prgrms.catchtable.reservation.domain.Reservation;
import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.mapper.ReservationMapper;
Expand All @@ -29,6 +37,7 @@
import com.prgrms.catchtable.shop.domain.Shop;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -40,6 +49,8 @@ public class MemberReservationService {
private final ReservationRepository reservationRepository;
private final ReservationAsync reservationAsync;
private final ReservationLockRepository reservationLockRepository;
private final OwnerRepository ownerRepository;
private final ApplicationEventPublisher publisher;

@Transactional
public CreateReservationResponse preOccupyReservation(Member member,
Expand All @@ -52,7 +63,8 @@ public CreateReservationResponse preOccupyReservation(Member member,
Thread.currentThread().interrupt();
}
}
ReservationTime reservationTime = reservationTimeRepository.findById(reservationTimeId)
ReservationTime reservationTime = reservationTimeRepository.findByIdWithShop(
reservationTimeId)
.orElseThrow(() -> {
reservationLockRepository.unlock(reservationTimeId);
return new NotFoundCustomException(NOT_EXIST_TIME);
Expand Down Expand Up @@ -91,6 +103,10 @@ public CreateReservationResponse registerReservation(Member member,
.member(member)
.build();
Reservation savedReservation = reservationRepository.save(reservation);

sendMessageToMemberAndOwner(member, reservationTime,
RESERVATION_COMPLETED); //점주와 회원에게 알림 발송

return toCreateReservationResponse(savedReservation);
}

Expand Down Expand Up @@ -129,7 +145,7 @@ public ModifyReservationResponse modifyReservation(Long reservavtionId,
}

@Transactional
public CancelReservationResponse cancelReservation(Long reservationId) {
public CancelReservationResponse cancelReservation(Member member, Long reservationId) {
Reservation reservation = reservationRepository.findByIdWithReservationTimeAndShop(
reservationId)
.orElseThrow(() -> new NotFoundCustomException(NOT_EXIST_RESERVATION));
Expand All @@ -140,9 +156,30 @@ public CancelReservationResponse cancelReservation(Long reservationId) {

reservationTime.setOccupiedFalse();

sendMessageToMemberAndOwner(member, reservationTime, RESERVATION_CANCELLED);

return toCancelReservationResponse(reservation);
}

private void sendMessageToMemberAndOwner(Member member, ReservationTime reservationTime,
NotificationContent content) {

Owner owner = ownerRepository.findOwnerByShop(reservationTime.getShop())
.orElseThrow(() -> new NotFoundCustomException(NOT_EXIST_OWNER));

SendMessageToMemberRequest sendMessageToMember = new SendMessageToMemberRequest(
member,
content.getMessage(reservationTime.getTime().toString())
); // 회원에게 보낼 해당 시간대의 예약 완료 알림 생성

SendMessageToOwnerRequest sendMessageToOwner = new SendMessageToOwnerRequest(owner,
content.getMessage(
reservationTime.getTime().toString())); // 해당 시간의 예약 취소 메세지 dto 생성

publisher.publishEvent(sendMessageToMember);
publisher.publishEvent(sendMessageToOwner); // 취소한 예약의 매장 점주에게 예약 취소 알림 발송
}

private void validateIsPreOccupied(ReservationTime reservationTime) {
if (reservationTime.isPreOccupied()) {
reservationLockRepository.unlock(reservationTime.getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.prgrms.catchtable.owner.repository;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;

import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.owner.fixture.OwnerFixture;
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.shop.fixture.ShopFixture;
import com.prgrms.catchtable.shop.repository.ShopRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
class OwnerRepositoryTest {

@Autowired
private OwnerRepository ownerRepository;
@Autowired
private ShopRepository shopRepository;

@Test
@DisplayName("매장을 통해 점주를 찾을 수 있다")
void findByShop() {
Shop shop = ShopFixture.shop();
Shop savedShop = shopRepository.save(shop);

Owner owner = OwnerFixture.getOwner("injun", "injun2480");
owner.insertShop(savedShop);
Owner savedOwner = ownerRepository.save(owner);

Owner findOwner = ownerRepository.findOwnerByShop(savedShop).orElseThrow();

assertThat(findOwner).isEqualTo(savedOwner);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import com.prgrms.catchtable.member.MemberFixture;
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.member.repository.MemberRepository;
import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.owner.fixture.OwnerFixture;
import com.prgrms.catchtable.owner.repository.OwnerRepository;
import com.prgrms.catchtable.reservation.domain.Reservation;
import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
Expand All @@ -28,6 +31,7 @@
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.shop.repository.ShopRepository;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -47,13 +51,18 @@ class MemberReservationControllerTest extends BaseIntegrationTest {
private ReservationRepository reservationRepository;
@Autowired
private MemberRepository memberRepository;
@Autowired
private OwnerRepository ownerRepository;
private Member member = MemberFixture.member("dlswns661035@gmail.com");

@BeforeEach
void setUp() {
Shop shop = ShopData.getShop();
Shop savedShop = shopRepository.save(shop);

Owner owner = OwnerFixture.getOwner("injun", "injun2480");
owner.insertShop(savedShop);
ownerRepository.save(owner);

Member savedMember = memberRepository.save(member);

Expand All @@ -66,6 +75,11 @@ void setUp() {
httpHeaders.add("RefreshToken", token.getRefreshToken());
}

@AfterEach
void tearDown() {
shopRepository.deleteAll();
}

@Test
@DisplayName("예약 선점 api 호출에 성공한다.")
void preOccupyReservation() throws Exception {
Expand Down Expand Up @@ -193,6 +207,7 @@ void cancelReservation() throws Exception {
Reservation savedReservation = reservationRepository.save(reservation);

mockMvc.perform(delete("/reservations/{reservationId}", savedReservation.getId())
.headers(httpHeaders)
.contentType(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(CANCELLED.toString()));
Expand Down
Loading

0 comments on commit 1ae2f38

Please sign in to comment.