Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/main/java/earlybird/earlybird/email/SendEmailService.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package earlybird.earlybird.email;

import static earlybird.earlybird.promotion.email.entity.PromotionEmailMessageType.BERKELEY_6_MONTH_FREE;

import earlybird.earlybird.common.util.LocalDateTimeUtil;
import earlybird.earlybird.promotion.email.entity.PromotionEmailMessageType;
import earlybird.earlybird.promotion.email.entity.PromotionEmailVerification;

import jakarta.mail.Message;
import jakarta.mail.internet.MimeMessage;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import static earlybird.earlybird.promotion.email.entity.PromotionEmailMessageType.BERKELEY_6_MONTH_FREE;

@Slf4j
@RequiredArgsConstructor
@Service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Builder
@AllArgsConstructor
@Getter
@NoArgsConstructor
public class CreatePromotionCampaignRequest {
Expand All @@ -34,9 +38,5 @@ public class CreatePromotionCampaignRequest {
timezone = "Asia/Seoul")
private LocalDateTime endTime;

@NotNull private Long perUserLimit;

@NotNull private Long totalIssueLimit;

@NotNull private PromotionCampaignType promotionCampaignType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class CreatePromotionCampaignResponse {
public static CreatePromotionCampaignResponse from(
CreatePromotionCampaignServiceResponse response) {
return CreatePromotionCampaignResponse.builder()
.promotionCampaignId(response.getPromotionCampaignId())
.promotionCampaignId(response.promotionCampaignId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package earlybird.earlybird.promotion.email.repository;

import earlybird.earlybird.promotion.email.entity.PromotionEmailVerification;
import earlybird.earlybird.promotion.entity.PromotionCampaign;
import earlybird.earlybird.promotion.entity.PromotionUrlUuid;

import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -11,4 +12,7 @@ public interface PromotionEmailVerificationRepository
extends JpaRepository<PromotionEmailVerification, Long> {

Optional<PromotionEmailVerification> findByPromotionUrlUuid(PromotionUrlUuid promotionUrlUuid);

Optional<PromotionEmailVerification> findByPromotionCampaignAndEmail(
PromotionCampaign promotionCampaign, String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,29 @@ public class SendPromotionEmailService {

@Transactional
public void sendVerificationEmail(SendVerificationEmailServiceRequest request) {
/**
* TODO - 이메일 유효성 검사 (완) - URL을 구성할 UUID 발급 (완) - DB에서 애플 프로모션 URL 하나 조회 - 락을 걸었는데 이거 성능 문제
* 좀 더 고민 필요 - 이메일로 인증 URL 전송 (여기에 UUID가 들어감) - 비동기 처리 필요 (완) - [email protected]
* 이메일 적용 필요 - 인증 URL을 클릭하면 애플 프로모션 URL로 리다이렉션 (완) - 엣지 케이스 좀 더 고민 필요 - 스프링 트랜잭션에 대한 고민 필요 -
* Retry 로직 도입 고려 (완) - 메일 발송 부분 로직 문제 없나 다시 체크
*/
/** TODO - 락을 걸었는데 이거 성능 문제 좀 더 고민 필요 - 스프링 트랜잭션에 대한 고민 필요 - 메일 발송 부분 로직 문제 없나 다시 체크 */
checkEmailAddress(request);

PromotionCampaign promotionCampaign =
getPromotionCampaignService.findById(request.getPromotionCampaignId());

promotionEmailVerificationRepository
.findByPromotionCampaignAndEmail(promotionCampaign, request.getEmail())
.ifPresentOrElse(
verification -> sendEmailIfSendBefore(verification, request),
() -> sendFirstEmail(promotionCampaign, request));
}

// 이전에 동일한 캠패인, 동일한 이메일로 인증 메일을 전송한 적이 있으면 이 함수 호출
private void sendEmailIfSendBefore(
PromotionEmailVerification promotionEmailVerification,
SendVerificationEmailServiceRequest request) {
sendEmailService.send(promotionEmailVerification, request.getPromotionEmailMessageType());
}

// 처음 인증 요청하는 건 이 함수 호출
private void sendFirstEmail(
PromotionCampaign promotionCampaign, SendVerificationEmailServiceRequest request) {
PromotionUrlUuid promotionUrlUuid = createPromotionUrlUuidService.create();
String promotionUrl =
getApplePromotionUrlService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ public class PromotionCampaign extends BaseTimeEntity {
@Column(name = "promotion_campaigns_ends_time", nullable = false)
private LocalDateTime endTime;

// // 인당 발급 허용 개수
// @Column(name = "promotion_campaigns_per_user_limit", nullable = false)
// private Long perUserLimit;
//
// // 캠페인 전체 발급 최대치 (없으면 NULL)
// @Column(name = "promotion_campaigns_total_issue_limit")
// private Long totalIssueLimit;

// 캠페인 타입
@Enumerated(EnumType.STRING)
@Column(name = "promotion_campaigns_type", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ public CreatePromotionCampaignServiceResponse create(
private PromotionCampaign createPromotionCampaign(
CreatePromotionCampaignServiceRequest request) {
return PromotionCampaign.builder()
.name(request.getName())
.description(request.getDescription())
.startTime(request.getStartTime())
.endTime(request.getEndTime())
// .perUserLimit(request.getPerUserLimit())
// .totalIssueLimit(request.getTotalIssueLimit())
.promotionCampaignType(request.getPromotionCampaignType())
.name(request.name())
.description(request.description())
.startTime(request.startTime())
.endTime(request.endTime())
.promotionCampaignType(request.promotionCampaignType())
.active(true)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,16 @@
import earlybird.earlybird.promotion.entity.PromotionCampaignType;

import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.time.LocalDateTime;

@Builder
@RequiredArgsConstructor
@Getter
public class CreatePromotionCampaignServiceRequest {
private final String name;
private final String description;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
private final Long perUserLimit;
private final Long totalIssueLimit;
private final PromotionCampaignType promotionCampaignType;
public record CreatePromotionCampaignServiceRequest(
String name,
String description,
LocalDateTime startTime,
LocalDateTime endTime,
PromotionCampaignType promotionCampaignType) {

public static CreatePromotionCampaignServiceRequest from(
CreatePromotionCampaignRequest request) {
Expand All @@ -28,8 +22,6 @@ public static CreatePromotionCampaignServiceRequest from(
.description(request.getPromotionCampaignDescription())
.startTime(request.getStartTime())
.endTime(request.getEndTime())
.perUserLimit(request.getPerUserLimit())
.totalIssueLimit(request.getTotalIssueLimit())
.promotionCampaignType(request.getPromotionCampaignType())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@
import earlybird.earlybird.promotion.entity.PromotionCampaign;

import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Builder
@Getter
@RequiredArgsConstructor
public class CreatePromotionCampaignServiceResponse {
private final Long PromotionCampaignId;
public record CreatePromotionCampaignServiceResponse(Long promotionCampaignId) {

public static CreatePromotionCampaignServiceResponse from(PromotionCampaign promotionCampaign) {
return CreatePromotionCampaignServiceResponse.builder()
.PromotionCampaignId(promotionCampaign.getId())
.promotionCampaignId(promotionCampaign.getId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package earlybird.earlybird.promotion.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import earlybird.earlybird.promotion.entity.PromotionCampaign;
import earlybird.earlybird.promotion.entity.PromotionCampaignType;
import earlybird.earlybird.promotion.repository.PromotionCampaignRepository;
import earlybird.earlybird.promotion.service.request.CreatePromotionCampaignServiceRequest;
import earlybird.earlybird.promotion.service.response.CreatePromotionCampaignServiceResponse;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDateTime;

@ExtendWith(MockitoExtension.class)
class CreatePromotionCampaignServiceTest {

@InjectMocks CreatePromotionCampaignService createPromotionCampaignService;

@Mock PromotionCampaignRepository promotionCampaignRepository;

@DisplayName("프로모션 캠페인을 생성하고 저장한다")
@Test
void create() {
// given
CreatePromotionCampaignServiceRequest request =
CreatePromotionCampaignServiceRequest.builder()
.name("테스트 캠페인")
.description("테스트 캠페인 설명")
.startTime(LocalDateTime.of(2024, 1, 1, 0, 0))
.endTime(LocalDateTime.of(2024, 12, 31, 23, 59))
.promotionCampaignType(PromotionCampaignType.EDU_EMAIL_6_MONTH_FREE)
.build();

PromotionCampaign savedCampaign =
PromotionCampaign.builder()
.id(1L)
.name("테스트 캠페인")
.description("테스트 캠페인 설명")
.startTime(LocalDateTime.of(2024, 1, 1, 0, 0))
.endTime(LocalDateTime.of(2024, 12, 31, 23, 59))
.promotionCampaignType(PromotionCampaignType.EDU_EMAIL_6_MONTH_FREE)
.active(true)
.build();

when(promotionCampaignRepository.save(any(PromotionCampaign.class)))
.thenReturn(savedCampaign);

// when
CreatePromotionCampaignServiceResponse response =
createPromotionCampaignService.create(request);

// then
assertThat(response).isNotNull();
assertThat(response.promotionCampaignId()).isEqualTo(1L);
verify(promotionCampaignRepository).save(any(PromotionCampaign.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package earlybird.earlybird.promotion.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import earlybird.earlybird.promotion.entity.PromotionUrlUuid;
import earlybird.earlybird.promotion.repository.PromotionUrlUuidRepository;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.UUID;

@ExtendWith(MockitoExtension.class)
class CreatePromotionUrlUuidServiceTest {

@InjectMocks CreatePromotionUrlUuidService createPromotionUrlUuidService;

@Mock PromotionUrlUuidRepository promotionUrlUuidRepository;

@DisplayName("UUID를 생성하고 저장한다")
@Test
void create() {
// given
PromotionUrlUuid savedEntity = PromotionUrlUuid.builder().uuid(UUID.randomUUID()).build();

when(promotionUrlUuidRepository.save(any(PromotionUrlUuid.class))).thenReturn(savedEntity);

// when
PromotionUrlUuid result = createPromotionUrlUuidService.create();

// then
assertThat(result).isNotNull();
assertThat(result.getUuid()).isNotNull();
verify(promotionUrlUuidRepository).save(any(PromotionUrlUuid.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package earlybird.earlybird.promotion.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import earlybird.earlybird.promotion.entity.PromotionCampaign;
import earlybird.earlybird.promotion.repository.PromotionCampaignRepository;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.NoSuchElementException;
import java.util.Optional;

@ExtendWith(MockitoExtension.class)
class GetPromotionCampaignServiceTest {

@InjectMocks private GetPromotionCampaignService getPromotionCampaignService;

@Mock private PromotionCampaignRepository promotionCampaignRepository;

@DisplayName("프로모션 캠페인 ID가 주어지면 해당하는 프로모션 캠페인 객체를 반환한다.")
@Test
void findById() {
// given
Long promotionCampaignId = 1L;
PromotionCampaign promotionCampaign =
PromotionCampaign.builder().id(promotionCampaignId).build();
when(promotionCampaignRepository.findById(promotionCampaignId))
.thenReturn(Optional.of(promotionCampaign));

// when
PromotionCampaign result = getPromotionCampaignService.findById(promotionCampaignId);

// then
assertThat(result.getId()).isEqualTo(promotionCampaignId);
assertThat(result).isEqualTo(promotionCampaign);
}

@DisplayName("주어진 프로모션 캠페인 ID에 해당하는 프로모션 캠페인 객체가 없으면 예외를 던진다.")
@Test
void findByIdThrowException() {
// given
Long promotionCampaignId = 1L;
when(promotionCampaignRepository.findById(promotionCampaignId))
.thenReturn(Optional.empty());

// when // then
assertThatThrownBy(() -> getPromotionCampaignService.findById(promotionCampaignId))
.isInstanceOf(NoSuchElementException.class);
}
}
Loading