Skip to content

Commit

Permalink
Merge pull request GDSC-CAU#30 from alsrudrl1220/feature/29
Browse files Browse the repository at this point in the history
[feat] 이미지 완성 알림 이메일 발송 기능 구현 (GDSC-CAU#29)
  • Loading branch information
win-luck authored Aug 11, 2024
2 parents e5c1215 + 04311a4 commit f1802c2
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/CD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
branches: [ "main" ]
types: [closed]
workflow_dispatch:
pull_request_target:
branches: [ "main" ]
types: [closed]

permissions:
contents: read
Expand Down
Binary file modified .gradle/8.8/checksums/checksums.lock
Binary file not shown.
Binary file modified .gradle/8.8/checksums/md5-checksums.bin
Binary file not shown.
Binary file modified .gradle/8.8/checksums/sha1-checksums.bin
Binary file not shown.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ dependencies {

// Local Cache
implementation 'org.springframework.boot:spring-boot-starter-cache'

// mail & thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ConstantUtil {

public static final String USER_ID_KEY = "photo-request-user";
public static final String GDSC_CAU_EMAIL = "GDSC CAU <[email protected]>";
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum ResponseCode {
// 500 Internal Server Error
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "서버에 오류가 발생하였습니다."),
JSON_PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "JSON 파싱 오류가 발생하였습니다."),
EMAIL_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "이메일 발송에 오류가 발생하였습니다."),

// 200 OK
USER_LOGIN_SUCCESS(HttpStatus.OK, true, "사용자 로그인 성공"),
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/gdsc/cau/puangbe/photo/dto/request/EmailInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gdsc.cau.puangbe.photo.dto.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmailInfo {
String email;
String name;
String photoUrl;
String framePageUrl;
}
45 changes: 42 additions & 3 deletions src/main/java/gdsc/cau/puangbe/photo/service/PhotoServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
import gdsc.cau.puangbe.common.exception.BaseException;
import gdsc.cau.puangbe.common.util.ConstantUtil;
import gdsc.cau.puangbe.common.util.ResponseCode;
import gdsc.cau.puangbe.photo.dto.request.EmailInfo;
import gdsc.cau.puangbe.photo.entity.PhotoRequest;
import gdsc.cau.puangbe.photo.entity.PhotoResult;
import gdsc.cau.puangbe.photo.repository.PhotoResultRepository;
import gdsc.cau.puangbe.photo.repository.PhotoRequestRepository;
import gdsc.cau.puangbe.user.entity.User;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Service
@RequiredArgsConstructor
Expand All @@ -23,6 +29,8 @@ public class PhotoServiceImpl implements PhotoService {
private final PhotoResultRepository photoResultRepository;
private final PhotoRequestRepository photoRequestRepository;
private final RedisTemplate<String, Long> redisTemplate;
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;

// 완성된 요청 id 및 imageUrl을 받아 저장
@Override
Expand All @@ -46,7 +54,14 @@ public void uploadPhoto(Long photoRequestId, String imageUrl) {
redisTemplate.delete(user.getId().toString());

// 이메일 발송
sendEmail(photoRequest.getEmail(), imageUrl);
EmailInfo emailInfo = EmailInfo.builder()
.email(photoRequest.getEmail())
.photoUrl(imageUrl)
.name(user.getUserName())
.framePageUrl("https://www.google.com/") // TODO : 프론트 분들 링크 관련 답변 오면 프레임 페이지 링크 관련 수정
.build();

sendEmail(emailInfo);
}

// 특정 요청의 imageUrl 조회
Expand All @@ -61,8 +76,32 @@ public String getPhotoUrl(Long photoRequestId) {
return photoResult.getImageUrl();
}

private void sendEmail(String email, String imageUrl) {
// TODO : 이메일 발송 로직
// 유저에게 이메일 전송
private void sendEmail(EmailInfo emailInfo) {
// 이메일 템플릿 내용 설정
Context context = new Context();
context.setVariable("userName", emailInfo.getName());
context.setVariable("photoUrl", emailInfo.getPhotoUrl());
context.setVariable("framePageUrl", emailInfo.getFramePageUrl());
String body = templateEngine.process("email-template", context);

try {
// 메일 정보 설정
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, "UTF-8");
messageHelper.setFrom(ConstantUtil.GDSC_CAU_EMAIL);
messageHelper.setTo(emailInfo.getEmail());
messageHelper.setSubject("[푸앙이 사진관] AI 프로필 사진 생성 완료");
messageHelper.setText(body, true);

// 메일 전송
mailSender.send(mimeMessage);

} catch (Exception e){
e.printStackTrace();
throw new BaseException(ResponseCode.EMAIL_SEND_ERROR);
}

}

private PhotoResult getPhotoResult(Long photoRequestId){
Expand Down
51 changes: 51 additions & 0 deletions src/main/resources/templates/email-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="kr" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>푸앙이 사진관 - 사진 생성 완료</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: Arial, sans-serif; color: #333;">
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width: 600px; margin: 0 auto; background: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
<!-- Top Logo -->
<tr>
<td style="text-align: center; padding: 20px;">
<img src="https://via.placeholder.com/300x100?text=Top+Logo" alt="Top Logo" style="max-width: 300px; height: auto; display: block; margin: 0 auto;">
</td>
</tr>

<!-- Content -->
<tr>
<td style="padding: 20px; text-align: center;">
<p style="margin: 0 0 40px 0;">안녕하세요, <strong th:text="${userName}">[사용자 이름]</strong>님!</p>
<p style="margin: 0 0 30px 0;">AI 프로필 사진이 성공적으로 생성되었어요😊</p>
<p style="margin: 0 0 10px 0;">AI 프로필에 프레임을 적용해서</p>
<p style="margin: 0 0 40px 0;">나만의 특별한 프로필을 만들어보세요✨</p>

<!-- AI Photo -->
<p style="margin: 0 0 30px 0;">
<img src="${photoUrl}" alt="AI 프로필 사진" style="max-width: 200px; border-radius: 8px; box-shadow: 0 0 5px rgba(0,0,0,0.2);">
</p>

<!-- Button -->
<p style="margin: 0 0 50px 0;">
<a href="#" th:href="@{${framePageUrl}}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007bff; text-decoration: none; border-radius: 5px;">프레임 적용해보기</a>
</p>
</td>
</tr>

<!-- Footer -->
<tr>
<td style="padding: 15px; text-align: center; font-size: 14px; color: #777;">
<p style="margin: 0;">GDSC CAU 푸앙이 사진관 드림</p>
</td>
</tr>

<!-- Bottom Logo -->
<tr>
<td style="text-align: center; padding: 5px;">
<img src="https://via.placeholder.com/300x100?text=Bottom+Logo" alt="Bottom Logo" style="max-width: 300px; height: auto; display: block; margin: 0 auto;">
</td>
</tr>
</table>
</body>
</html>

0 comments on commit f1802c2

Please sign in to comment.