Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f6bfc5f
deploy: CI/CD Test (#90)
shinheekim Jan 2, 2025
ca8f446
refactor: 뉴스 관련 코드 최적화 (#102)
junseok708 Jan 2, 2025
b77160b
refactor: 메일 발송 로직이 비동기로 돌아가게 변경 (#104)
SeonJuuuun Jan 2, 2025
1aabf9b
feat: 관리자 뉴스 히스토리 조회 (#107)
Hamiwood Jan 2, 2025
815835c
feat: 관리자 블로그 히스토리 조회 (#109)
Hamiwood Jan 2, 2025
948c5ac
refactor: 이메일 템플릿 수정 및 프롬프트 수정 (#106)
SeonJuuuun Jan 2, 2025
41b2bd5
refactor: 히스토리 테스트 Mock으로 변경 (#112)
Hamiwood Jan 2, 2025
f7f2027
fix: import 와일드카드로 수정
shinheekim Jan 2, 2025
d028e0c
Merge branch 'dev' of https://github.com/crawling-project-crowrong/it…
shinheekim Jan 3, 2025
3434f90
docs: README.md 프로젝트 소개 업로드
shinheekim Jan 3, 2025
c66bc2d
docs: 이미지 재업로드 README.md
shinheekim Jan 3, 2025
642cabd
feat: 메일 인증 기능 구현 (#116)
SeonJuuuun Jan 3, 2025
6bedc17
feat: 뉴스 히스토리 CSV 파일 다운로드 기능 구현 (#117)
Hamiwood Jan 3, 2025
a2d379a
docs: 불필요한 숫자 삭제
shinheekim Jan 3, 2025
df52119
Merge branch 'dev' of https://github.com/crawling-project-crowrong/it…
shinheekim Jan 3, 2025
3d8c409
feat: 블로그 히스토리 CSV 파일 다운로드 기능 구현 (#119)
Hamiwood Jan 3, 2025
a3de414
feat: MailEvents 엔티티 생성
SeonJuuuun Jan 3, 2025
cbdfd0b
Merge remote-tracking branch 'origin/dev' into dev
SeonJuuuun Jan 3, 2025
fdb6b43
feat: 메일 실패시 재발송 로직 및 MailEvents에 저장 기능 구현 (#126)
SeonJuuuun Jan 4, 2025
8ac0049
feat: 관리자 메일 조회 구현 (#127)
Hamiwood Jan 6, 2025
6a07b9c
deploy: 배포 작업 마무리(log쪽 에러 해결) (#135)
shinheekim Jan 6, 2025
8d33ce0
Merge branch 'main' into dev
junseok708 Jan 6, 2025
dc5b3fa
feat: 뉴스 히스토리 CSV 파일 메일 발송 구현 (#121)
Hamiwood Jan 6, 2025
eae3a79
feat: 블로그 히스토리 CSV 파일 메일 발송 구현 (#123)
Hamiwood Jan 6, 2025
4f500e2
feat: 메일 재 발송 실패 시 Slack 알림 구축 (#132)
junseok708 Jan 6, 2025
98ce9bd
feat: 관리자 실패 메일 재 발송 (#133)
Hamiwood Jan 6, 2025
f913553
feat: 기본 main.html 추가 (#130)
SeonJuuuun Jan 6, 2025
90daf04
refactor: 누락된 html 수정
SeonJuuuun Jan 6, 2025
6d76164
refactor: getContent -> getOriginalContent 변경, 불 필요한 import 수정
SeonJuuuun Jan 6, 2025
f80f5b7
[#dev] fix: 없는 import 삭제
junseok708 Jan 6, 2025
366b672
feat: 블로그/뉴스 문자 전송 기능 추가 (#134)
seoyeong-4811 Jan 6, 2025
90c2445
feat: 인증번호 문자 전송 (#128)
seoyeong-4811 Jan 6, 2025
d3a8d62
deploy: 추가된 환경변수 설정 (#137)
shinheekim Jan 6, 2025
527ebb9
Merge branch 'main' into dev
SeonJuuuun Jan 6, 2025
a649e95
fix: 잘못된거 수정
SeonJuuuun Jan 6, 2025
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
14 changes: 14 additions & 0 deletions .github/workflows/itcast-build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
spring.datasource.url: ${{ secrets.MYSQL_URL }}
spring.datasource.username: ${{ secrets.DB_USERNAME }}
spring.datasource.password: ${{ secrets.DB_PASSWORD }}
slack.token: ${{ secrets.SLACK_TOKEN }}
slack.channel.monitor: ${{ secrets.SLACK_CHANNEL_MONITOR }}

- name: Set Admin Yaml
uses: microsoft/variable-substitution@v1
Expand All @@ -49,6 +51,10 @@ jobs:
aws.ses.secret-key: ${{ secrets.AWS_SES_SECRET_KEY }}
aws.ses.sender-email: ${{ secrets.AWS_SES_SENDER_EMAIL }}
jwt.secret.key: ${{ secrets.JWT_SECRET_KEY }}
mail.username: ${{ secrets.ADMIN_MAIL }}
mail.password: ${{ secrets.ADMIN_MAIL_PASSWORD }}
slack.token: ${{ secrets.SLACK_TOKEN }}
slack.channel.monitor: ${{ secrets.SLACK_CHANNEL_MONITOR }}

- name: Set B2C Yaml
uses: microsoft/variable-substitution@v1
Expand All @@ -63,6 +69,11 @@ jobs:
aws.ses.sender-email: ${{ secrets.AWS_SES_SENDER_EMAIL }}
spring.kakao.client-id: ${{ secrets.KAKAO_CLIENT_ID }}
jwt.secret.key: ${{ secrets.JWT_SECRET_KEY }}
sms.api.key: ${{ secrets.SMS_API_KEY }}
sms.api.secret: ${{ secrets.SMS_API_SECRET }}
sms.sender.phone: ${{ secrets.SMS_SENDER_PHONE }}
slack.token: ${{ secrets.SLACK_TOKEN }}
slack.channel.monitor: ${{ secrets.SLACK_CHANNEL_MONITOR }}

- name: Set Schedule Yaml
uses: microsoft/variable-substitution@v1
Expand All @@ -78,7 +89,10 @@ jobs:
jwt.secret.key: ${{ secrets.JWT_SECRET_KEY }}
sms.api.key: ${{ secrets.SMS_API_KEY }}
sms.api.secret: ${{ secrets.SMS_API_SECRET }}
sms.sender.phone: ${{ secrets.SMS_SENDER_PHONE }}
openai.secret-key: ${{ secrets.OPENAI_SECRET_KEY }}
slack.token: ${{ secrets.SLACK_TOKEN }}
slack.channel.monitor: ${{ secrets.SLACK_CHANNEL_MONITOR }}

- name: Install Docker Compose
run: |
Expand Down
5 changes: 4 additions & 1 deletion admin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

//csv 작업
implementation 'com.opencsv:opencsv:5.9'
implementation 'org.springframework.boot:spring-boot-starter-mail'

implementation 'com.amazonaws:aws-java-sdk-ses:1.12.3'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
import itcast.jwt.repository.UserRepository;
import itcast.repository.AdminRepository;
import itcast.repository.BlogHistoryRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.io.StringWriter;
Expand All @@ -25,6 +30,7 @@ public class AdminBlogHistoryService {
private final UserRepository userRepository;
private final AdminRepository adminRepository;
private final BlogHistoryRepository blogHistoryRepository;
private final JavaMailSender mailSender;

public Page<AdminBlogHistoryResponse> retrieveBlogHistory(Long adminId, Long userId, Long blogId, LocalDate createdAt,
int page, int size
Expand Down Expand Up @@ -63,6 +69,24 @@ public String createCsvFile(Long adminId, Long userId, Long blogId, LocalDate st
return stringWriter.toString();
}

public void sendEmail(byte[] csvFile) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

String fileName = "BlogHistory_File(" + LocalDate.now() + ").csv";
String title = "[관리자 전용 발신] 블로그 히스토리 CSV 파일";
String content = "첨부된 CSV 파일을 확인해주십시오.";
String to = "[email protected]";

helper.setFrom("[email protected]");
helper.setTo(to);
helper.setSubject(title);
helper.setText(content, false);

helper.addAttachment(fileName, new ByteArrayResource(csvFile));
mailSender.send(message);
}

private void isAdmin(Long id) {
User user = userRepository.findById(id).orElseThrow(() ->
new ItCastApplicationException(ErrorCodes.USER_NOT_FOUND));
Expand Down
65 changes: 65 additions & 0 deletions admin/src/main/java/itcast/application/AdminEmailSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package itcast.application;

import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;
import itcast.dto.request.AdminSendMailRequest;
import itcast.mail.dto.request.SendMailRequest;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Getter
@Component
@RequiredArgsConstructor
public class AdminEmailSender {

private static final String MAIL_SUBJECT = "[IT-Cast 뉴스레터] 오늘의 인기 글을 확인해보세요~🔖";
private final AmazonSimpleEmailService amazonSimpleEmailService;

@Value("${aws.ses.sender-email}")
private String senderEmail;

private final TemplateEngine templateEngine;

public void send(AdminSendMailRequest request) {
final SendEmailRequest emailRequest = from(request, request.getReceiver());
amazonSimpleEmailService.sendEmail(emailRequest);
}

private SendEmailRequest from(final AdminSendMailRequest request, final String receiver) {
final Destination destination = new Destination()
.withToAddresses(receiver);

final Message message = new Message()
.withSubject(createContent(MAIL_SUBJECT))
.withBody(new Body()
.withHtml(createContent(createHtmlBody(request))));

return new SendEmailRequest()
.withSource(senderEmail)
.withDestination(destination)
.withMessage(message);
}

private Content createContent(final String text) {
return new Content()
.withCharset("UTF-8")
.withData(text);
}

private String createHtmlBody(final AdminSendMailRequest request) {
final Context context = new Context();
context.setVariable("sender", senderEmail);
context.setVariable("subject", MAIL_SUBJECT);
context.setVariable("contents", request.getContents());

return templateEngine.process("email-template", context);
}
}
53 changes: 46 additions & 7 deletions admin/src/main/java/itcast/application/AdminMailService.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
package itcast.application;

import itcast.domain.mailEvent.MailEvents;
import itcast.dto.request.AdminSendMailRequest;
import itcast.dto.response.MailResponse;
import itcast.exception.ErrorCodes;
import itcast.exception.ItCastApplicationException;
import itcast.jwt.repository.UserRepository;
import itcast.mail.repository.MailEventsRepository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class AdminMailService {

private final MailEventsRepository mailEventsRepository;
private final AdminCheckService adminCheckService;
private final AdminEmailSender adminEmailSender;
private final UserRepository userRepository;

public Page<MailResponse> retrieveMailEvent(Long adminId, int page, int size) {
adminCheckService.isAdmin(adminId);
Expand Down Expand Up @@ -56,4 +62,37 @@ public Page<MailResponse> retrieveMailEvent(Long adminId, int page, int size) {

return new PageImpl<>(mailResponses, pageable, mailEventsPage.getTotalElements());
}
}

public void sendMailEvent(Long adminId, Long userId, LocalDate createdAt) {
adminCheckService.isAdmin(adminId);

LocalDateTime startOfDay = createdAt.atStartOfDay();
LocalDateTime endOfDay = createdAt.atTime(23, 59, 59);

List<MailEvents> mailEvents = mailEventsRepository.findByUserIdAndCreatedAtBetween(userId, startOfDay,
endOfDay);
if (mailEvents.isEmpty()) {
throw new ItCastApplicationException(ErrorCodes.EMAIL_EVENT_NOT_FOUND);
}

String userEmail = userRepository.findEmailById(userId)
.orElseThrow(() -> new ItCastApplicationException(ErrorCodes.USER_EMAIL_NOT_FOUND));

List<AdminSendMailRequest.MailContent> mailContents = mailEvents.stream()
.map(event -> new AdminSendMailRequest.MailContent(
event.getId(),
event.getTitle(),
event.getSummary(),
event.getOriginalLink(),
event.getThumbnail()
))
.collect(Collectors.toList());

AdminSendMailRequest sendMailRequest = new AdminSendMailRequest(
userEmail,
mailContents
);

adminEmailSender.send(sendMailRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
package itcast.application;

import com.opencsv.CSVWriter;
import itcast.domain.newsHistory.NewsHistory;
import itcast.domain.user.User;
import itcast.dto.response.AdminNewsHistoryResponse;
import itcast.exception.ErrorCodes;
import itcast.exception.ItCastApplicationException;
import itcast.jwt.repository.UserRepository;
import itcast.repository.AdminRepository;
import itcast.repository.NewsHistoryRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.io.StringWriter;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.io.StringWriter;
import java.time.LocalDate;
import java.util.List;

@Service
@RequiredArgsConstructor
public class AdminNewsHistoryService {

private final UserRepository userRepository;
private final AdminRepository adminRepository;
private final NewsHistoryRepository newsHistoryRepository;
private final JavaMailSender mailSender;

public Page<AdminNewsHistoryResponse> retrieveNewsHistory(Long adminId, Long userId, Long newsId, LocalDate createdAt,
public Page<AdminNewsHistoryResponse> retrieveNewsHistory(Long adminId, Long userId, Long newsId,
LocalDate createdAt,
int page, int size
) {
isAdmin(adminId);
Expand All @@ -37,7 +42,8 @@ public Page<AdminNewsHistoryResponse> retrieveNewsHistory(Long adminId, Long use

public String createCsvFile(Long adminId, Long userId, Long newsId, LocalDate startAt, LocalDate endAt) {
isAdmin(adminId);
List<AdminNewsHistoryResponse> newsHistoryList = newsHistoryRepository.downloadNewsHistoryListByCondition(userId, newsId, startAt, endAt);
List<AdminNewsHistoryResponse> newsHistoryList = newsHistoryRepository.downloadNewsHistoryListByCondition(
userId, newsId, startAt, endAt);
StringWriter stringWriter = new StringWriter();
CSVWriter csvWriter = new CSVWriter(stringWriter);

Expand All @@ -64,6 +70,24 @@ public String createCsvFile(Long adminId, Long userId, Long newsId, LocalDate st
return stringWriter.toString();
}

public void sendEmail(byte[] csvFile) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

String fileName = "NewsHistory_File(" + LocalDate.now() + ").csv";
String title = "[관리자 전용 발신] 뉴스 히스토리 CSV 파일";
String content = "첨부된 CSV 파일을 확인해주십시오.";
String to = "[email protected]";

helper.setFrom("[email protected]");
helper.setTo(to);
helper.setSubject(title);
helper.setText(content, false);

helper.addAttachment(fileName, new ByteArrayResource(csvFile));
mailSender.send(message);
}

private void isAdmin(Long id) {
User user = userRepository.findById(id).orElseThrow(() ->
new ItCastApplicationException(ErrorCodes.USER_NOT_FOUND));
Expand Down
34 changes: 34 additions & 0 deletions admin/src/main/java/itcast/config/MailConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package itcast.config;

import itcast.dto.request.MailProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;

@Configuration
@RequiredArgsConstructor
public class MailConfig {

private final MailProperties mailProperties;

@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(mailProperties.getHost());
mailSender.setPort(mailProperties.getPort());
mailSender.setUsername(mailProperties.getUsername());
mailSender.setPassword(mailProperties.getPassword());

Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", String.valueOf(mailProperties.getProperties().isAuth()));
props.put("mail.smtp.ssl.enable", String.valueOf(mailProperties.getProperties().isSslEnable()));
props.put("mail.smtp.starttls.enable", String.valueOf(mailProperties.getProperties().isStarttlsEnable()));
props.put("mail.debug", "true");

return mailSender;
}
}
Loading
Loading