Skip to content
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package earlybird.earlybird.promotion.service.request;

import static earlybird.earlybird.promotion.entity.PromotionCampaignType.EDU_EMAIL_6_MONTH_FREE;

import static org.assertj.core.api.Assertions.assertThat;

import earlybird.earlybird.promotion.controller.request.CreatePromotionCampaignRequest;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;

class CreatePromotionCampaignServiceRequestTest {

@DisplayName(
"CreatePromotionCampaignRequest 객체로부터 CreatePromotionCampaignServiceRequest 객체를 만든다.")
@Test
void from() {
// given
String promotionCampaignName = "promotionCampaignName";
String promotionCampaignDescription = "promotionCampaignDescription";
LocalDateTime localDateTime = LocalDateTime.of(2025, 8, 4, 0, 0, 0);
CreatePromotionCampaignRequest request =
CreatePromotionCampaignRequest.builder()
.promotionCampaignName(promotionCampaignName)
.promotionCampaignDescription(promotionCampaignDescription)
.startTime(localDateTime)
.endTime(localDateTime)
.promotionCampaignType(EDU_EMAIL_6_MONTH_FREE)
.build();

// when
CreatePromotionCampaignServiceRequest result =
CreatePromotionCampaignServiceRequest.from(request);

// then
assertThat(result).isNotNull();
assertThat(result.name()).isEqualTo(promotionCampaignName);
assertThat(result.description()).isEqualTo(promotionCampaignDescription);
assertThat(result.startTime()).isEqualTo(localDateTime);
assertThat(result.endTime()).isEqualTo(localDateTime);
assertThat(result.promotionCampaignType()).isEqualTo(EDU_EMAIL_6_MONTH_FREE);
}
}
Loading