Skip to content

Commit

Permalink
Merge pull request #45 from Leets-Official/feat/#42
Browse files Browse the repository at this point in the history
[feat] #42 사전알림 기능 구현
  • Loading branch information
jwnnoh authored Dec 5, 2024
2 parents 020fbd9 + 2b35562 commit e35bd98
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package land.leets.domain.mail.controller;

import jakarta.validation.Valid;
import land.leets.domain.mail.controller.dto.RecruitMailRequest;
import land.leets.domain.mail.usecase.SubscribeRecruitMail;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -19,6 +23,7 @@
public class MailController {

private final SendMailCron sendMailCron;
private final SubscribeRecruitMail subscribeRecruitMail;

@Operation(summary = "서류 결과 메일 전송", description = "서류 지원 결과를 메일로 전송합니다.")
@ApiResponses({
Expand Down Expand Up @@ -47,4 +52,32 @@ public boolean sendFinalMail() {
sendMailCron.sendFinalMail();
return true;
}

@Operation(summary = "모집 알림 신청 메일 전송", description = "모집 시작 알림을 메일로 전송합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping("/recruit")
public boolean sendRecruitMail() {
sendMailCron.sendRecruitMail();
return true;
}

@Operation(summary = "모집 알림 신청 메일 등록", description = "모집 시작 알림을 받을 메일을 등록합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping("/subscribe")
public boolean subscribeRecruitMail(@Valid @RequestBody RecruitMailRequest request) {
subscribeRecruitMail.execute(request);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package land.leets.domain.mail.controller.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record RecruitMailRequest(
@Email
@NotBlank
String email
) {
}
16 changes: 16 additions & 0 deletions src/main/java/land/leets/domain/mail/domain/RecruitMail.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package land.leets.domain.mail.domain;

import jakarta.persistence.*;
import land.leets.domain.shared.BaseTimeEntity;
import lombok.*;

@Entity(name = "recruit_mails")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RecruitMail extends BaseTimeEntity {

@Id
@Column(nullable = false)
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package land.leets.domain.mail.domain.repository;

import land.leets.domain.mail.domain.RecruitMail;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface RecruitMailRepository extends JpaRepository<RecruitMail, Long> {
List<RecruitMail> findAll();

boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package land.leets.domain.mail.exception;

import land.leets.global.error.ErrorCode;
import land.leets.global.error.exception.ServiceException;

public class MailAlreadyExistsException extends ServiceException {
public MailAlreadyExistsException() {
super(ErrorCode.EMAIL_ALREADY_EXISTS);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package land.leets.domain.mail.usecase;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import land.leets.domain.application.type.ApplicationStatus;
Expand All @@ -13,6 +14,7 @@ public class SendMailCron {

private final SendFinalMailImpl sendFinalMailImpl;
private final SendPaperMailImpl sendPaperMailImpl;
private final SendRecruitMail sendRecruitMail;

// @Scheduled(cron = "0 0 23 8 9 ?")
public void sendPaperMail() {
Expand Down Expand Up @@ -43,5 +45,12 @@ public void sendFinalMail() {
public void sendPlusMail() {
log.info("Send final result mail successfully.");
}

// @Scheduled(cron = "0 0 00 ?? 3 ?")
public void sendRecruitMail() {
sendRecruitMail.execute();

log.info("Send recruit result mail successfully.");
}
}

44 changes: 44 additions & 0 deletions src/main/java/land/leets/domain/mail/usecase/SendRecruitMail.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package land.leets.domain.mail.usecase;

import land.leets.domain.mail.domain.Mail;
import land.leets.domain.mail.domain.repository.RecruitMailRepository;
import land.leets.domain.mail.service.MailManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.List;

@RequiredArgsConstructor
@Service
@Transactional
public class SendRecruitMail {

private static final String MAIL_TITLE = "[Leets] 5기 모집 시작 안내 메일입니다.";
private static final String GENERATION_FIELD = "generation";
private static final String TEMPLATE = "Recruit.html";

private final RecruitMailRepository recruitMailRepository;
private final TemplateEngine templateEngine;
private final MailManager mailManager;

public void execute() {
List<Mail> mails = recruitMailRepository.findAll().stream()
.map(recruitMail -> {
Context context = makeContext();
String message = templateEngine.process(TEMPLATE, context);
return new Mail(MAIL_TITLE, recruitMail.getEmail(), message);
})
.toList();

mailManager.sendEmails(mails);
}

private Context makeContext() {
Context context = new Context();
context.setVariable(GENERATION_FIELD, 5);
return context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package land.leets.domain.mail.usecase;

import land.leets.domain.mail.controller.dto.RecruitMailRequest;
import land.leets.domain.mail.domain.RecruitMail;
import land.leets.domain.mail.domain.repository.RecruitMailRepository;
import land.leets.domain.mail.exception.MailAlreadyExistsException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class SubscribeRecruitMail {

private final RecruitMailRepository recruitMailRepository;

public void execute(RecruitMailRequest request) {
if (recruitMailRepository.existsByEmail(request.email())) {
throw new MailAlreadyExistsException();
}
RecruitMail recruitMail = new RecruitMail(request.email());

recruitMailRepository.save(recruitMail);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
import land.leets.global.error.ErrorResponse;
import land.leets.global.error.exception.ServiceException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestCookieException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice
public class ExceptionHandleAdvice {

@ExceptionHandler(ServiceException.class)
public ResponseEntity<ErrorResponse> handleServiceException(ServiceException ex) {
ErrorResponse response = new ErrorResponse(ex.getErrorCode());
Expand All @@ -29,4 +33,18 @@ public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorResponse response = new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR);
return ResponseEntity.internalServerError().body(response);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
String fieldErrors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getField)
.collect(Collectors.joining(", "));

String customMessage = String.format(ErrorCode.INVALID_REQUEST_BODY.getMessage(), fieldErrors);
ErrorResponse response = new ErrorResponse(ErrorCode.INVALID_REQUEST_BODY, customMessage);

return ResponseEntity.status(ErrorCode.INVALID_REQUEST_BODY.getHttpStatus()).body(response);
}
}
1 change: 1 addition & 0 deletions src/main/java/land/leets/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

.requestMatchers("/comments/**").hasAuthority(AuthRole.ROLE_ADMIN.getRole())

.requestMatchers(HttpMethod.POST, "/mail/subscribe").permitAll()
.requestMatchers(HttpMethod.POST, "/mail/**").hasAuthority(AuthRole.ROLE_ADMIN.getRole())

.requestMatchers("/portfolios/**").permitAll()
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/land/leets/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
@AllArgsConstructor
public enum ErrorCode {

INVALID_REQUEST_BODY(400, "INVALID_REQUEST_BODY", "올바르지 않은 요청입니다 [%s]"),
PATCH_REQUEST_FAIL(404, "PATCH_REQUEST_FAIL", "PATCH 요청에 실패했습니다."),
INTERVIEW_NOT_FOUND(404, "INTERVIEW_NOT_FOUND", "면접 정보를 찾을 수 없습니다."),
APPLICATION_ALREADY_EXISTS(409, "APPLICATION_ALREADY_EXISTS", "이미 지원한 지원자입니다."),
EMAIL_ALREADY_EXISTS(409, "EMAIL_ALREADY_EXISTS", "이미 등록한 이메일입니다."),
PERMISSION_DENIED(403, "PERMISSION_DENIED", "권한이 없습니다."),
APPLICATION_NOT_FOUND(404, "APPLICATION_NOT_FOUND", "신청서를 찾을 수 없습니다."),
PASSWORD_NOT_MATCH(403, "PASSWORD_NOT_MATCH", "비밀번호가 일치하지 않습니다."),
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/land/leets/global/error/ErrorResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public ErrorResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
}

public ErrorResponse(ErrorCode errorCode, String customMessage) {
this.httpStatus = errorCode.getHttpStatus();
this.message = customMessage;
this.code = errorCode.getCode();
}

public static ErrorResponse of(ErrorCode errorCode) {
return new ErrorResponse(errorCode);
}
Expand Down
Loading

0 comments on commit e35bd98

Please sign in to comment.