diff --git a/src/main/java/com/waruru/areyouhere/active/service/ActiveSessionServiceImpl.java b/src/main/java/com/waruru/areyouhere/active/service/ActiveSessionServiceImpl.java index 5ab1cd7..a3a7ae3 100644 --- a/src/main/java/com/waruru/areyouhere/active/service/ActiveSessionServiceImpl.java +++ b/src/main/java/com/waruru/areyouhere/active/service/ActiveSessionServiceImpl.java @@ -31,6 +31,7 @@ public String activate(Long sessionId, Long courseId) { Course course = courseService.get(courseId); Session session = sessionQueryService.get(sessionId); LocalDateTime currentTime = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + sessionCommandService.setAuthCodeDate(session, currentTime); return activeAttendanceService.activate(course, session, currentTime); } diff --git a/src/main/java/com/waruru/areyouhere/auth/interceptor/LoginInterceptor.java b/src/main/java/com/waruru/areyouhere/auth/interceptor/LoginInterceptor.java index 20634e8..61a2b26 100644 --- a/src/main/java/com/waruru/areyouhere/auth/interceptor/LoginInterceptor.java +++ b/src/main/java/com/waruru/areyouhere/auth/interceptor/LoginInterceptor.java @@ -33,7 +33,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons // TODO: 인증과 인가는 별개이다. 인가 중에서도 각 user가 어디 record에 접근권한을 가질 지 제어하는 공통된 로직을 고민해 봐야 한다. // TODO } catch (UnAuthenticatedException e) { - request.getRequestDispatcher("/api/manager/unauthorized").forward(request, response); + request.getRequestDispatcher("/api/auth/unauthorized").forward(request, response); return false; } } diff --git a/src/main/java/com/waruru/areyouhere/auth/session/SessionManager.java b/src/main/java/com/waruru/areyouhere/auth/session/SessionManager.java index 51332ec..630bd9e 100644 --- a/src/main/java/com/waruru/areyouhere/auth/session/SessionManager.java +++ b/src/main/java/com/waruru/areyouhere/auth/session/SessionManager.java @@ -17,17 +17,10 @@ public class SessionManager { private final HttpSession httpSession; - private final CourseRepository courseRepository; private static final String LOG_ID = "logId"; public void createSession(Long managerId){ - List courses = courseRepository.findAllByManagerId(managerId); - - List courseIds = courses == null || courses.isEmpty() ? - Collections.emptyList() - : courses.stream().map(course -> course.getManager().getId()).toList(); - LoginUser loginUser = new LoginUser(managerId); httpSession.setAttribute(LOG_ID, loginUser); } diff --git a/src/main/java/com/waruru/areyouhere/common/config/EmailConfig.java b/src/main/java/com/waruru/areyouhere/common/config/EmailConfig.java deleted file mode 100644 index ecd4708..0000000 --- a/src/main/java/com/waruru/areyouhere/common/config/EmailConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.waruru.areyouhere.common.config; - -import java.util.Properties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.JavaMailSenderImpl; - - -@Configuration -@ConfigurationProperties(prefix = "spring.mail") -public class EmailConfig { - private String username; - private String password; - private String host; - private int port; - - @Profile({"develop", "release"}) - @Bean - public JavaMailSender getJavaMailSender() { - JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost(host); - mailSender.setUsername(username); - mailSender.setPassword(password); - mailSender.setPort(port); - - Properties javaMailProperties = new Properties(); - javaMailProperties.put("mail.smtp.auth", "true"); - javaMailProperties.put("mail.smtp.starttls.enable", "true"); - javaMailProperties.put("mail.smtp.starttls.required", "true"); - - mailSender.setJavaMailProperties(javaMailProperties); - - return mailSender; - } - - @Profile("local") - @Bean - public JavaMailSender getLocalJavaMailSender() { - return new JavaMailSenderImpl(); - } -} diff --git a/src/main/java/com/waruru/areyouhere/common/error/GlobalExceptionHandler.java b/src/main/java/com/waruru/areyouhere/common/error/GlobalExceptionHandler.java index 8ef636e..687adbc 100644 --- a/src/main/java/com/waruru/areyouhere/common/error/GlobalExceptionHandler.java +++ b/src/main/java/com/waruru/areyouhere/common/error/GlobalExceptionHandler.java @@ -1,23 +1,28 @@ package com.waruru.areyouhere.common.error; +import static com.waruru.areyouhere.common.utils.HttpStatusResponseEntity.RESPONSE_NOT_FOUND; + import com.waruru.areyouhere.common.annotation.SlackNotification; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; -import net.gpedro.integrations.slack.SlackApi; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handle404Exception(HttpServletRequest request, Exception e){ + return RESPONSE_NOT_FOUND; + } + @SlackNotification @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) diff --git a/src/main/java/com/waruru/areyouhere/common/utils/HttpStatusResponseEntity.java b/src/main/java/com/waruru/areyouhere/common/utils/HttpStatusResponseEntity.java index e636f9e..9c4af6e 100644 --- a/src/main/java/com/waruru/areyouhere/common/utils/HttpStatusResponseEntity.java +++ b/src/main/java/com/waruru/areyouhere/common/utils/HttpStatusResponseEntity.java @@ -13,4 +13,5 @@ public class HttpStatusResponseEntity { public static final ResponseEntity RESPONSE_UNAUTHORIZED = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); public static final ResponseEntity RESPONSE_FORBIDDEN = ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + public static final ResponseEntity RESPONSE_INTERNAL_ERROR = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } diff --git a/src/main/java/com/waruru/areyouhere/course/domain/repository/CourseRepository.java b/src/main/java/com/waruru/areyouhere/course/domain/repository/CourseRepository.java index ceda2ee..3896c36 100644 --- a/src/main/java/com/waruru/areyouhere/course/domain/repository/CourseRepository.java +++ b/src/main/java/com/waruru/areyouhere/course/domain/repository/CourseRepository.java @@ -4,8 +4,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CourseRepository extends JpaRepository { - List findAllByManagerId(Long managerId); + + @Query("SELECT c FROM course c WHERE c.manager.id = :managerId") + List findAllByManagerId(@Param("managerId")Long managerId); } diff --git a/src/main/java/com/waruru/areyouhere/course/service/CourseServiceImpl.java b/src/main/java/com/waruru/areyouhere/course/service/CourseServiceImpl.java index fd92903..8ade8c1 100644 --- a/src/main/java/com/waruru/areyouhere/course/service/CourseServiceImpl.java +++ b/src/main/java/com/waruru/areyouhere/course/service/CourseServiceImpl.java @@ -11,6 +11,8 @@ import com.waruru.areyouhere.course.domain.repository.CourseRepository; import com.waruru.areyouhere.attendee.exception.AttendeesNotUniqueException; import com.waruru.areyouhere.course.dto.CourseData; +import com.waruru.areyouhere.course.exception.CourseNotFoundException; +import com.waruru.areyouhere.course.exception.UnauthorizedManagerException; import com.waruru.areyouhere.manager.domain.entity.Manager; import com.waruru.areyouhere.manager.domain.repository.ManagerRepository; import com.waruru.areyouhere.session.domain.entity.Session; @@ -45,7 +47,7 @@ public class CourseServiceImpl implements CourseService { public void create(Long managerId, String name, String description, List attendees, boolean onlyListNameAllowed) { Manager manager = managerRepository.findManagerById(managerId) - .orElseThrow(() -> new IllegalArgumentException("Manager not found")); + .orElseThrow(() -> new UnauthorizedManagerException("Manager not found")); Course course = Course.builder() .manager(manager) @@ -97,10 +99,10 @@ public List getAll(Long managerId) { @Override public void update(Long managerId, Long courseId, String name, String description, boolean onlyListNameAllowed) { Course course = courseRepository.findById(courseId). - orElseThrow(() -> new IllegalArgumentException("Course not found")); + orElseThrow(() -> new CourseNotFoundException("Course not found")); if (!course.getManager().getId().equals(managerId)) { - throw new IllegalArgumentException("Manager not authorized"); + throw new UnauthorizedManagerException("Manager not authorized"); } course.update(name, description, onlyListNameAllowed); @@ -115,10 +117,10 @@ public void delete(Long managerId, Long courseId) { } Course course = courseRepository.findById(courseId). - orElseThrow(() -> new IllegalArgumentException("Course not found")); + orElseThrow(() -> new CourseNotFoundException("Course not found")); if (!course.getManager().getId().equals(managerId)) { - throw new IllegalArgumentException("Manager not authorized"); + throw new UnauthorizedManagerException("Manager not authorized"); } List sessions = sessionRepository.findAllByCourseId(courseId); attendanceRepository.deleteAllBySessionIds(sessions.stream().map(Session::getId).toList()); @@ -127,11 +129,13 @@ public void delete(Long managerId, Long courseId) { courseRepository.delete(course); } + + @Override @Transactional(readOnly = true) public Course get(Long courseId) { return courseRepository.findById(courseId). - orElseThrow(() -> new IllegalArgumentException("Course not found")); + orElseThrow(() -> new CourseNotFoundException("Course not found")); } private boolean isAttendeesUnique(List attendees) { diff --git a/src/main/java/com/waruru/areyouhere/email/advice/EmailExceptionAdvice.java b/src/main/java/com/waruru/areyouhere/email/advice/EmailExceptionAdvice.java new file mode 100644 index 0000000..7c111c6 --- /dev/null +++ b/src/main/java/com/waruru/areyouhere/email/advice/EmailExceptionAdvice.java @@ -0,0 +1,29 @@ +package com.waruru.areyouhere.email.advice; + + +import com.waruru.areyouhere.common.annotation.SlackNotification; +import com.waruru.areyouhere.common.error.ErrorResponse; +import com.waruru.areyouhere.email.exception.EmailSendException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +// 이메일 전송이 안되는 것은 이메일 서버에 이상이 있으므로 slack에서 처리될 수 있도록 한다. +@RestControllerAdvice("com.waruru.areyouhere.email") +@Slf4j +public class EmailExceptionAdvice { + + @ExceptionHandler(EmailSendException.class) + @SlackNotification + public ErrorResponse emailSendException(HttpServletRequest request, Exception e){ + log.error("Internal Server Error", e); + StringBuilder sb = new StringBuilder(); + sb.append(request.getMethod()); + sb.append(request.getRemoteAddr()); + sb.append(request.getRequestURL()); + sb.append(e.getMessage()); + + return ErrorResponse.of("INTERNAL SERVER ERROR", sb.toString()); + } +} diff --git a/src/main/java/com/waruru/areyouhere/email/domain/MessageHolder.java b/src/main/java/com/waruru/areyouhere/email/domain/MessageHolder.java index 90b1d18..65dc85c 100644 --- a/src/main/java/com/waruru/areyouhere/email/domain/MessageHolder.java +++ b/src/main/java/com/waruru/areyouhere/email/domain/MessageHolder.java @@ -10,6 +10,6 @@ public class MessageHolder { public MessageHolder(String title, MessageTemplate messageTemplate, Object... values){ this.title = title; - this.contents = String.format(messageTemplate.getTemplate(), (Object) values); + this.contents = String.format(messageTemplate.getTemplate(), values); } } diff --git a/src/main/java/com/waruru/areyouhere/email/domain/MessageTemplate.java b/src/main/java/com/waruru/areyouhere/email/domain/MessageTemplate.java index 1668271..f02fc54 100644 --- a/src/main/java/com/waruru/areyouhere/email/domain/MessageTemplate.java +++ b/src/main/java/com/waruru/areyouhere/email/domain/MessageTemplate.java @@ -1,8 +1,8 @@ package com.waruru.areyouhere.email.domain; public enum MessageTemplate { - PASSWORD_RESET("You can reset password by this link: %s"), - SIGN_UP("You can verify your email by this link: %s"); + PASSWORD_RESET("You can reset password by this code: %s"), + SIGN_UP("You can verify your email by this code: %s"); private final String template; diff --git a/src/main/java/com/waruru/areyouhere/email/exception/EmailSendException.java b/src/main/java/com/waruru/areyouhere/email/exception/EmailSendException.java new file mode 100644 index 0000000..d076017 --- /dev/null +++ b/src/main/java/com/waruru/areyouhere/email/exception/EmailSendException.java @@ -0,0 +1,23 @@ +package com.waruru.areyouhere.email.exception; + +public class EmailSendException extends RuntimeException{ + + public EmailSendException() { + } + + public EmailSendException(String message) { + super(message); + } + + public EmailSendException(String message, Throwable cause) { + super(message, cause); + } + + public EmailSendException(Throwable cause) { + super(cause); + } + + public EmailSendException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/waruru/areyouhere/email/service/JavaEmailServiceImpl.java b/src/main/java/com/waruru/areyouhere/email/service/JavaEmailServiceImpl.java index 3d28efa..4619ab0 100644 --- a/src/main/java/com/waruru/areyouhere/email/service/JavaEmailServiceImpl.java +++ b/src/main/java/com/waruru/areyouhere/email/service/JavaEmailServiceImpl.java @@ -2,21 +2,36 @@ import com.waruru.areyouhere.email.domain.MessageHolder; import com.waruru.areyouhere.email.domain.MessageTemplate; +import com.waruru.areyouhere.email.exception.EmailSendException; +import jakarta.mail.Message.RecipientType; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import java.io.UnsupportedEncodingException; +import java.util.Properties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service @Slf4j public class JavaEmailServiceImpl implements EmailService { - private final JavaMailSender emailSender; @Value("${spring.mail.from}") private String from; + @Value("${spring.mail.username}") + private String username; + @Value("${spring.mail.password}") + private String password; + @Value("${spring.mail.host}") + private String host; + @Value("${spring.mail.port}") + private int port; + public void sendVerifyEmail(String to, String title, String verificationLink, MessageTemplate messageTemplate) { @@ -24,17 +39,55 @@ public void sendVerifyEmail(String to, String title, String verificationLink, Me sendSimpleMessage(to, messageHolder.getTitle(), messageHolder.getContents()); } - private void sendSimpleMessage(String to, String title, String content) { + private void sendSimpleMessage(String to, String title, String content) { if (from == null || from.isEmpty()) { log.info("Email text Test: " + content); return; } - SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom(from); - message.setTo(to); - message.setSubject(title); - message.setText(content); - emailSender.send(message); + + Session session = getSession(); + try { + MimeMessage msg = setMessage(to, from, content, session); + doSend(session, msg); + + } catch (MessagingException | UnsupportedEncodingException e) { + throw new EmailSendException(); + } + + + } + + private void doSend(Session session, MimeMessage msg) throws MessagingException { + Transport transport = session.getTransport(); + transport.connect(host, username, password); + transport.sendMessage(msg, msg.getAllRecipients()); + transport.close(); + } + + private MimeMessage setMessage(String to, String title, String content, Session session) + throws MessagingException, UnsupportedEncodingException { + MimeMessage msg = new MimeMessage(session); + msg.setFrom(new InternetAddress(from, from)); + InternetAddress internetAddress = new InternetAddress(to); + internetAddress.validate(); + msg.setRecipient(RecipientType.TO, internetAddress); + msg.setSubject(title); + msg.setContent(content, "text/html"); + msg.setHeader("X-SES-CONFIGURATION-SET", "ConfigSet"); + return msg; + } + + private Session getSession(){ + Properties emailProps = System.getProperties(); + emailProps.put("mail.transport.protocol", "smtp"); + emailProps.put("mail.smtp.port", port); + emailProps.put("mail.smtp.starttls.required", "true"); + emailProps.put("mail.smtp.auth.login.disable", "true"); + emailProps.put("mail.smtp.starttls.enable", "true"); + emailProps.put("mail.smtp.auth", "true"); + + return Session.getDefaultInstance(emailProps); + } } diff --git a/src/main/java/com/waruru/areyouhere/manager/controller/ManagerController.java b/src/main/java/com/waruru/areyouhere/manager/controller/ManagerController.java index 9fd61b3..c0dfafe 100644 --- a/src/main/java/com/waruru/areyouhere/manager/controller/ManagerController.java +++ b/src/main/java/com/waruru/areyouhere/manager/controller/ManagerController.java @@ -101,13 +101,13 @@ public ResponseEntity delete(@Login Manager manager) { @GetMapping("/email") public ResponseEntity sendSignUpEmail(@RequestParam String email) { managerService.sendEmailForSignUp(email); - return RESPONSE_BAD_REQUEST; + return RESPONSE_OK; } @GetMapping("/password") public ResponseEntity sendPasswordEmail(@RequestParam String email) { managerService.sendEmailForPasswordReset(email); - return RESPONSE_BAD_REQUEST; + return RESPONSE_OK; } @PostMapping("/verification") diff --git a/src/main/java/com/waruru/areyouhere/manager/service/SessionManagerService.java b/src/main/java/com/waruru/areyouhere/manager/service/SessionManagerService.java index bb89310..d101a5e 100644 --- a/src/main/java/com/waruru/areyouhere/manager/service/SessionManagerService.java +++ b/src/main/java/com/waruru/areyouhere/manager/service/SessionManagerService.java @@ -2,6 +2,7 @@ import com.waruru.areyouhere.auth.entity.LoginUser; import com.waruru.areyouhere.auth.session.SessionManager; +import com.waruru.areyouhere.course.domain.entity.Course; import com.waruru.areyouhere.email.domain.MessageTemplate; import com.waruru.areyouhere.email.service.EmailService; import com.waruru.areyouhere.course.domain.repository.CourseRepository; @@ -11,14 +12,18 @@ import com.waruru.areyouhere.manager.domain.repository.VerifyCodeRepository; import com.waruru.areyouhere.manager.exception.DuplicatedEmailException; import com.waruru.areyouhere.manager.exception.UnAuthenticatedException; +import jakarta.persistence.EntityManager; +import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor +@Slf4j public class SessionManagerService implements ManagerService { // TODO: refactor: 계층 구조 위반 private final CourseService courseService; @@ -27,6 +32,7 @@ public class SessionManagerService implements ManagerService { private final CourseRepository courseRepository; private final ManagerRepository managerRepository; private final VerifyCodeRepository verifyCodeRepository; + private final EntityManager entityManager; private final SessionManager sessionManager; private final PasswordEncoder passwordEncoder; @@ -58,9 +64,10 @@ public void signUp(String email, String password, String nickname) { boolean isEmailDuplicated = isDuplicatedEmail(email); -// if (!verifyCodeRepository.isVerified(email)) { -// throw new UnAuthenticatedException("이메일 인증을 완료해주세요."); -// } + if (!verifyCodeRepository.isVerified(email)) { + throw new UnAuthenticatedException("이메일 인증을 완료해주세요."); + } + if (isEmailDuplicated) { @@ -75,7 +82,9 @@ public void signUp(String email, String password, String nickname) { ); -// verifyCodeRepository.deleteByEmail(email); + + verifyCodeRepository.deleteByEmail(email); + sessionManager.createSession(manager.getId()); } @@ -109,9 +118,13 @@ public void update(Long userId, String name, String password) { @Override @Transactional public void delete(Long userId) { - courseRepository.findAllByManagerId(userId).forEach(course -> courseService.delete(userId, course.getId())); - sessionManager.removeSession(); + courseRepository.findAllByManagerId(userId).forEach(course -> { + courseService.delete(userId, course.getId()); + entityManager.flush(); + }); + managerRepository.deleteById(userId); + sessionManager.removeSession(); } @Override diff --git a/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandService.java b/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandService.java index f84b698..cdc1386 100644 --- a/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandService.java +++ b/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandService.java @@ -1,5 +1,6 @@ package com.waruru.areyouhere.session.service.command; +import com.waruru.areyouhere.session.domain.entity.Session; import com.waruru.areyouhere.session.service.dto.UpdateSession; import java.time.LocalDateTime; import java.util.List; @@ -13,5 +14,7 @@ public interface SessionCommandService { public void deactivate(Long sessionId); + public void setAuthCodeDate(Session session, LocalDateTime date); + public void updateAll(List sessions); } diff --git a/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandServiceImpl.java b/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandServiceImpl.java index 86edb1b..16448e0 100644 --- a/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandServiceImpl.java +++ b/src/main/java/com/waruru/areyouhere/session/service/command/SessionCommandServiceImpl.java @@ -13,6 +13,8 @@ import com.waruru.areyouhere.session.exception.CurrentSessionNotFoundException; import com.waruru.areyouhere.session.exception.SessionIdNotFoundException; import com.waruru.areyouhere.session.service.dto.UpdateSession; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -80,6 +82,11 @@ public void deactivate(Long sessionId) { sessionRepository.save(session); } + @Override + public void setAuthCodeDate(Session session, LocalDateTime date){ + session.setAuthCodeCreatedAt(date); + sessionRepository.save(session); + } @Override public void updateAll(List sessions) {