Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ java {

repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}

dependencies {
Expand Down Expand Up @@ -44,10 +45,9 @@ dependencies {
implementation 'io.awspring.cloud:spring-cloud-aws-s3:3.1.0'
implementation 'javax.xml.bind:jaxb-api:2.3.1'

// mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // option
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' // option
// brevo
implementation 'com.sendinblue:sib-api-v3-sdk:7.0.0'


// lombok
implementation 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.writon.admin.domain.controller;

import com.writon.admin.domain.dto.request.auth.LoginRequestDto;
import com.writon.admin.domain.dto.request.auth.ReissueRequestDto;
import com.writon.admin.domain.dto.request.auth.SignUpRequestDto;
import com.writon.admin.domain.dto.response.auth.LoginResponseDto;
import com.writon.admin.domain.dto.response.auth.ReissueResponseDto;
import com.writon.admin.domain.dto.response.auth.SignUpResponseDto;
import com.writon.admin.domain.dto.wrapper.auth.LoginResponseWrapper;
import com.writon.admin.domain.service.AuthService;
import com.writon.admin.global.config.auth.CookieProvider;
import com.writon.admin.global.response.SuccessDto;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,9 @@
import com.writon.admin.global.error.ErrorCode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -92,10 +88,12 @@ public CreateChallengeResponseDto createChallenge(CreateChallengeRequestDto requ
}

// 5. 이메일 전송 & 정보 저장
for (String email : requestDto.getEmailList()) {
emailService.sendEmail(challenge, email);
emailRepository.save(new Email(email, challenge));
}
emailService.sendEmail(challenge, requestDto.getEmailList());

List<Email> emailEntities = requestDto.getEmailList().stream()
.map(email -> new Email(email, challenge))
.collect(Collectors.toList());
emailRepository.saveAll(emailEntities);

// 6. Response 생성
List<Challenge> challenges = challengeRepository.findByOrganizationId(organization.getId());
Expand Down Expand Up @@ -132,7 +130,7 @@ public List<UserStatus> getDashboard(Long challengeId) {
for (UserChallenge userChallenge : userChallengeList) {
List<Status> statusList = new ArrayList<>();
List<UserTemplate> userTemplateList = userTemplateRepository.findByUserChallengeId(
userChallenge.getId());
userChallenge.getId());

for (ChallengeDay challengeDay : challengeDayList) {
// 참여여부 확인과정
Expand Down
100 changes: 57 additions & 43 deletions src/main/java/com/writon/admin/domain/service/EmailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,84 @@
import com.writon.admin.domain.util.TokenUtil;
import com.writon.admin.global.error.CustomException;
import com.writon.admin.global.error.ErrorCode;
import jakarta.mail.internet.MimeMessage;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.thymeleaf.context.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.spring6.SpringTemplateEngine;
import sendinblue.ApiClient;
import sendinblue.ApiException;
import sendinblue.Configuration;
import sendinblue.auth.ApiKeyAuth;
import sibApi.TransactionalEmailsApi;
import sibModel.CreateSmtpEmail;
import sibModel.SendSmtpEmail;
import sibModel.SendSmtpEmailMessageVersions;
import sibModel.SendSmtpEmailTo1;

@Service
@RequiredArgsConstructor
@Slf4j
public class EmailService {

private final JavaMailSender javaMailSender;
private final SpringTemplateEngine templateEngine;
private final TokenUtil tokenUtil;

@Async
public void sendEmail(Challenge challenge, String email) {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
@Value("${email.apiKey}")
private String BREVO_API_KEY;

@Value("${email.templateId}")
private Long BREVO_TEMPLATE_ID;

public void sendEmail(Challenge challenge, List<String> emailList) {
Organization organization = tokenUtil.getOrganization();
String baseUrl = "https://www.writon.co.kr/login";
String link = String.format(
"%s?organization=%s&challengeId=%s", baseUrl,
encodeURIComponent(organization.getName()),
encodeURIComponent(String.valueOf(challenge.getId()))
);

try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
mimeMessageHelper.setTo(email);
mimeMessageHelper.setSubject(String.format(
"[Writon] %s의 챌린지에 참여해보세요",
organization.getName()
)); // 메일 제목
mimeMessageHelper.setText(
setContext(organization.getName(), challenge.getName(), challenge.getId(), email),
true
); // 메일 본문 내용, HTML 여부
javaMailSender.send(mimeMessage);
ApiClient defaultClient = Configuration.getDefaultApiClient();

log.info("Succeeded to send Email");
} catch (Exception e) {
log.info("Failed to send Email");
throw new CustomException(ErrorCode.EMAIL_SEND_FAILED);
}
}
ApiKeyAuth apiKey = (ApiKeyAuth) defaultClient.getAuthentication("api-key");
apiKey.setApiKey(BREVO_API_KEY);

//thymeleaf를 통한 html 적용
public String setContext(String organization, String challenge, Long challengeId, String email) {
Context context = new Context();
context.setVariable("organization", organization);
context.setVariable("challenge", challenge);
context.setVariable("email", email);
context.setVariable("challengeId", challengeId);
// 수신자 리스트 구성
List<SendSmtpEmailMessageVersions> messageVersions = new ArrayList<>();

String baseUrl = "https://www.writon.co.kr/login";
String link = String.format("%s?organization=%s&challengeId=%s", baseUrl,
encodeURIComponent(organization),
encodeURIComponent(String.valueOf(challengeId)));
context.setVariable("link", link);
for (String email : emailList) {
messageVersions.add(new SendSmtpEmailMessageVersions()
.to(List.of(new SendSmtpEmailTo1().email(email)))
.params(Map.of(
"ORGANIZATION", organization.getName(),
"CHALLENGE", challenge.getName(),
"EMAIL", email,
"LINK", link
)));
}

return templateEngine.process("participate_card", context);
TransactionalEmailsApi apiInstance = new TransactionalEmailsApi();
SendSmtpEmail sendSmtpEmail = new SendSmtpEmail()
// 템플릿 종류
.templateId(BREVO_TEMPLATE_ID)
// 동적 param값 설정
.messageVersions(messageVersions);

try {
CreateSmtpEmail result = apiInstance.sendTransacEmail(sendSmtpEmail);
log.info("Succeded to send Email: {}", result);
} catch (ApiException e) {
log.error("Failed to send Email");
throw new CustomException(ErrorCode.EMAIL_SEND_FAILED);
}
}

private String encodeURIComponent(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import com.writon.admin.domain.entity.organization.AdminUser;
import com.writon.admin.domain.entity.organization.Organization;
import com.writon.admin.domain.entity.organization.Position;
import com.writon.admin.domain.repository.organization.AdminUserRepository;
import com.writon.admin.domain.repository.organization.OrganizationRepository;
import com.writon.admin.domain.repository.organization.PositionRepository;
import com.writon.admin.domain.util.TokenUtil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ public List<String> participate(Long challengeId, List<String> emailList) {
Challenge challenge = challengeRepository.findById(challengeId)
.orElseThrow(() -> new CustomException(ErrorCode.CHALLENGE_NOT_FOUND));

for (String email : emailList) {
emailService.sendEmail(challenge, email);
emailRepository.save(new Email(email, challenge));
}
emailService.sendEmail(challenge, emailList);

List<Email> emailEntities = emailList.stream()
.map(email -> new Email(email, challenge))
.collect(Collectors.toList());
emailRepository.saveAll(emailEntities);

List<Email> sendedEmailList = emailRepository.findByChallengeId(challengeId);
if (sendedEmailList.isEmpty()) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/writon/admin/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public enum ErrorCode {
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "405", "허용되지 않은 메소드입니다"), // 405 Method Not Allowed
CONFLICT(HttpStatus.CONFLICT, "409", "이미 가입한 사용자입니다"), // 409 Conflict
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "서버에 오류가 발생하였습니다"), // 500 Internal Server Error
GATEWAY_TIMEOUT_ERROR(HttpStatus.GATEWAY_TIMEOUT, "504", "연결 시간을 초과하였습니다"), // 503 Gateway Timeout
ETC_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "0314", "사용자 지정 오류"),

// auth
Expand Down
Empty file added src/main/resources/.gitkeep
Empty file.
38 changes: 0 additions & 38 deletions src/main/resources/templates/participate_card.html

This file was deleted.