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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
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;

import java.util.Optional;

public interface ApplicantRepository extends JpaRepository<Applicant, Long> {

@Query("SELECT a FROM Applicant a ORDER BY a.id DESC")
Page<Applicant> findAllWithPageRequest(final Pageable pageable);

// 상태별 지원자 조회
List<Applicant> findByStatus(ApplicantStatus status);
List<Applicant> findByStatusIn(List<ApplicantStatus> statuses);

Optional<Applicant> findByStudentNo(final String studentNo);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -16,4 +17,6 @@ public interface ApplicantService {

ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final ApplicantStatusUpdateRequestDto request);

void sendEmailsToApplicants(MailType mailType);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@
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;
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 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 org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional
Expand All @@ -25,6 +33,7 @@ public class ApplicantServiceImpl implements ApplicantService {
private final static int DEFAULT_PAGE_SIZE = 20;

private final ApplicantRepository applicantRepository;
private final EmailService emailService;

// 지원자 저장
@Override
Expand Down Expand Up @@ -73,6 +82,35 @@ public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final Ap
return applicant.toApplicantDetailsResponse();
}

@Override
public void sendEmailsToApplicants(MailType mailType) {
List<Applicant> 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최종 결과 안내는 상태가 INTERVIEW_PASSED, INTERVIEW_FAILED 인 지원자에게 발송하도록 수정해주세요

);
break;
default:
throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
}

for (Applicant applicant : applicants) {
try {
emailService.sendEmail(applicant.getEmail(), applicant.getName(), mailType);
} catch (MessagingException e) {
System.err.println("Failed to send email to: " + applicant.getEmail());
}
}
}


// Repository에서 ID로 지원자 조회
private Applicant findById(final Long id) {
return applicantRepository.findById(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ErrorCode {
WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."),
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."),
DUPLICATED_STUDENT_NO(400, "C013", "이미 등록된 학번입니다."),
SEND_EMAIL_FAIL(400, "C014", "이메일 전송에 실패하였습니다.")
;

private final int status;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/dmu/dasom/api/domain/email/enums/MailType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dmu.dasom.api.domain.email.enums;

public enum MailType {
DOCUMENT_RESULT, // 서류 합격
FINAL_RESULT // 최종 합격
}
57 changes: 57 additions & 0 deletions src/main/java/dmu/dasom/api/domain/email/service/EmailService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.Value;
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 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); // 지원자 이름 전달

// HTML 템플릿 처리
String htmlBody = templateEngine.process(templateName, 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,4 +81,34 @@ public ResponseEntity<ApplicantDetailsResponseDto> updateApplicantStatus(@PathVa
return ResponseEntity.ok(applicantService.updateApplicantStatus(id, request));
}

@Operation(
summary = "메일 전송",
description = "지원자들에게 서류 결과 또는 최종 결과 이메일을 발송합니다."
)
@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-email")
public ResponseEntity<Void> sendEmailsToApplicants(
@RequestParam
@Parameter(description = "메일 발송 타입", examples = {
@ExampleObject(name = "서류 합격 메일", value = "DOCUMENT_PASS"),
@ExampleObject(name = "최종 결과 메일", value = "FINAL_RESULT")
}) MailType mailType) {
applicantService.sendEmailsToApplicants(mailType);
return ResponseEntity.ok().build();
}

}
11 changes: 11 additions & 0 deletions src/main/resources/application-credentials.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
79 changes: 79 additions & 0 deletions src/main/resources/template/email-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>지원 결과 확인</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #1a1a1a;
color: #ffffff;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #1a1a1a;
border-radius: 8px;
text-align: center;
}
.header {
font-size: 24px;
font-weight: bold;
color: #00B493;
margin-bottom: 20px;
}
.sub-header {
font-size: 16px;
background-color: #00B493;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
.content {
font-size: 14px;
line-height: 1.8;
}
.content p {
margin-bottom: 10px;
}
.highlight {
color: #00B493;
}
.button {
display: inline-block;
background-color: #00B493;
color: #ffffff;
padding: 12px 24px;
border-radius: 5px;
text-decoration: none;
font-size: 16px;
}
.button:hover {
background-color: #00B493;
}
</style>
</head>
<body>
<div class="container">
<!-- Header Section -->
<div class="header">DASOM</div>

<!-- Sub-header Section -->
<div class="sub-header">컴퓨터소프트웨어공학과 전공동아리 다솜<br>34기 서류 합격자 조회</div>

<!-- Content Section -->
<div class="content">
<p>안녕하세요, <span class="highlight" th:text="${name}"></span>님.</p>
<p>학번 마지막 <span class="highlight">4자리</span> + 전화번호 마지막 <span class="highlight">4자리</span>를 입력하여<br>지원 결과를 확인할 수 있습니다.</p>
<p>예시) <span class="highlight">08470542</span></p>

<!-- Button -->
<a href="https://example.com/result" class="button">결과 확인하기</a>
</div>
</div>
</body>
</html>