Skip to content

Commit 69fec1f

Browse files
committed
2 parents 82c0d1a + 795aced commit 69fec1f

39 files changed

+1720
-40
lines changed

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/dto/ApplicantCreateRequestDto.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package dmu.dasom.api.domain.applicant.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.annotation.JsonSetter;
5+
import com.fasterxml.jackson.annotation.Nulls;
36
import dmu.dasom.api.domain.applicant.entity.Applicant;
47
import io.swagger.v3.oas.annotations.media.Schema;
58
import jakarta.validation.constraints.*;
@@ -9,6 +12,11 @@
912
@Schema(name = "ApplicantCreateRequestDto", description = "지원자 생성 요청 DTO")
1013
public class ApplicantCreateRequestDto {
1114

15+
@NotNull
16+
@Size(max = 16)
17+
@Schema(description = "이름", example = "홍길동")
18+
private String name;
19+
1220
@NotNull
1321
@Pattern(regexp = "^[0-9]{8}$")
1422
@Size(min = 8, max = 8)
@@ -39,15 +47,21 @@ public class ApplicantCreateRequestDto {
3947
private String reasonForApply;
4048

4149
@Size(max = 200)
42-
@Schema(description = "활동 희망사항", example = "동아리 활동 참여")
50+
@Schema(description = "활동 희망사항", example = "동아리 활동 참여", nullable = true)
4351
private String activityWish;
4452

4553
@NotNull
4654
@Schema(description = "개인정보 처리방침 동의 여부", example = "true")
4755
private Boolean isPrivacyPolicyAgreed;
4856

57+
@JsonProperty(defaultValue = "false")
58+
@JsonSetter(nulls = Nulls.SKIP)
59+
@Schema(description = "지원서 덮어쓰기 확인 여부", example = "false", defaultValue = "false", nullable = true)
60+
private Boolean isOverwriteConfirmed = false;
61+
4962
public Applicant toEntity() {
5063
return Applicant.builder()
64+
.name(this.name)
5165
.studentNo(this.studentNo)
5266
.contact(this.contact)
5367
.email(this.email)

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dmu.dasom.api.domain.applicant.entity;
22

3+
import dmu.dasom.api.domain.applicant.dto.ApplicantCreateRequestDto;
34
import dmu.dasom.api.domain.applicant.dto.ApplicantDetailsResponseDto;
45
import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto;
56
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
@@ -9,6 +10,7 @@
910
import lombok.Builder;
1011
import lombok.Getter;
1112
import lombok.NoArgsConstructor;
13+
import org.hibernate.annotations.DynamicUpdate;
1214
import org.springframework.data.annotation.CreatedDate;
1315
import org.springframework.data.annotation.LastModifiedDate;
1416
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@@ -17,6 +19,7 @@
1719

1820
@AllArgsConstructor
1921
@Builder
22+
@DynamicUpdate
2023
@Entity
2124
@EntityListeners(AuditingEntityListener.class)
2225
@Getter
@@ -32,7 +35,7 @@ public class Applicant {
3235
@Size(max = 16)
3336
private String name;
3437

35-
@Column(name = "student_no", nullable = false, length = 8)
38+
@Column(name = "student_no", nullable = false, unique = true, length = 8)
3639
@Pattern(regexp = "^[0-9]{8}$")
3740
@Size(min = 8, max = 8)
3841
private String studentNo;
@@ -105,4 +108,14 @@ public ApplicantDetailsResponseDto toApplicantDetailsResponse() {
105108
.build();
106109
}
107110

111+
public void overwrite(final ApplicantCreateRequestDto request) {
112+
this.name = request.getName();
113+
this.contact = request.getContact();
114+
this.email = request.getEmail();
115+
this.grade = request.getGrade();
116+
this.reasonForApply = request.getReasonForApply();
117+
this.activityWish = request.getActivityWish();
118+
this.isPrivacyPolicyAgreed = request.getIsPrivacyPolicyAgreed();
119+
}
120+
108121
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
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;
11+
12+
import java.util.Optional;
13+
914
public interface ApplicantRepository extends JpaRepository<Applicant, Long> {
1015

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

19+
// 상태별 지원자 조회
20+
List<Applicant> findByStatus(ApplicantStatus status);
21+
List<Applicant> findByStatusIn(List<ApplicantStatus> statuses);
22+
23+
Optional<Applicant> findByStudentNo(final String studentNo);
24+
1425
}

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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,53 @@
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.global.dto.PageResponse;
15+
import jakarta.mail.MessagingException;
1216
import lombok.RequiredArgsConstructor;
17+
import lombok.extern.slf4j.Slf4j;
1318
import org.springframework.data.domain.Page;
1419
import org.springframework.data.domain.PageRequest;
1520
import org.springframework.stereotype.Service;
21+
import org.springframework.transaction.annotation.Transactional;
1622

23+
import java.util.Optional;
24+
25+
import java.util.List;
26+
27+
@Slf4j
1728
@RequiredArgsConstructor
1829
@Service
30+
@Transactional
1931
public class ApplicantServiceImpl implements ApplicantService {
2032

2133
private final static int DEFAULT_PAGE_SIZE = 20;
2234

2335
private final ApplicantRepository applicantRepository;
36+
private final EmailService emailService;
2437

2538
// 지원자 저장
2639
@Override
2740
public void apply(final ApplicantCreateRequestDto request) {
41+
final Optional<Applicant> applicant = findByStudentNo(request.getStudentNo());
42+
43+
// 이미 지원한 학번이 존재할 경우
44+
if (applicant.isPresent()) {
45+
// 덮어쓰기 확인 여부가 false일 경우 예외 발생
46+
if (!request.getIsOverwriteConfirmed())
47+
throw new CustomException(ErrorCode.DUPLICATED_STUDENT_NO);
48+
49+
// 기존 지원자 정보 갱신 수행
50+
applicant.get().overwrite(request);
51+
return;
52+
}
53+
54+
// 새로운 지원자일 경우 저장
2855
applicantRepository.save(request.toEntity());
2956
}
3057

@@ -55,10 +82,51 @@ public ApplicantDetailsResponseDto updateApplicantStatus(final Long id, final Ap
5582
return applicant.toApplicantDetailsResponse();
5683
}
5784

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

127+
// 학번으로 지원자 존재 여부 확인
128+
private Optional<Applicant> findByStudentNo(final String studentNo) {
129+
return applicantRepository.findByStudentNo(studentNo);
130+
}
131+
64132
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ public enum ErrorCode {
1818
INTERNAL_SERVER_ERROR(500, "C009", "서버에 문제가 발생하였습니다."),
1919
NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."),
2020
WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."),
21-
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다.")
21+
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."),
22+
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", "조회 기간이 아닙니다.")
2228
;
2329

2430
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+
}

src/main/java/dmu/dasom/api/domain/member/controller/MemberController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public ResponseEntity<Void> tokenRotation(@AuthenticationPrincipal final UserDet
6464
final HttpHeaders headers = new HttpHeaders();
6565
headers.add("Access-Token", tokenBox.getAccessToken());
6666
headers.add("Refresh-Token", tokenBox.getRefreshToken());
67+
headers.add("Authority", tokenBox.getAuthority());
6768

6869
return ResponseEntity.ok().headers(headers).build();
6970
}

0 commit comments

Comments
 (0)