From 58648bf955af5e54905caf19ddcc2aaf4543219a Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 12 Feb 2025 11:36:50 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20google=20API=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++ .../google/controller/GoogleController.java | 37 +++++++++++ .../google/service/GoogleApiService.java | 63 +++++++++++++++++++ .../resources/application-credentials.yml | 3 + 4 files changed, 107 insertions(+) create mode 100644 src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java create mode 100644 src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java diff --git a/build.gradle b/build.gradle index 603bf06..d2c1c8e 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,10 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'com.google.apis:google-api-services-sheets:v4-rev516-1.23.0' + implementation 'com.google.auth:google-auth-library-oauth2-http:0.20.0' + + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' diff --git a/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java b/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java new file mode 100644 index 0000000..a1577a5 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java @@ -0,0 +1,37 @@ +package dmu.dasom.api.domain.google.controller; + +import dmu.dasom.api.domain.google.service.GoogleApiService; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; + +@RestController +@RequestMapping("/google") +@RequiredArgsConstructor +public class GoogleController { + + private final GoogleApiService googleApiService; + + private static final String SPREADSHEET_ID = "1Vpu6_2raNvJN_GGg7aGBmzV4cXG1rCoizHl9v7kbG2o"; + private static final String RANGE = "A1"; + + @PostMapping("/write") + public ResponseEntity writeToSheet(@RequestParam String word){ + try{ + List> values = List.of(Collections.singletonList(word)); + + googleApiService.writeToSheet(SPREADSHEET_ID, RANGE, values); + return ResponseEntity.ok("Data written successfully to the spreadsheet" + word); + } catch (Exception e){ + e.printStackTrace(); + return ResponseEntity.internalServerError().body("Failed to write data to the spreadsheet" + e.getMessage()); + } + } +} diff --git a/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java b/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java new file mode 100644 index 0000000..3b39c61 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java @@ -0,0 +1,63 @@ +package dmu.dasom.api.domain.google.service; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.Value; +import com.google.api.services.sheets.v4.Sheets; +import com.google.api.services.sheets.v4.model.UpdateValuesResponse; +import com.google.api.services.sheets.v4.model.ValueRange; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; + +@Service +public class GoogleApiService { + + private static final Logger logger = LoggerFactory.getLogger(GoogleApiService.class); + private static final String APPLICATION_NAME = "Recruit Form"; + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + @Value("${google.credentials.file.path}") + private String credentialsFilePath; + private Sheets sheetsService; + + // 해당 메소드는 sheets의 인스턴스를 얻는데 사용 + private Sheets getSheetsService() throws IOException, GeneralSecurityException{ + if(sheetsService == null){ + GoogleCredentials credentials = GoogleCredentials + .fromStream(new ClassPathResource(credentialsFilePath).getInputStream()) + .createScoped(Collections.singletonList("https://www.googleapis.com/auth/spreadsheets")); + sheetsService = new Sheets.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, new HttpCredentialsAdapter(credentials)) + .setApplicationName(APPLICATION_NAME) + .build(); + } + return sheetsService; + } + + public void writeToSheet(String spreadsheetId, String range, List> values) { + try { + Sheets service = getSheetsService(); + ValueRange body = new ValueRange().setValues(values); + UpdateValuesResponse result = service.spreadsheets().values() + .update(spreadsheetId, range, body) + .setValueInputOption("USER_ENTERED") + .execute(); + logger.info("Updated rows: {}", result.getUpdatedRows()); + } catch (IOException e) { + logger.error("Failed to write data to the spreadsheet", e); + throw new RuntimeException("Failed to write data to the spreadsheet: " + e.getMessage(), e); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/resources/application-credentials.yml b/src/main/resources/application-credentials.yml index b63ae1a..669fbcf 100644 --- a/src/main/resources/application-credentials.yml +++ b/src/main/resources/application-credentials.yml @@ -19,3 +19,6 @@ jwt: secret: ${JWT_SECRET} access-token-expiration: ${JWT_ACCESS_TOKEN_EXPIRATION} refresh-token-expiration: ${JWT_REFRESH_TOKEN_EXPIRATION} +google: + credentials: + path: ${GOOGLE_CREDENTIALS_PATH} \ No newline at end of file From 31f5121d0ecca593dac38a9b018d855ce5d7b6e4 Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 12 Feb 2025 13:58:35 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20google=20API=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dasom/api/domain/common/exception/ErrorCode.java | 3 ++- .../domain/google/controller/GoogleController.java | 7 ++++--- .../api/domain/google/service/GoogleApiService.java | 11 +++++++---- src/main/resources/application-credentials.yml | 4 +++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index ac0a036..472da76 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -16,7 +16,8 @@ public enum ErrorCode { ARGUMENT_NOT_VALID(400, "C007", "요청한 값이 올바르지 않습니다."), TOKEN_NOT_VALID(400, "C008", "토큰이 올바르지 않습니다."), INTERNAL_SERVER_ERROR(500, "C009", "서버에 문제가 발생하였습니다."), - NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다.") + NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."), + WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다.") ; private final int status; diff --git a/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java b/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java index a1577a5..984ed66 100644 --- a/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java +++ b/src/main/java/dmu/dasom/api/domain/google/controller/GoogleController.java @@ -2,7 +2,7 @@ import dmu.dasom.api.domain.google.service.GoogleApiService; import lombok.RequiredArgsConstructor; -import lombok.Value; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,8 +18,9 @@ public class GoogleController { private final GoogleApiService googleApiService; + @Value("${google.spreadsheet.id}") + private String spreadsheetId; - private static final String SPREADSHEET_ID = "1Vpu6_2raNvJN_GGg7aGBmzV4cXG1rCoizHl9v7kbG2o"; private static final String RANGE = "A1"; @PostMapping("/write") @@ -27,7 +28,7 @@ public ResponseEntity writeToSheet(@RequestParam String word){ try{ List> values = List.of(Collections.singletonList(word)); - googleApiService.writeToSheet(SPREADSHEET_ID, RANGE, values); + googleApiService.writeToSheet(spreadsheetId, RANGE, values); return ResponseEntity.ok("Data written successfully to the spreadsheet" + word); } catch (Exception e){ e.printStackTrace(); diff --git a/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java b/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java index 3b39c61..921deba 100644 --- a/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java +++ b/src/main/java/dmu/dasom/api/domain/google/service/GoogleApiService.java @@ -1,17 +1,19 @@ package dmu.dasom.api.domain.google.service; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.api.client.util.Value; + import com.google.api.services.sheets.v4.Sheets; import com.google.api.services.sheets.v4.model.UpdateValuesResponse; import com.google.api.services.sheets.v4.model.ValueRange; import com.google.auth.http.HttpCredentialsAdapter; import com.google.auth.oauth2.GoogleCredentials; +import dmu.dasom.api.domain.common.exception.CustomException; +import dmu.dasom.api.domain.common.exception.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; @@ -54,9 +56,10 @@ public void writeToSheet(String spreadsheetId, String range, List> logger.info("Updated rows: {}", result.getUpdatedRows()); } catch (IOException e) { logger.error("Failed to write data to the spreadsheet", e); - throw new RuntimeException("Failed to write data to the spreadsheet: " + e.getMessage(), e); + throw new CustomException(ErrorCode.WRITE_FAIL); } catch (GeneralSecurityException e) { - throw new RuntimeException(e); + logger.error("Failed to write data to the spreadsheet", e); + throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); } } diff --git a/src/main/resources/application-credentials.yml b/src/main/resources/application-credentials.yml index 669fbcf..d718107 100644 --- a/src/main/resources/application-credentials.yml +++ b/src/main/resources/application-credentials.yml @@ -21,4 +21,6 @@ jwt: refresh-token-expiration: ${JWT_REFRESH_TOKEN_EXPIRATION} google: credentials: - path: ${GOOGLE_CREDENTIALS_PATH} \ No newline at end of file + path: ${GOOGLE_CREDENTIALS_PATH} + spreadsheet: + id: ${GOOGLE_SPREADSHEET_ID} \ No newline at end of file From db28bfc5577df0a7878e3c98549e01e473337f8b Mon Sep 17 00:00:00 2001 From: hodoon Date: Tue, 18 Feb 2025 20:19:22 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++ .../applicant/service/ApplicantService.java | 2 + .../service/ApplicantServiceImpl.java | 28 +++++++ .../domain/common/exception/ErrorCode.java | 3 +- .../domain/email/service/EmailService.java | 44 +++++++++++ .../admin/controller/AdminController.java | 22 ++++++ .../resources/application-credentials.yml | 11 +++ .../resources/template/email-template.html | 79 +++++++++++++++++++ 8 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dmu/dasom/api/domain/email/service/EmailService.java create mode 100644 src/main/resources/template/email-template.html diff --git a/build.gradle b/build.gradle index d2c1c8e..c2fdc7d 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.oracle.database.jdbc:ojdbc11' @@ -53,6 +55,9 @@ dependencies { implementation 'com.google.auth:google-auth-library-oauth2-http:0.20.0' + + + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java index 1015833..e7e67c6 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java @@ -16,4 +16,6 @@ public interface ApplicantService { ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final ApplicantStatusUpdateRequestDto request); + void sendEmailsToApplicants(); + } diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java index 8164831..085dce4 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java @@ -8,12 +8,18 @@ import dmu.dasom.api.domain.applicant.repository.ApplicantRepository; import dmu.dasom.api.domain.common.exception.CustomException; import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.email.service.EmailService; import dmu.dasom.api.global.dto.PageResponse; +import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import java.util.List; + +@Slf4j @RequiredArgsConstructor @Service public class ApplicantServiceImpl implements ApplicantService { @@ -21,6 +27,7 @@ public class ApplicantServiceImpl implements ApplicantService { private final static int DEFAULT_PAGE_SIZE = 20; private final ApplicantRepository applicantRepository; + private final EmailService emailService; // 지원자 저장 @Override @@ -55,6 +62,27 @@ public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final Ap return applicant.toApplicantDetailsResponse(); } + // 지원자 이메일 보내기 + @Override + public void sendEmailsToApplicants(){ + List applicants = applicantRepository.findAll(); + + if(applicants.isEmpty()) { + throw new CustomException(ErrorCode.EMPTY_RESULT); + } + + String subject = "다솜 지원 결과"; + + for(Applicant applicant : applicants){ + try { + emailService.sendEmail(applicant.getEmail(), subject, applicant.getName()); + log.info("HTML 이메일 전송 완료: {}", applicant.getEmail()); + } catch (MessagingException e) { + log.error("이메일 전송 실패: {}", applicant.getEmail(), e); + } + } + } + // Repository에서 ID로 지원자 조회 private Applicant findById(final Long id) { return applicantRepository.findById(id) diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index 1d8a0bf..73e22f3 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -18,7 +18,8 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR(500, "C009", "서버에 문제가 발생하였습니다."), NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."), WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."), - EMPTY_RESULT(400, "C012", "조회 결과가 없습니다.") + EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."), + SEND_EMAIL_FAIL(400, "C013", "이메일 전송에 실패하였습니다."), ; private final int status; diff --git a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java new file mode 100644 index 0000000..8f65299 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java @@ -0,0 +1,44 @@ +package dmu.dasom.api.domain.email.service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +@RequiredArgsConstructor +@Service +public class EmailService { + + private JavaMailSender javaMailSender; + private TemplateEngine templateEngine; + @Value("${spring.mail.username}") + private String from; + + public void sendEmail(String to, String subject, String name) throws MessagingException { + // HTML 템플릿에 전달할 데이터 설정 + Context context = new Context(); + context.setVariable("name", name); // 지원자 이름 전달 + + // HTML 템플릿 처리 + String htmlBody = templateEngine.process("email-template", context); + + // 이메일 생성 및 전송 + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setTo(to); + helper.setSubject(subject); + helper.setText(htmlBody, true); + helper.setFrom(from); + + javaMailSender.send(message); + } + +} diff --git a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java index ebfadc2..0f06497 100644 --- a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java +++ b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java @@ -79,4 +79,26 @@ public ResponseEntity updateApplicantStatus(@PathVa return ResponseEntity.ok(applicantService.updateApplicantStatus(id, request)); } + @Operation(summary = "지원자 메일 전송") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메일 전송 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "전송 실패", + value = "{ \"code\": \"C013\", \"message\": \"이메일 전송에 실패하였습니다.\" }" + ) + } + ) + ) + }) + @PostMapping("/applicants/send-email") + public ResponseEntity sendEmailsToApplicants() { + applicantService.sendEmailsToApplicants(); + return ResponseEntity.ok("이메일 전송 성공"); + } + } diff --git a/src/main/resources/application-credentials.yml b/src/main/resources/application-credentials.yml index e9a7ac3..f300773 100644 --- a/src/main/resources/application-credentials.yml +++ b/src/main/resources/application-credentials.yml @@ -15,6 +15,17 @@ spring: redis: host: ${REDIS_HOST} port: ${REDIS_PORT} + mail: + host: smtp.gmail.com + port: 587 + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true jwt: secret: ${JWT_SECRET} access-token-expiration: ${JWT_ACCESS_TOKEN_EXPIRATION} diff --git a/src/main/resources/template/email-template.html b/src/main/resources/template/email-template.html new file mode 100644 index 0000000..3be04d2 --- /dev/null +++ b/src/main/resources/template/email-template.html @@ -0,0 +1,79 @@ + + + + + + 지원 결과 확인 + + + +
+ +
DASOM
+ + +
컴퓨터 소프트웨어 공학과 전공 동아리 다솜
34기 합격자 조회
+ + +
+

안녕하세요, 님.

+

학번 마지막 4자리 + 전화번호 마지막 4자리를 입력하여
지원 결과를 확인할 수 있습니다.

+

예시) 08470542

+ + + 결과 확인하기 +
+
+ + From 52651c0581e557269a929eacb7d0aeb0209a3f01 Mon Sep 17 00:00:00 2001 From: hodoon Date: Tue, 18 Feb 2025 20:30:03 +0900 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B4=80=EB=A0=A8=20ErrorCode=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/dmu/dasom/api/domain/common/exception/ErrorCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index 73e22f3..8d5cf7c 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -19,7 +19,7 @@ public enum ErrorCode { NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."), WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."), EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."), - SEND_EMAIL_FAIL(400, "C013", "이메일 전송에 실패하였습니다."), + SEND_EMAIL_FAIL(400, "C014", "이메일 전송에 실패하였습니다."), ; private final int status; From 79d3fd519b66946f0b60649bd3cde29e8dc4da7f Mon Sep 17 00:00:00 2001 From: hodoon Date: Tue, 18 Feb 2025 22:53:16 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EC=84=9C=EB=A5=98=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EA=B2=B0=EA=B3=BC=EC=99=80=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=ED=95=A9=EA=B2=A9=20=EA=B2=B0=EA=B3=BC=20API=20=EB=82=98?= =?UTF-8?q?=EB=88=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ApplicantRepository.java | 6 +++ .../applicant/service/ApplicantService.java | 4 +- .../service/ApplicantServiceImpl.java | 38 +++++++++++++++++-- .../domain/email/service/EmailService.java | 4 +- .../admin/controller/AdminController.java | 32 +++++++++++++--- .../resources/template/email-template.html | 12 +++--- 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java b/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java index 24aae15..b32dbe9 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java @@ -1,14 +1,20 @@ package dmu.dasom.api.domain.applicant.repository; import dmu.dasom.api.domain.applicant.entity.Applicant; +import dmu.dasom.api.domain.applicant.enums.ApplicantStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.util.List; + public interface ApplicantRepository extends JpaRepository { @Query("SELECT a FROM Applicant a ORDER BY a.id DESC") Page findAllWithPageRequest(final Pageable pageable); + // 상태별 지원자 조회 + List findByStatus(ApplicantStatus status); + } diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java index e7e67c6..c9a9077 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java @@ -16,6 +16,8 @@ public interface ApplicantService { ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final ApplicantStatusUpdateRequestDto request); - void sendEmailsToApplicants(); + void sendDocumentPassEmailsToApplicants(); + + void sendFinalPassEmailsToDocumentPassApplicants(); } diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java index 085dce4..11ef5da 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java @@ -5,6 +5,7 @@ import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto; import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto; import dmu.dasom.api.domain.applicant.entity.Applicant; +import dmu.dasom.api.domain.applicant.enums.ApplicantStatus; import dmu.dasom.api.domain.applicant.repository.ApplicantRepository; import dmu.dasom.api.domain.common.exception.CustomException; import dmu.dasom.api.domain.common.exception.ErrorCode; @@ -64,19 +65,48 @@ public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final Ap // 지원자 이메일 보내기 @Override - public void sendEmailsToApplicants(){ + public void sendDocumentPassEmailsToApplicants(){ List applicants = applicantRepository.findAll(); if(applicants.isEmpty()) { throw new CustomException(ErrorCode.EMPTY_RESULT); } - String subject = "다솜 지원 결과"; + String subject = "다솜 서류 지원 결과"; for(Applicant applicant : applicants){ try { - emailService.sendEmail(applicant.getEmail(), subject, applicant.getName()); - log.info("HTML 이메일 전송 완료: {}", applicant.getEmail()); + emailService.sendEmail( + applicant.getEmail(), + subject, + "document-pass-template", + applicant.getName() + ); + log.info("이메일 전송 완료: {}", applicant.getEmail()); + } catch (MessagingException e) { + log.error("이메일 전송 실패: {}", applicant.getEmail(), e); + } + } + } + + @Override + public void sendFinalPassEmailsToDocumentPassApplicants() { + List applicants = applicantRepository.findByStatus(ApplicantStatus.DOCUMENT_PASSED); + + if(applicants.isEmpty()){ + throw new CustomException(ErrorCode.EMPTY_RESULT); + } + + String subject = "다솜 최종 합격 결과"; + for (Applicant applicant : applicants) { + try { + emailService.sendEmail( + applicant.getEmail(), + subject, + "final-pass-template", + applicant.getName() + ); + log.info("이메일 전송 완료: {}", applicant.getEmail()); } catch (MessagingException e) { log.error("이메일 전송 실패: {}", applicant.getEmail(), e); } diff --git a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java index 8f65299..2ffdc2a 100644 --- a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java +++ b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java @@ -21,13 +21,13 @@ public class EmailService { @Value("${spring.mail.username}") private String from; - public void sendEmail(String to, String subject, String name) throws MessagingException { + public void sendEmail(String to, String subject, String templateName, String name) throws MessagingException { // HTML 템플릿에 전달할 데이터 설정 Context context = new Context(); context.setVariable("name", name); // 지원자 이름 전달 // HTML 템플릿 처리 - String htmlBody = templateEngine.process("email-template", context); + String htmlBody = templateEngine.process(templateName, context); // 이메일 생성 및 전송 MimeMessage message = javaMailSender.createMimeMessage(); diff --git a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java index 0f06497..1eae14b 100644 --- a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java +++ b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java @@ -79,7 +79,7 @@ public ResponseEntity updateApplicantStatus(@PathVa return ResponseEntity.ok(applicantService.updateApplicantStatus(id, request)); } - @Operation(summary = "지원자 메일 전송") + @Operation(summary = "서류 결과 메일 전송") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "메일 전송 성공"), @ApiResponse(responseCode = "400", description = "잘못된 요청", @@ -89,15 +89,37 @@ public ResponseEntity updateApplicantStatus(@PathVa examples = { @ExampleObject( name = "전송 실패", - value = "{ \"code\": \"C013\", \"message\": \"이메일 전송에 실패하였습니다.\" }" + value = "{ \"code\": \"C014\", \"message\": \"이메일 전송에 실패하였습니다.\" }" ) } ) ) }) - @PostMapping("/applicants/send-email") - public ResponseEntity sendEmailsToApplicants() { - applicantService.sendEmailsToApplicants(); + @PostMapping("/applicants/send-document-pass-email") + public ResponseEntity sendDocumentPassEmails() { + applicantService.sendDocumentPassEmailsToApplicants(); + return ResponseEntity.ok("이메일 전송 성공"); + } + + @Operation(summary = "최종 결과 메일 전송") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메일 전송 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "전송 실패", + value = "{ \"code\": \"C014\", \"message\": \"이메일 전송에 실패하였습니다.\" }" + ) + } + ) + ) + }) + @PostMapping("/applicants/send-final-pass-email") + public ResponseEntity sendFinalPassEmail(){ + applicantService.sendFinalPassEmailsToDocumentPassApplicants(); return ResponseEntity.ok("이메일 전송 성공"); } diff --git a/src/main/resources/template/email-template.html b/src/main/resources/template/email-template.html index 3be04d2..462ab74 100644 --- a/src/main/resources/template/email-template.html +++ b/src/main/resources/template/email-template.html @@ -23,12 +23,12 @@ .header { font-size: 24px; font-weight: bold; - color: #4caf50; + color: #00B493; margin-bottom: 20px; } .sub-header { font-size: 16px; - background-color: #4caf50; + background-color: #00B493; padding: 10px; border-radius: 5px; margin-bottom: 20px; @@ -41,11 +41,11 @@ margin-bottom: 10px; } .highlight { - color: #4caf50; + color: #00B493; } .button { display: inline-block; - background-color: #4caf50; + background-color: #00B493; color: #ffffff; padding: 12px 24px; border-radius: 5px; @@ -53,7 +53,7 @@ font-size: 16px; } .button:hover { - background-color: #45a049; + background-color: #00B493; } @@ -63,7 +63,7 @@
DASOM
-
컴퓨터 소프트웨어 공학과 전공 동아리 다솜
34기 합격자 조회
+
컴퓨터소프트웨어공학과 전공동아리 다솜
34기 서류 합격자 조회
From 64e7268a8e07712ee34fc44c1a166d085a0242d5 Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 19 Feb 2025 00:31:24 +0900 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20=EC=84=9C=EB=A5=98=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EA=B2=B0=EA=B3=BC=EC=99=80=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=ED=95=A9=EA=B2=A9=20=EA=B2=B0=EA=B3=BC=20API=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EC=9D=98=20=EB=A9=94=EC=86=8C=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A7=80=EC=9B=90=EB=90=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20MailType=20=EC=97=B4=EA=B1=B0=ED=98=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ApplicantRepository.java | 1 + .../applicant/service/ApplicantService.java | 5 +- .../service/ApplicantServiceImpl.java | 58 ++++++------------- .../api/domain/email/enums/MailType.java | 6 ++ .../domain/email/service/EmailService.java | 19 +++++- .../admin/controller/AdminController.java | 42 +++++--------- 6 files changed, 59 insertions(+), 72 deletions(-) create mode 100644 src/main/java/dmu/dasom/api/domain/email/enums/MailType.java diff --git a/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java b/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java index 834a63a..48035ac 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java @@ -18,6 +18,7 @@ public interface ApplicantRepository extends JpaRepository { // 상태별 지원자 조회 List findByStatus(ApplicantStatus status); + List findByStatusIn(List statuses); Optional findByStudentNo(final String studentNo); diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java index c9a9077..687c350 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java @@ -4,6 +4,7 @@ import dmu.dasom.api.domain.applicant.dto.ApplicantDetailsResponseDto; import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto; import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto; +import dmu.dasom.api.domain.email.enums.MailType; import dmu.dasom.api.global.dto.PageResponse; public interface ApplicantService { @@ -16,8 +17,6 @@ public interface ApplicantService { ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final ApplicantStatusUpdateRequestDto request); - void sendDocumentPassEmailsToApplicants(); - - void sendFinalPassEmailsToDocumentPassApplicants(); + void sendEmailsToApplicants(MailType mailType); } diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java index d80bab8..0a3e5d7 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java @@ -9,6 +9,7 @@ import dmu.dasom.api.domain.applicant.repository.ApplicantRepository; import dmu.dasom.api.domain.common.exception.CustomException; import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.email.enums.MailType; import dmu.dasom.api.domain.email.service.EmailService; import dmu.dasom.api.global.dto.PageResponse; import jakarta.mail.MessagingException; @@ -81,56 +82,35 @@ public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final Ap return applicant.toApplicantDetailsResponse(); } - // 지원자 이메일 보내기 @Override - public void sendDocumentPassEmailsToApplicants(){ - List applicants = applicantRepository.findAll(); - - if(applicants.isEmpty()) { - throw new CustomException(ErrorCode.EMPTY_RESULT); - } - - String subject = "다솜 서류 지원 결과"; - - for(Applicant applicant : applicants){ - try { - emailService.sendEmail( - applicant.getEmail(), - subject, - "document-pass-template", - applicant.getName() + public void sendEmailsToApplicants(MailType mailType) { + List applicants; + + // MailType에 따라 지원자 조회 + switch (mailType) { + case DOCUMENT_RESULT: + applicants = applicantRepository.findAll(); + break; + case FINAL_RESULT: + applicants = applicantRepository.findByStatusIn( + List.of(ApplicantStatus.INTERVIEW_PASSED, + ApplicantStatus.INTERVIEW_PASSED) ); - log.info("이메일 전송 완료: {}", applicant.getEmail()); - } catch (MessagingException e) { - log.error("이메일 전송 실패: {}", applicant.getEmail(), e); - } - } - } - - @Override - public void sendFinalPassEmailsToDocumentPassApplicants() { - List applicants = applicantRepository.findByStatus(ApplicantStatus.DOCUMENT_PASSED); - - if(applicants.isEmpty()){ - throw new CustomException(ErrorCode.EMPTY_RESULT); + break; + default: + throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); } - String subject = "다솜 최종 합격 결과"; for (Applicant applicant : applicants) { try { - emailService.sendEmail( - applicant.getEmail(), - subject, - "final-pass-template", - applicant.getName() - ); - log.info("이메일 전송 완료: {}", applicant.getEmail()); + emailService.sendEmail(applicant.getEmail(), applicant.getName(), mailType); } catch (MessagingException e) { - log.error("이메일 전송 실패: {}", applicant.getEmail(), e); + System.err.println("Failed to send email to: " + applicant.getEmail()); } } } + // Repository에서 ID로 지원자 조회 private Applicant findById(final Long id) { return applicantRepository.findById(id) diff --git a/src/main/java/dmu/dasom/api/domain/email/enums/MailType.java b/src/main/java/dmu/dasom/api/domain/email/enums/MailType.java new file mode 100644 index 0000000..8a1940c --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/email/enums/MailType.java @@ -0,0 +1,6 @@ +package dmu.dasom.api.domain.email.enums; + +public enum MailType { + DOCUMENT_RESULT, // 서류 합격 + FINAL_RESULT // 최종 합격 +} diff --git a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java index 2ffdc2a..6cf04e2 100644 --- a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java +++ b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java @@ -1,11 +1,10 @@ package dmu.dasom.api.domain.email.service; +import dmu.dasom.api.domain.email.enums.MailType; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @@ -21,7 +20,21 @@ public class EmailService { @Value("${spring.mail.username}") private String from; - public void sendEmail(String to, String subject, String templateName, String name) throws MessagingException { + public void sendEmail(String to, String name, MailType mailType) throws MessagingException { + // 메일 제목 및 템플릿 설정 + String subject; + String templateName = switch (mailType) { + case DOCUMENT_RESULT -> { + subject = "서류 합격 안내"; + yield "document-pass-template"; + } + case FINAL_RESULT -> { + subject = "최종 합격 안내"; + yield "final-pass-template"; + } + default -> throw new IllegalStateException("Unexpected value: " + mailType); + }; + // HTML 템플릿에 전달할 데이터 설정 Context context = new Context(); context.setVariable("name", name); // 지원자 이름 전달 diff --git a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java index 1eae14b..c8b5536 100644 --- a/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java +++ b/src/main/java/dmu/dasom/api/global/admin/controller/AdminController.java @@ -4,8 +4,10 @@ import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto; import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto; import dmu.dasom.api.domain.applicant.service.ApplicantService; +import dmu.dasom.api.domain.email.enums.MailType; import dmu.dasom.api.global.dto.PageResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; @@ -79,7 +81,10 @@ public ResponseEntity updateApplicantStatus(@PathVa return ResponseEntity.ok(applicantService.updateApplicantStatus(id, request)); } - @Operation(summary = "서류 결과 메일 전송") + @Operation( + summary = "메일 전송", + description = "지원자들에게 서류 결과 또는 최종 결과 이메일을 발송합니다." + ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "메일 전송 성공"), @ApiResponse(responseCode = "400", description = "잘못된 요청", @@ -95,32 +100,15 @@ public ResponseEntity updateApplicantStatus(@PathVa ) ) }) - @PostMapping("/applicants/send-document-pass-email") - public ResponseEntity sendDocumentPassEmails() { - applicantService.sendDocumentPassEmailsToApplicants(); - return ResponseEntity.ok("이메일 전송 성공"); - } - - @Operation(summary = "최종 결과 메일 전송") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "메일 전송 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class), - examples = { - @ExampleObject( - name = "전송 실패", - value = "{ \"code\": \"C014\", \"message\": \"이메일 전송에 실패하였습니다.\" }" - ) - } - ) - ) - }) - @PostMapping("/applicants/send-final-pass-email") - public ResponseEntity sendFinalPassEmail(){ - applicantService.sendFinalPassEmailsToDocumentPassApplicants(); - return ResponseEntity.ok("이메일 전송 성공"); + @PostMapping("/applicants/send-email") + public ResponseEntity sendEmailsToApplicants( + @RequestParam + @Parameter(description = "메일 발송 타입", examples = { + @ExampleObject(name = "서류 합격 메일", value = "DOCUMENT_PASS"), + @ExampleObject(name = "최종 결과 메일", value = "FINAL_RESULT") + }) MailType mailType) { + applicantService.sendEmailsToApplicants(mailType); + return ResponseEntity.ok().build(); } } From 272bd014b2d744f8b90f8c1c8597a1856f6ae41a Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 19 Feb 2025 01:03:28 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20ApplicantServiceImpl=EC=9D=98=20se?= =?UTF-8?q?ndEmailsToApplicants=20=EB=A9=94=EC=86=8C=EB=93=9C=20ApplicantS?= =?UTF-8?q?tatus=20=EC=88=98=EC=A0=95=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/applicant/service/ApplicantServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java index 0a3e5d7..8bce02c 100644 --- a/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java @@ -93,7 +93,7 @@ public void sendEmailsToApplicants(MailType mailType) { break; case FINAL_RESULT: applicants = applicantRepository.findByStatusIn( - List.of(ApplicantStatus.INTERVIEW_PASSED, + List.of(ApplicantStatus.INTERVIEW_FAILED, ApplicantStatus.INTERVIEW_PASSED) ); break; From 526448a85446e23593b20fe5f2aa6f7b673aa75a Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 19 Feb 2025 01:43:51 +0900 Subject: [PATCH 08/10] =?UTF-8?q?test:=20ApplicantServiceImpl=EC=9D=98=20s?= =?UTF-8?q?endEmailsToApplicants=20=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20EmailServiceTest=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicant/ApplicantServiceTest.java | 68 ++++++++++++++ .../api/domain/email/EmailServiceTest.java | 91 +++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java diff --git a/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java b/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java index 256a302..3255149 100644 --- a/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java +++ b/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java @@ -3,11 +3,15 @@ import dmu.dasom.api.domain.applicant.dto.ApplicantCreateRequestDto; import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto; import dmu.dasom.api.domain.applicant.entity.Applicant; +import dmu.dasom.api.domain.applicant.enums.ApplicantStatus; import dmu.dasom.api.domain.applicant.repository.ApplicantRepository; import dmu.dasom.api.domain.applicant.service.ApplicantServiceImpl; import dmu.dasom.api.domain.common.exception.CustomException; import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.email.enums.MailType; +import dmu.dasom.api.domain.email.service.EmailService; import dmu.dasom.api.global.dto.PageResponse; +import jakarta.mail.MessagingException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,6 +23,7 @@ import org.springframework.data.domain.PageRequest; import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -30,6 +35,9 @@ class ApplicantServiceTest { @Mock private ApplicantRepository applicantRepository; + @Mock + private EmailService emailService; + @InjectMocks private ApplicantServiceImpl applicantService; @@ -123,4 +131,64 @@ void getApplicants_fail_emptyResult() { assertEquals(ErrorCode.EMPTY_RESULT, exception.getErrorCode()); verify(applicantRepository).findAllWithPageRequest(pageRequest); } + + @Test + @DisplayName("메일 전송 - 서류 결과 메일 (DOCUMENT_RESULT)") + void sendEmailsToApplicants_documentResult() throws MessagingException { + // given + MailType mailType = MailType.DOCUMENT_RESULT; + Applicant applicant = mock(Applicant.class); + when(applicantRepository.findAll()).thenReturn(Collections.singletonList(applicant)); + when(applicant.getEmail()).thenReturn("test@example.com"); + when(applicant.getName()).thenReturn("지원자"); + + // when + assertDoesNotThrow(() -> applicantService.sendEmailsToApplicants(mailType)); + + // then + verify(applicantRepository).findAll(); + verify(emailService).sendEmail("test@example.com", "지원자", mailType); + } + + @Test + @DisplayName("메일 전송 - 최종 결과 메일 (FINAL_RESULT)") + void sendEmailsToApplicants_finalResult() throws MessagingException { + // given + MailType mailType = MailType.FINAL_RESULT; + Applicant passedApplicant = mock(Applicant.class); + Applicant failedApplicant = mock(Applicant.class); + + when(applicantRepository.findByStatusIn( + List.of(ApplicantStatus.INTERVIEW_FAILED, ApplicantStatus.INTERVIEW_PASSED))) + .thenReturn(List.of(passedApplicant, failedApplicant)); + + when(passedApplicant.getEmail()).thenReturn("passed@example.com"); + when(passedApplicant.getName()).thenReturn("합격자"); + when(failedApplicant.getEmail()).thenReturn("failed@example.com"); + when(failedApplicant.getName()).thenReturn("불합격자"); + + // when + assertDoesNotThrow(() -> applicantService.sendEmailsToApplicants(mailType)); + + // then + verify(applicantRepository).findByStatusIn( + List.of(ApplicantStatus.INTERVIEW_FAILED, ApplicantStatus.INTERVIEW_PASSED)); + verify(emailService).sendEmail("passed@example.com", "합격자", mailType); + verify(emailService).sendEmail("failed@example.com", "불합격자", mailType); + } + + @Test + @DisplayName("메일 전송 - 잘못된 MailType") + void sendEmailsToApplicants_invalidMailType() { + // given + MailType invalidMailType = null; + + // when & then + CustomException exception = assertThrows(CustomException.class, () -> { + applicantService.sendEmailsToApplicants(invalidMailType); + }); + + assertEquals(ErrorCode.INTERNAL_SERVER_ERROR, exception.getErrorCode()); + } + } \ No newline at end of file diff --git a/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java b/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java new file mode 100644 index 0000000..99ea830 --- /dev/null +++ b/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java @@ -0,0 +1,91 @@ +package dmu.dasom.api.domain.email; + +import dmu.dasom.api.domain.email.enums.MailType; +import dmu.dasom.api.domain.email.service.EmailService; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.mail.javamail.JavaMailSender; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class EmailServiceTest { + + @Mock + private JavaMailSender javaMailSender; + + @Mock + private TemplateEngine templateEngine; + + @InjectMocks + private EmailService emailService; + + @Mock + private MimeMessage mimeMessage; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mimeMessage = mock(MimeMessage.class); + when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + } + + @Test + @DisplayName("서류 합격 메일 발송 테스트") + void sendEmail_documentResult() throws MessagingException { + // given + String to = "applicant@example.com"; + String name = "테스트"; + MailType mailType = MailType.DOCUMENT_RESULT; + + String expectedTemplate = "document-pass-template"; + String expectedHtmlBody = "Document Pass"; + when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); + + // when + emailService.sendEmail(to, name, mailType); + + // then + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); + verify(javaMailSender).send(messageCaptor.capture()); + + MimeMessage sentMessage = messageCaptor.getValue(); + assertNotNull(sentMessage); + verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); + } + + @Test + @DisplayName("최종 합격 메일 발송 테스트") + void sendEmail_finalResult() throws MessagingException { + // given + String to = "applicant@example.com"; + String name = "지원자"; + MailType mailType = MailType.FINAL_RESULT; + + String expectedTemplate = "final-pass-template"; + String expectedHtmlBody = "Final Pass"; + when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); + + // when + emailService.sendEmail(to, name, mailType); + + // then + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); + verify(javaMailSender).send(messageCaptor.capture()); + + MimeMessage sentMessage = messageCaptor.getValue(); + assertNotNull(sentMessage); + verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); + } + + +} From b0bf4eabc7e77ffc2841a830047f48b6932f72b0 Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 19 Feb 2025 01:47:34 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20EmailServiceTest=EC=97=90=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=80=EB=8A=94=20from=EC=9D=98=20value?= =?UTF-8?q?=EA=B0=80=20=EC=99=B8=EB=B6=80=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=9D=B8=EC=8B=9D=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=B6=94=ED=9B=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9E=91=EC=84=B1=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicant/ApplicantServiceTest.java | 15 --- .../api/domain/email/EmailServiceTest.java | 91 ------------------- 2 files changed, 106 deletions(-) delete mode 100644 src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java diff --git a/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java b/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java index 3255149..f9b89af 100644 --- a/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java +++ b/src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java @@ -176,19 +176,4 @@ void sendEmailsToApplicants_finalResult() throws MessagingException { verify(emailService).sendEmail("passed@example.com", "합격자", mailType); verify(emailService).sendEmail("failed@example.com", "불합격자", mailType); } - - @Test - @DisplayName("메일 전송 - 잘못된 MailType") - void sendEmailsToApplicants_invalidMailType() { - // given - MailType invalidMailType = null; - - // when & then - CustomException exception = assertThrows(CustomException.class, () -> { - applicantService.sendEmailsToApplicants(invalidMailType); - }); - - assertEquals(ErrorCode.INTERNAL_SERVER_ERROR, exception.getErrorCode()); - } - } \ No newline at end of file diff --git a/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java b/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java deleted file mode 100644 index 99ea830..0000000 --- a/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package dmu.dasom.api.domain.email; - -import dmu.dasom.api.domain.email.enums.MailType; -import dmu.dasom.api.domain.email.service.EmailService; -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.mail.javamail.JavaMailSender; -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.context.Context; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -public class EmailServiceTest { - - @Mock - private JavaMailSender javaMailSender; - - @Mock - private TemplateEngine templateEngine; - - @InjectMocks - private EmailService emailService; - - @Mock - private MimeMessage mimeMessage; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - mimeMessage = mock(MimeMessage.class); - when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); - } - - @Test - @DisplayName("서류 합격 메일 발송 테스트") - void sendEmail_documentResult() throws MessagingException { - // given - String to = "applicant@example.com"; - String name = "테스트"; - MailType mailType = MailType.DOCUMENT_RESULT; - - String expectedTemplate = "document-pass-template"; - String expectedHtmlBody = "Document Pass"; - when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); - - // when - emailService.sendEmail(to, name, mailType); - - // then - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); - verify(javaMailSender).send(messageCaptor.capture()); - - MimeMessage sentMessage = messageCaptor.getValue(); - assertNotNull(sentMessage); - verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); - } - - @Test - @DisplayName("최종 합격 메일 발송 테스트") - void sendEmail_finalResult() throws MessagingException { - // given - String to = "applicant@example.com"; - String name = "지원자"; - MailType mailType = MailType.FINAL_RESULT; - - String expectedTemplate = "final-pass-template"; - String expectedHtmlBody = "Final Pass"; - when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); - - // when - emailService.sendEmail(to, name, mailType); - - // then - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); - verify(javaMailSender).send(messageCaptor.capture()); - - MimeMessage sentMessage = messageCaptor.getValue(); - assertNotNull(sentMessage); - verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); - } - - -} From b834817b5e102a4617f1a4b5563750820a973605 Mon Sep 17 00:00:00 2001 From: hodoon Date: Wed, 19 Feb 2025 14:03:03 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20EmailServiceTest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/common/exception/ErrorCode.java | 3 +- .../domain/email/service/EmailService.java | 5 + .../api/domain/email/EmailServiceTest.java | 109 ++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index 4311056..bce69bd 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -20,7 +20,8 @@ public enum ErrorCode { WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."), EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."), DUPLICATED_STUDENT_NO(400, "C013", "이미 등록된 학번입니다."), - SEND_EMAIL_FAIL(400, "C014", "이메일 전송에 실패하였습니다.") + SEND_EMAIL_FAIL(400, "C014", "이메일 전송에 실패하였습니다."), + MAIL_TYPE_NOT_VALID(400, "C015", "메일 타입이 올바르지 않습니다.") ; private final int status; diff --git a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java index 6cf04e2..bac1c87 100644 --- a/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java +++ b/src/main/java/dmu/dasom/api/domain/email/service/EmailService.java @@ -1,5 +1,7 @@ package dmu.dasom.api.domain.email.service; +import dmu.dasom.api.domain.common.exception.CustomException; +import dmu.dasom.api.domain.common.exception.ErrorCode; import dmu.dasom.api.domain.email.enums.MailType; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -21,6 +23,9 @@ public class EmailService { private String from; public void sendEmail(String to, String name, MailType mailType) throws MessagingException { + if (mailType == null){ + throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); + } // 메일 제목 및 템플릿 설정 String subject; String templateName = switch (mailType) { diff --git a/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java b/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java new file mode 100644 index 0000000..ab580be --- /dev/null +++ b/src/test/java/dmu/dasom/api/domain/email/EmailServiceTest.java @@ -0,0 +1,109 @@ +package dmu.dasom.api.domain.email; + +import dmu.dasom.api.domain.common.exception.CustomException; +import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.email.enums.MailType; +import dmu.dasom.api.domain.email.service.EmailService; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.test.util.ReflectionTestUtils; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class EmailServiceTest { + + @Mock + private JavaMailSender javaMailSender; + + @Mock + private TemplateEngine templateEngine; + + @InjectMocks + private EmailService emailService; + + private MimeMessage mimeMessage; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mimeMessage = mock(MimeMessage.class); + when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + + // 테스트 환경에서 from 값을 설정 + ReflectionTestUtils.setField(emailService, "from", "test_email@example.com"); + } + + @Test + @DisplayName("서류 합격 메일 발송 테스트") + void sendEmail_documentResult() throws MessagingException { + // given + String to = "applicant@example.com"; + String name = "지원자"; + MailType mailType = MailType.DOCUMENT_RESULT; + + String expectedTemplate = "document-pass-template"; + String expectedHtmlBody = "Document Pass"; + when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); + + // when + emailService.sendEmail(to, name, mailType); + + // then + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); + verify(javaMailSender).send(messageCaptor.capture()); + + MimeMessage sentMessage = messageCaptor.getValue(); + assertNotNull(sentMessage); + verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); + } + + @Test + @DisplayName("최종 합격 메일 발송 테스트") + void sendEmail_finalResult() throws MessagingException { + // given + String to = "applicant@example.com"; + String name = "지원자"; + MailType mailType = MailType.FINAL_RESULT; + + String expectedTemplate = "final-pass-template"; + String expectedHtmlBody = "Final Pass"; + when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); + + // when + emailService.sendEmail(to, name, mailType); + + // then + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); + verify(javaMailSender).send(messageCaptor.capture()); + + MimeMessage sentMessage = messageCaptor.getValue(); + assertNotNull(sentMessage); + verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); + } + + @Test + @DisplayName("잘못된 MailType 처리 테스트") + void sendEmail_invalidMailType() { + // given + String to = "applicant@example.com"; + String name = "지원자"; + + // when & then + CustomException exception = assertThrows(CustomException.class, () -> { + emailService.sendEmail(to, name, null); + }); + + assertEquals(ErrorCode.MAIL_TYPE_NOT_VALID, exception.getErrorCode()); + } +} \ No newline at end of file