diff --git a/src/main/java/ceos/backend/domain/subscriber/SubscriberController.java b/src/main/java/ceos/backend/domain/subscriber/SubscriberController.java new file mode 100644 index 00000000..1f77fe78 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/SubscriberController.java @@ -0,0 +1,35 @@ +package ceos.backend.domain.subscriber; + + +import ceos.backend.domain.subscriber.dto.request.SubscribeRequest; +import ceos.backend.domain.subscriber.service.SubscriberService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/subscribe") +@Tag(name = "Subscriber") +public class SubscriberController { + + private final SubscriberService subscriberService; + + @Operation(summary = "메일링 서비스 구독") + @PostMapping + public void subscribeMail(@RequestBody @Valid SubscribeRequest subscribeRequest) { + log.info("메일링 서비스 구독"); + subscriberService.subscribeMail(subscribeRequest); + } + + @Operation(summary = "슈퍼유저 - 메일링 서비스") + @GetMapping("/mail") + public void setRecruitingMail() { + log.info("슈퍼유저 - 메일링 서비스"); + subscriberService.sendRecruitingMail(); + } +} diff --git a/src/main/java/ceos/backend/domain/subscriber/domain/Subscriber.java b/src/main/java/ceos/backend/domain/subscriber/domain/Subscriber.java new file mode 100644 index 00000000..6b466de5 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/domain/Subscriber.java @@ -0,0 +1,37 @@ +package ceos.backend.domain.subscriber.domain; + + +import ceos.backend.global.common.entity.BaseEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Subscriber extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "subscriber_id") + private Long id; + + @NotNull + @Size(max = 255) + private String email; + + // 생성자 + @Builder + private Subscriber(String email) { + this.email = email; + } + + public static Subscriber from(String email) { + return Subscriber.builder() + .email(email) + .build(); + } +} diff --git a/src/main/java/ceos/backend/domain/subscriber/dto/request/SubscribeRequest.java b/src/main/java/ceos/backend/domain/subscriber/dto/request/SubscribeRequest.java new file mode 100644 index 00000000..0c9fb0e9 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/dto/request/SubscribeRequest.java @@ -0,0 +1,14 @@ +package ceos.backend.domain.subscriber.dto.request; + + +import ceos.backend.global.common.annotation.ValidEmail; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class SubscribeRequest { + + @Schema(defaultValue = "ceos@ceos-sinchon.com", description = "이메일") + @ValidEmail + private String email; +} diff --git a/src/main/java/ceos/backend/domain/subscriber/exception/DuplicateData.java b/src/main/java/ceos/backend/domain/subscriber/exception/DuplicateData.java new file mode 100644 index 00000000..633f9c19 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/exception/DuplicateData.java @@ -0,0 +1,13 @@ +package ceos.backend.domain.subscriber.exception; + + +import ceos.backend.global.error.BaseErrorException; + +public class DuplicateData extends BaseErrorException { + + public static final DuplicateData EXCEPTION = new DuplicateData(); + + private DuplicateData() { + super(SubscriberErrorCode.DUPLICATE_DATA); + } +} diff --git a/src/main/java/ceos/backend/domain/subscriber/exception/InvalidAction.java b/src/main/java/ceos/backend/domain/subscriber/exception/InvalidAction.java new file mode 100644 index 00000000..6fbbf4b6 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/exception/InvalidAction.java @@ -0,0 +1,13 @@ +package ceos.backend.domain.subscriber.exception; + + +import ceos.backend.global.error.BaseErrorException; + +public class InvalidAction extends BaseErrorException { + + public static final InvalidAction EXCEPTION = new InvalidAction(); + + private InvalidAction() { + super(SubscriberErrorCode.INVALID_ACTION); + } +} diff --git a/src/main/java/ceos/backend/domain/subscriber/exception/SubscriberErrorCode.java b/src/main/java/ceos/backend/domain/subscriber/exception/SubscriberErrorCode.java new file mode 100644 index 00000000..06ada2e6 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/exception/SubscriberErrorCode.java @@ -0,0 +1,27 @@ +package ceos.backend.domain.subscriber.exception; + +import ceos.backend.global.common.dto.ErrorReason; +import ceos.backend.global.error.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@Getter +@AllArgsConstructor +public enum SubscriberErrorCode implements BaseErrorCode { + + /* Data */ + INVALID_ACTION(BAD_REQUEST, "SUBSCRIBER_400_1", "리쿠르팅 시작 전입니다."), + DUPLICATE_DATA(CONFLICT, "SUBSCRIBER_409_1", "이미 존재하는 데이터입니다"); + + private HttpStatus status; + private String code; + private String reason; + + @Override + public ErrorReason getErrorReason() { + return ErrorReason.of(status.value(), code, reason); + } +} diff --git a/src/main/java/ceos/backend/domain/subscriber/helper/SubscriberHelper.java b/src/main/java/ceos/backend/domain/subscriber/helper/SubscriberHelper.java new file mode 100644 index 00000000..fbfa706b --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/helper/SubscriberHelper.java @@ -0,0 +1,35 @@ +package ceos.backend.domain.subscriber.helper; + +import ceos.backend.domain.subscriber.exception.DuplicateData; +import ceos.backend.domain.subscriber.exception.InvalidAction; +import ceos.backend.domain.subscriber.repository.SubscriberRepository; +import ceos.backend.global.common.dto.AwsSESRecruitMail; +import ceos.backend.global.common.event.Event; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Component +@RequiredArgsConstructor +public class SubscriberHelper { + + private final SubscriberRepository subscriberRepository; + + public void validateEmail(String email) { + if (subscriberRepository.findByEmail(email).isPresent()) { + throw DuplicateData.EXCEPTION; + } + } + + public void validateDate(LocalDate date, LocalDate now) { + if (!date.equals(now)) { + throw InvalidAction.EXCEPTION; + } + } + + public void sendRecruitEmail(String email) { + Event.raise(AwsSESRecruitMail.from(email)); + } + +} diff --git a/src/main/java/ceos/backend/domain/subscriber/repository/SubscriberRepository.java b/src/main/java/ceos/backend/domain/subscriber/repository/SubscriberRepository.java new file mode 100644 index 00000000..9c6c1cd0 --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/repository/SubscriberRepository.java @@ -0,0 +1,11 @@ +package ceos.backend.domain.subscriber.repository; + + +import ceos.backend.domain.subscriber.domain.Subscriber; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SubscriberRepository extends JpaRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/ceos/backend/domain/subscriber/service/SubscriberService.java b/src/main/java/ceos/backend/domain/subscriber/service/SubscriberService.java new file mode 100644 index 00000000..9e03fbcc --- /dev/null +++ b/src/main/java/ceos/backend/domain/subscriber/service/SubscriberService.java @@ -0,0 +1,50 @@ +package ceos.backend.domain.subscriber.service; + + +import ceos.backend.domain.recruitment.repository.RecruitmentRepository; +import ceos.backend.domain.subscriber.domain.Subscriber; +import ceos.backend.domain.subscriber.dto.request.SubscribeRequest; +import ceos.backend.domain.subscriber.helper.SubscriberHelper; +import ceos.backend.domain.subscriber.repository.SubscriberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SubscriberService { + + private final SubscriberHelper subscriberHelper; + private final RecruitmentRepository recruitmentRepository; + private final SubscriberRepository subscriberRepository; + + @Transactional + public void subscribeMail(SubscribeRequest subscribeRequest) { + + //이메일 중복 검증 + subscriberHelper.validateEmail(subscribeRequest.getEmail()); + + Subscriber subscriber = Subscriber.from(subscribeRequest.getEmail()); + subscriberRepository.save(subscriber); + } + + @Transactional(readOnly = true) + public void sendRecruitingMail() { + LocalDate date = recruitmentRepository.findAll().get(0).getStartDateDoc().toLocalDate(); + LocalDate now = LocalDate.now(); + List subscribers = subscriberRepository.findAll(); + + //리쿠르팅 시작 날짜 검증 + subscriberHelper.validateDate(date, now); + + // 메일 보내기 + for (Subscriber subscriber : subscribers) { + subscriberHelper.sendRecruitEmail(subscriber.getEmail()); + } + } +} diff --git a/src/main/java/ceos/backend/global/common/dto/AwsSESRecruitMail.java b/src/main/java/ceos/backend/global/common/dto/AwsSESRecruitMail.java new file mode 100644 index 00000000..406a32da --- /dev/null +++ b/src/main/java/ceos/backend/global/common/dto/AwsSESRecruitMail.java @@ -0,0 +1,21 @@ +package ceos.backend.global.common.dto; + + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class AwsSESRecruitMail { + //수정 예정 -> 이름을 받을 것인가 말 것인가... + private String email; + + @Builder + private AwsSESRecruitMail(String email) { + this.email = email; + } + + public static AwsSESRecruitMail from(String email) { + return AwsSESRecruitMail.builder().email(email) + .build(); + } +} diff --git a/src/main/java/ceos/backend/global/common/dto/mail/EmailInfo.java b/src/main/java/ceos/backend/global/common/dto/mail/EmailInfo.java new file mode 100644 index 00000000..66b5b8ab --- /dev/null +++ b/src/main/java/ceos/backend/global/common/dto/mail/EmailInfo.java @@ -0,0 +1,22 @@ +package ceos.backend.global.common.dto.mail; + + +import ceos.backend.global.common.dto.AwsSESRecruitMail; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class EmailInfo { + private String email; + + @Builder + private EmailInfo(String email) { + this.email = email; + } + + public static EmailInfo from(AwsSESRecruitMail awsSESRecruitMail) { + return EmailInfo.builder() + .email(awsSESRecruitMail.getEmail()) + .build(); + } +} diff --git a/src/main/java/ceos/backend/global/config/WebSecurityConfig.java b/src/main/java/ceos/backend/global/config/WebSecurityConfig.java index 725e7df7..f9948be1 100644 --- a/src/main/java/ceos/backend/global/config/WebSecurityConfig.java +++ b/src/main/java/ceos/backend/global/config/WebSecurityConfig.java @@ -103,7 +103,7 @@ public class WebSecurityConfig { "/applications/interview", "/applications/pass" }; - private final String[] RootPatterns = {"/admin/super"}; + private final String[] RootPatterns = {"/admin/super", "/subscribe/mail"}; @Bean public UserDetailsService userDetailsService() { diff --git a/src/main/java/ceos/backend/infra/ses/AwsSESMailGenerator.java b/src/main/java/ceos/backend/infra/ses/AwsSESMailGenerator.java index 904157f7..35e873f9 100644 --- a/src/main/java/ceos/backend/infra/ses/AwsSESMailGenerator.java +++ b/src/main/java/ceos/backend/infra/ses/AwsSESMailGenerator.java @@ -8,6 +8,7 @@ import ceos.backend.domain.application.vo.AnswerVo; import ceos.backend.global.common.dto.AwsSESMail; import ceos.backend.global.common.dto.AwsSESPasswordMail; +import ceos.backend.global.common.dto.AwsSESRecruitMail; import ceos.backend.global.common.dto.ParsedDuration; import ceos.backend.global.common.dto.mail.*; import ceos.backend.global.common.entity.Part; @@ -128,4 +129,16 @@ public Context generatePasswordMailContext(AwsSESPasswordMail awsSESPasswordMail public String generatePasswordMailSubject() { return "세오스 관리자 페이지 임시 비밀번호 발급"; } + + public Context generateRecruitMailContext(AwsSESRecruitMail awsSESRecruitMail) { + Context context = new Context(); + context.setVariable("email", EmailInfo.from(awsSESRecruitMail)); + + return context; + } + + // 수정 예정 + public String generateRecruitMailSubject() { + return "세오스 리쿠르팅 메일 발송"; + } } diff --git a/src/main/java/ceos/backend/infra/ses/AwsSESSendMailHandler.java b/src/main/java/ceos/backend/infra/ses/AwsSESSendMailHandler.java index f9fbb472..d6d56b02 100644 --- a/src/main/java/ceos/backend/infra/ses/AwsSESSendMailHandler.java +++ b/src/main/java/ceos/backend/infra/ses/AwsSESSendMailHandler.java @@ -4,6 +4,7 @@ import ceos.backend.domain.application.dto.request.CreateApplicationRequest; import ceos.backend.global.common.dto.AwsSESMail; import ceos.backend.global.common.dto.AwsSESPasswordMail; +import ceos.backend.global.common.dto.AwsSESRecruitMail; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; @@ -35,4 +36,12 @@ public void handle(AwsSESPasswordMail awsSESPasswordMail) { final Context CONTEXT = awsSESMailGenerator.generatePasswordMailContext(awsSESPasswordMail); awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendPasswordMail", CONTEXT); } + + @EventListener(AwsSESRecruitMail.class) + public void handle(AwsSESRecruitMail awsSESRecruitMail) { + final String TO = awsSESRecruitMail.getEmail(); + final String SUBJECT = awsSESMailGenerator.generateRecruitMailSubject(); + final Context CONTEXT = awsSESMailGenerator.generateRecruitMailContext(awsSESRecruitMail); + awsSesUtils.singleEmailRequest(TO, SUBJECT, "sendRecruitMail", CONTEXT); + } } diff --git a/src/main/resources/templates/component/copyright.html b/src/main/resources/templates/component/copyright.html index f96c442f..b29553ce 100644 --- a/src/main/resources/templates/component/copyright.html +++ b/src/main/resources/templates/component/copyright.html @@ -21,7 +21,7 @@ font-size: 20px; line-height: 150%; color: #D6DADF;"> - © 2016-2023 Ceos ALL RIGHTS RESERVED. + © 2016-2024 Ceos ALL RIGHTS RESERVED. diff --git a/src/main/resources/templates/component/recruit.html b/src/main/resources/templates/component/recruit.html new file mode 100644 index 00000000..c818a4af --- /dev/null +++ b/src/main/resources/templates/component/recruit.html @@ -0,0 +1,30 @@ + + + + + +
+
+ + 세오스 리루르팅 시작 알림 + 텍스트추가텍스트추가 + + +
+
+
+ \ No newline at end of file diff --git a/src/main/resources/templates/component/recruitButton.html b/src/main/resources/templates/component/recruitButton.html new file mode 100644 index 00000000..7ad90b89 --- /dev/null +++ b/src/main/resources/templates/component/recruitButton.html @@ -0,0 +1,35 @@ + + + + + +
+
+ + + 지원하기 + + +
+
+ diff --git a/src/main/resources/templates/sendRecruitMail.html b/src/main/resources/templates/sendRecruitMail.html new file mode 100644 index 00000000..f2cc9498 --- /dev/null +++ b/src/main/resources/templates/sendRecruitMail.html @@ -0,0 +1,43 @@ + + + + + + + + + + +
+
title
+
divider
+
+
recruit
+
divider
+
+
+ button +
+
+
copyright
+
+ +