Skip to content

Commit 7752aa6

Browse files
authored
Merge branch 'dev' into feat/#28-1
2 parents c2e2118 + 7c29b07 commit 7752aa6

40 files changed

+1788
-148
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ out/
4141

4242
### dev ###
4343
application-dev.yml
44+
45+
### google ###
46+
src/main/resources/credentials/dasomGoogle.json

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ dependencies {
3939
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
4040
implementation 'org.springframework.boot:spring-boot-starter-security'
4141
implementation 'org.springframework.boot:spring-boot-starter-validation'
42+
implementation 'org.springframework.boot:spring-boot-starter-mail'
4243
implementation 'org.springframework.boot:spring-boot-starter-web'
44+
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
4345
compileOnly 'org.projectlombok:lombok'
4446
developmentOnly 'org.springframework.boot:spring-boot-devtools'
4547
runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
@@ -53,6 +55,9 @@ dependencies {
5355
implementation 'com.google.auth:google-auth-library-oauth2-http:0.20.0'
5456

5557

58+
59+
60+
5661
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
5762
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
5863
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

src/main/java/dmu/dasom/api/domain/applicant/entity/Applicant.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
1717

1818
import java.time.LocalDateTime;
19+
import java.util.List;
1920

2021
@AllArgsConstructor
2122
@Builder
@@ -82,6 +83,23 @@ public void updateStatus(final ApplicantStatus status) {
8283
this.status = status;
8384
}
8485

86+
public List<Object> toGoogleSheetRow(){
87+
return List.of(
88+
this.id,
89+
this.name,
90+
this.studentNo,
91+
this.contact,
92+
this.email,
93+
this.grade,
94+
this.reasonForApply,
95+
this.activityWish,
96+
this.isPrivacyPolicyAgreed,
97+
this.status.name(),
98+
this.createdAt.toString(),
99+
this.updatedAt.toString()
100+
);
101+
}
102+
85103
public ApplicantResponseDto toApplicantResponse() {
86104
return ApplicantResponseDto.builder()
87105
.id(this.id)

src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package dmu.dasom.api.domain.applicant.repository;
22

33
import dmu.dasom.api.domain.applicant.entity.Applicant;
4+
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
45
import org.springframework.data.domain.Page;
56
import org.springframework.data.domain.Pageable;
67
import org.springframework.data.jpa.repository.JpaRepository;
78
import org.springframework.data.jpa.repository.Query;
89

10+
import java.util.List;
911
import java.util.Optional;
1012

1113
public interface ApplicantRepository extends JpaRepository<Applicant, Long> {
1214

1315
@Query("SELECT a FROM Applicant a ORDER BY a.id DESC")
1416
Page<Applicant> findAllWithPageRequest(final Pageable pageable);
17+
// 상태별 지원자 조회
18+
List<Applicant> findByStatus(ApplicantStatus status);
19+
List<Applicant> findByStatusIn(List<ApplicantStatus> statuses);
1520

1621
Optional<Applicant> findByStudentNo(final String studentNo);
1722

src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import dmu.dasom.api.domain.applicant.dto.ApplicantDetailsResponseDto;
55
import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto;
66
import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto;
7+
import dmu.dasom.api.domain.email.enums.MailType;
78
import dmu.dasom.api.global.dto.PageResponse;
89

910
public interface ApplicantService {
@@ -16,4 +17,8 @@ public interface ApplicantService {
1617

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

20+
void sendEmailsToApplicants(MailType mailType);
21+
22+
ApplicantDetailsResponseDto getApplicantByStudentNo(final String studentNo);
23+
1924
}

src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto;
66
import dmu.dasom.api.domain.applicant.dto.ApplicantStatusUpdateRequestDto;
77
import dmu.dasom.api.domain.applicant.entity.Applicant;
8+
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
89
import dmu.dasom.api.domain.applicant.repository.ApplicantRepository;
910
import dmu.dasom.api.domain.common.exception.CustomException;
1011
import dmu.dasom.api.domain.common.exception.ErrorCode;
12+
import dmu.dasom.api.domain.email.enums.MailType;
13+
import dmu.dasom.api.domain.email.service.EmailService;
1114
import dmu.dasom.api.domain.google.service.GoogleApiService;
1215
import dmu.dasom.api.global.dto.PageResponse;
16+
import jakarta.mail.MessagingException;
1317
import lombok.RequiredArgsConstructor;
18+
import lombok.extern.slf4j.Slf4j;
1419
import org.springframework.beans.factory.annotation.Value;
1520
import org.springframework.data.domain.Page;
1621
import org.springframework.data.domain.PageRequest;
@@ -21,6 +26,7 @@
2126

2227
import java.util.List;
2328

29+
@Slf4j
2430
@RequiredArgsConstructor
2531
@Service
2632
@Transactional
@@ -29,11 +35,9 @@ public class ApplicantServiceImpl implements ApplicantService {
2935
private final static int DEFAULT_PAGE_SIZE = 20;
3036

3137
private final ApplicantRepository applicantRepository;
38+
private final EmailService emailService;
3239
private final GoogleApiService googleApiService;
3340

34-
@Value("${google.spreadsheet.id}")
35-
private String spreadsheetId;
36-
3741
// 지원자 저장
3842
@Override
3943
public void apply(final ApplicantCreateRequestDto request) {
@@ -45,55 +49,15 @@ public void apply(final ApplicantCreateRequestDto request) {
4549
if (!request.getIsOverwriteConfirmed())
4650
throw new CustomException(ErrorCode.DUPLICATED_STUDENT_NO);
4751

48-
// 기존 지원자 정보 갱신 수행
4952
Applicant existingApplicant = applicant.get();
50-
applicant.get().overwrite(request);
51-
52-
// 구글 시트 업데이트를 위한 데이터 구성
53-
List<List<Object>> values = List.of(List.of(
54-
existingApplicant.getId(),
55-
existingApplicant.getName(),
56-
existingApplicant.getStudentNo(),
57-
existingApplicant.getContact(),
58-
existingApplicant.getEmail(),
59-
existingApplicant.getGrade(),
60-
existingApplicant.getReasonForApply(),
61-
existingApplicant.getActivityWish(),
62-
existingApplicant.getIsPrivacyPolicyAgreed(),
63-
existingApplicant.getCreatedAt(),
64-
existingApplicant.getUpdatedAt()
65-
));
66-
67-
// 구글 시트에서 해당 지원자의 행 번호 조회(A열에 저장된 지원자 id 기준)
68-
int rowNumber = googleApiService.findRowByApplicantId(spreadsheetId, "Sheet1", existingApplicant.getId());
69-
70-
// 찾아낸 행 번호에 따라 범위를 지정
71-
String range = "Sheet1!A" + rowNumber + ":K" + rowNumber;
72-
googleApiService.updateSheet(spreadsheetId, range, values);
53+
existingApplicant.overwrite(request);
54+
55+
googleApiService.updateSheet(List.of(existingApplicant));
7356
return;
7457
}
7558

76-
// 새로운 지원자일 경우 저장
77-
Applicant newApplicant = applicantRepository.save(request.toEntity());
78-
79-
// 스프레드 시트에 기록할 데이터
80-
List<List<Object>> values = List.of(List.of(
81-
newApplicant.getId(),
82-
newApplicant.getName(),
83-
newApplicant.getStudentNo(),
84-
newApplicant.getContact(),
85-
newApplicant.getEmail(),
86-
newApplicant.getGrade(),
87-
newApplicant.getReasonForApply(),
88-
newApplicant.getActivityWish(),
89-
newApplicant.getIsPrivacyPolicyAgreed(),
90-
newApplicant.getCreatedAt(),
91-
newApplicant.getUpdatedAt()
92-
));
93-
94-
String range = "Sheet1!A:K";
95-
96-
googleApiService.writeToSheet(spreadsheetId, range, values);
59+
Applicant savedApplicant = applicantRepository.save(request.toEntity());
60+
googleApiService.appendToSheet(List.of(savedApplicant));
9761
}
9862

9963
// 지원자 조회
@@ -118,11 +82,49 @@ public ApplicantDetailsResponseDto getApplicant(final Long id) {
11882
@Override
11983
public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final ApplicantStatusUpdateRequestDto request) {
12084
final Applicant applicant = findById(id);
85+
12186
applicant.updateStatus(request.getStatus());
87+
googleApiService.updateSheet(List.of(applicant));
12288

12389
return applicant.toApplicantDetailsResponse();
12490
}
12591

92+
@Override
93+
public void sendEmailsToApplicants(MailType mailType) {
94+
List<Applicant> applicants;
95+
96+
// MailType에 따라 지원자 조회
97+
switch (mailType) {
98+
case DOCUMENT_RESULT:
99+
applicants = applicantRepository.findAll();
100+
break;
101+
case FINAL_RESULT:
102+
applicants = applicantRepository.findByStatusIn(
103+
List.of(ApplicantStatus.INTERVIEW_FAILED,
104+
ApplicantStatus.INTERVIEW_PASSED)
105+
);
106+
break;
107+
default:
108+
throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
109+
}
110+
111+
for (Applicant applicant : applicants) {
112+
try {
113+
emailService.sendEmail(applicant.getEmail(), applicant.getName(), mailType);
114+
} catch (MessagingException e) {
115+
System.err.println("Failed to send email to: " + applicant.getEmail());
116+
}
117+
}
118+
}
119+
120+
// 학번으로 지원자 조회
121+
@Override
122+
public ApplicantDetailsResponseDto getApplicantByStudentNo(final String studentNo) {
123+
return findByStudentNo(studentNo)
124+
.map(Applicant::toApplicantDetailsResponse)
125+
.orElseThrow(() -> new CustomException(ErrorCode.ARGUMENT_NOT_VALID));
126+
}
127+
126128
// Repository에서 ID로 지원자 조회
127129
private Applicant findById(final Long id) {
128130
return applicantRepository.findById(id)

src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public enum ErrorCode {
2020
WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."),
2121
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."),
2222
DUPLICATED_STUDENT_NO(400, "C013", "이미 등록된 학번입니다."),
23+
SEND_EMAIL_FAIL(400, "C014", "이메일 전송에 실패하였습니다."),
24+
MAIL_TYPE_NOT_VALID(400, "C015", "메일 타입이 올바르지 않습니다."),
25+
INVALID_DATETIME_FORMAT(400, "C016", "날짜 형식이 올바르지 않습니다."),
26+
INVALID_TIME_FORMAT(400, "C017", "시간 형식이 올바르지 않습니다."),
27+
INVALID_INQUIRY_PERIOD(400, "C018", "조회 기간이 아닙니다."),
28+
SHEET_WRITE_FAIL(400, "C019", "시트에 데이터를 쓰는데 실패하였습니다."),
29+
SHEET_READ_FAIL(400, "C200", "시트에 데이터를 쓰는데 실패하였습니다."),
2330
;
2431

2532
private final int status;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dmu.dasom.api.domain.email.enums;
2+
3+
public enum MailType {
4+
DOCUMENT_RESULT, // 서류 합격
5+
FINAL_RESULT // 최종 합격
6+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package dmu.dasom.api.domain.email.service;
2+
3+
import dmu.dasom.api.domain.common.exception.CustomException;
4+
import dmu.dasom.api.domain.common.exception.ErrorCode;
5+
import dmu.dasom.api.domain.email.enums.MailType;
6+
import jakarta.mail.MessagingException;
7+
import jakarta.mail.internet.MimeMessage;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.beans.factory.annotation.Value;
10+
import org.springframework.mail.javamail.JavaMailSender;
11+
import org.springframework.mail.javamail.MimeMessageHelper;
12+
import org.springframework.stereotype.Service;
13+
import org.thymeleaf.TemplateEngine;
14+
import org.thymeleaf.context.Context;
15+
16+
@RequiredArgsConstructor
17+
@Service
18+
public class EmailService {
19+
20+
private JavaMailSender javaMailSender;
21+
private TemplateEngine templateEngine;
22+
@Value("${spring.mail.username}")
23+
private String from;
24+
25+
public void sendEmail(String to, String name, MailType mailType) throws MessagingException {
26+
if (mailType == null){
27+
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
28+
}
29+
// 메일 제목 및 템플릿 설정
30+
String subject;
31+
String emailContent;
32+
String buttonUrl = "#";
33+
String buttonText;
34+
switch (mailType) {
35+
case DOCUMENT_RESULT -> {
36+
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내";
37+
emailContent = "먼저 다솜 34기에 많은 관심을 갖고 지원해 주셔서 감사드리며,<br>" +
38+
"내부 서류 평가 결과 및 추후 일정에 관해 안내드리고자 이메일을 발송하게 되었습니다.<br>" +
39+
"모집 폼 합/불합 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
40+
buttonText = "서류 결과 확인하기";
41+
}
42+
case FINAL_RESULT -> {
43+
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 합격 안내";
44+
emailContent = "먼저 다솜 34기에 많은 관심을 갖고 지원해 주셔서 감사드리며,<br>" +
45+
"최종 면접 결과 및 추후 일정에 관해 안내드리고자 이메일을 발송하게 되었습니다.<br>" +
46+
"모집 폼 합/불합 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
47+
buttonText = "최종 결과 확인하기";
48+
}
49+
default -> throw new IllegalStateException("Unexpected value: " + mailType);
50+
}
51+
52+
// HTML 템플릿에 전달할 데이터 설정
53+
Context context = new Context();
54+
context.setVariable("name", name); // 지원자 이름 전달
55+
context.setVariable("emailContent", emailContent); // 이메일 내용 전달
56+
context.setVariable("buttonUrl", buttonUrl); // 버튼 링크 전달
57+
context.setVariable("buttonText", buttonText);
58+
59+
// HTML 템플릿 처리
60+
String htmlBody = templateEngine.process("email-template", context);
61+
62+
// 이메일 생성 및 전송
63+
MimeMessage message = javaMailSender.createMimeMessage();
64+
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
65+
66+
helper.setTo(to);
67+
helper.setSubject(subject);
68+
helper.setText(htmlBody, true);
69+
helper.setFrom(from);
70+
71+
javaMailSender.send(message);
72+
}
73+
74+
}

0 commit comments

Comments
 (0)