Skip to content

Commit

Permalink
refactor: 대규모 리펙토링 (#94)
Browse files Browse the repository at this point in the history
- 폴더 구조 변경
- api -> controller
- 팩토리 메서드 패턴 메서드 명 수정
- spring security 제거
- Custom Annotation을 활용한 역할 분리
  • Loading branch information
soomanbaek committed Oct 27, 2023
1 parent dcfbcbd commit 3e16ba7
Show file tree
Hide file tree
Showing 66 changed files with 779 additions and 863 deletions.
3 changes: 2 additions & 1 deletion src/main/java/blacktokkies/toquiz/ToquizApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class ToquizApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,73 @@
package blacktokkies.toquiz.domain.answer.application;

import blacktokkies.toquiz.domain.answer.dao.AnswerRepository;
import blacktokkies.toquiz.domain.answer.domain.AnswerRepository;
import blacktokkies.toquiz.domain.answer.domain.Answer;
import blacktokkies.toquiz.domain.answer.dto.request.CreateAnswerRequest;
import blacktokkies.toquiz.domain.answer.dto.response.AnswerResponse;
import blacktokkies.toquiz.domain.answer.dto.response.GetAnswersResponse;
import blacktokkies.toquiz.domain.answer.dto.CreateAnswerRequest;
import blacktokkies.toquiz.domain.answer.dto.AnswerResponse;
import blacktokkies.toquiz.domain.answer.dto.GetAnswersResponse;
import blacktokkies.toquiz.domain.member.domain.Member;
import blacktokkies.toquiz.domain.question.dao.QuestionRepository;
import blacktokkies.toquiz.domain.member.domain.MemberRepository;
import blacktokkies.toquiz.domain.question.domain.QuestionRepository;
import blacktokkies.toquiz.domain.question.domain.Question;
import blacktokkies.toquiz.global.common.annotation.auth.MemberEmailDto;
import blacktokkies.toquiz.global.common.error.RestApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

import static blacktokkies.toquiz.domain.auth.exception.AuthErrorCode.NOT_EXIST_MEMBER;
import static blacktokkies.toquiz.domain.panel.exception.PanelErrorCode.NOT_CREATOR_PANEL;
import static blacktokkies.toquiz.domain.question.exception.QuestionErrorCode.NOT_EXIST_QUESTION;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AnswerService {
private final MemberRepository memberRepository;
private final QuestionRepository questionRepository;
private final AnswerRepository answerRepository;

@Transactional
public AnswerResponse createAnswer(Long questionId, CreateAnswerRequest createAnswerRequest) {
public AnswerResponse createAnswer(
MemberEmailDto memberEmailDto,
Long questionId,
CreateAnswerRequest createAnswerRequest
) {
Question question = getQuestion(questionId);
checkIsAuthorizedToCreate(question);
checkIsAuthorizedToCreate(getMemberByEmail(memberEmailDto.email()), question);

Answer answer = new Answer(createAnswerRequest.getContent(), question);
question.addAnswer(answerRepository.save(answer));

return AnswerResponse.toDto(answer);
return AnswerResponse.from(answer);
}

public GetAnswersResponse getAnswers(Long questionId) {
Question question = getQuestion(questionId);
List<Answer> answers = answerRepository.findAllByQuestionOrderByCreatedDateDesc(question);
List<AnswerResponse> answerResponses = answers.stream().map(AnswerResponse::toDto).toList();
List<AnswerResponse> answerResponses = answers.stream().map(AnswerResponse::from).toList();

return GetAnswersResponse.toDto(question, answerResponses);
return GetAnswersResponse.of(question, answerResponses);
}

private Member getMember() {
return (Member) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// ------------ [엔티티 가져오는 메서드] ------------ //
private Member getMemberByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new RestApiException(NOT_EXIST_MEMBER));
}

private Question getQuestion(Long questionId){
return questionRepository.findById(questionId).orElseThrow(
()->new RestApiException(NOT_EXIST_QUESTION)
);
return questionRepository.findById(questionId)
.orElseThrow(()->new RestApiException(NOT_EXIST_QUESTION));
}

private void checkIsAuthorizedToCreate(Question question) {
// ---------------- [검증 메서드] ---------------- //
private void checkIsAuthorizedToCreate(Member member, Question question) {
Member panelCreator = question.getPanel().getMember();

Member member = getMember();

if(!Objects.equals(member.getId(), panelCreator.getId())){
throw new RestApiException(NOT_CREATOR_PANEL);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package blacktokkies.toquiz.domain.answer.dao;
package blacktokkies.toquiz.domain.answer.domain;

import blacktokkies.toquiz.domain.answer.domain.Answer;
import blacktokkies.toquiz.domain.question.domain.Question;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package blacktokkies.toquiz.domain.answer.dto.response;
package blacktokkies.toquiz.domain.answer.dto;

import blacktokkies.toquiz.domain.answer.domain.Answer;
import lombok.AllArgsConstructor;
Expand All @@ -18,7 +18,7 @@ public class AnswerResponse {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;

public static AnswerResponse toDto(Answer answer){
public static AnswerResponse from(Answer answer){
return AnswerResponse.builder()
.id(answer.getId())
.content(answer.getContent())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package blacktokkies.toquiz.domain.answer.dto.request;
package blacktokkies.toquiz.domain.answer.dto;

import blacktokkies.toquiz.domain.answer.domain.Answer;
import blacktokkies.toquiz.domain.question.domain.Question;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package blacktokkies.toquiz.domain.answer.dto.response;
package blacktokkies.toquiz.domain.answer.dto;

import blacktokkies.toquiz.domain.question.domain.Question;
import lombok.*;
Expand All @@ -20,7 +20,7 @@ public class GetAnswersResponse {
private LocalDateTime updatedAt;
private List<AnswerResponse> answers;

public static GetAnswersResponse toDto(Question question, List<AnswerResponse> answers){
public static GetAnswersResponse of(Question question, List<AnswerResponse> answers){
return GetAnswersResponse.builder()
.id(question.getId())
.content(question.getContent())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package blacktokkies.toquiz.domain.answer.api;
package blacktokkies.toquiz.domain.answer.presentation;

import blacktokkies.toquiz.domain.answer.application.AnswerService;
import blacktokkies.toquiz.domain.answer.dto.request.CreateAnswerRequest;
import blacktokkies.toquiz.domain.answer.dto.response.AnswerResponse;
import blacktokkies.toquiz.domain.answer.dto.response.GetAnswersResponse;
import blacktokkies.toquiz.domain.answer.dto.CreateAnswerRequest;
import blacktokkies.toquiz.domain.answer.dto.AnswerResponse;
import blacktokkies.toquiz.domain.answer.dto.GetAnswersResponse;
import blacktokkies.toquiz.global.common.annotation.auth.Auth;
import blacktokkies.toquiz.global.common.annotation.auth.MemberEmailDto;
import blacktokkies.toquiz.global.common.response.SuccessResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -12,24 +14,24 @@

@RestController
@RequiredArgsConstructor
public class AnswerApi {
public class AnswerController {
private final AnswerService answerService;

@PostMapping("api/questions/{questionId}/answer")
public ResponseEntity<SuccessResponse<AnswerResponse>> createAnswer(
@Auth MemberEmailDto memberEmailDto,
@PathVariable Long questionId,
@RequestBody @Valid CreateAnswerRequest request
){
AnswerResponse response = answerService.createAnswer(questionId, request);
AnswerResponse response = answerService.createAnswer(memberEmailDto, questionId, request);

return ResponseEntity.ok(new SuccessResponse<>(response));
}

@GetMapping("api/questions/{questionId}/answers")
public ResponseEntity<SuccessResponse<GetAnswersResponse>> getAnswers(
@PathVariable Long questionId
) {

) {
GetAnswersResponse response = answerService.getAnswers(questionId);

return ResponseEntity.ok(new SuccessResponse<>(response));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package blacktokkies.toquiz.domain.member.application;
package blacktokkies.toquiz.domain.auth.application;

import blacktokkies.toquiz.domain.activeinfo.ActiveInfoRepository;
import blacktokkies.toquiz.domain.activeinfo.domain.ActiveInfo;
import blacktokkies.toquiz.domain.member.dto.response.AuthenticateResponse;
import blacktokkies.toquiz.domain.auth.dto.AuthenticateResponse;
import blacktokkies.toquiz.domain.auth.jwt.JwtTokenProvider;
import blacktokkies.toquiz.global.common.annotation.activeInfoId.ActiveInfoIdDto;
import blacktokkies.toquiz.global.common.annotation.auth.MemberEmailDto;
import blacktokkies.toquiz.global.common.error.RestApiException;
import blacktokkies.toquiz.domain.member.dto.request.LoginRequest;
import blacktokkies.toquiz.domain.member.dto.request.SignUpRequest;
import blacktokkies.toquiz.domain.member.dao.MemberRepository;
import blacktokkies.toquiz.domain.auth.dto.LoginRequest;
import blacktokkies.toquiz.domain.auth.dto.SignUpRequest;
import blacktokkies.toquiz.domain.member.domain.MemberRepository;
import blacktokkies.toquiz.domain.member.domain.Member;
import blacktokkies.toquiz.global.util.auth.PasswordEncryptor;
import blacktokkies.toquiz.global.util.auth.TokenService;
import blacktokkies.toquiz.domain.auth.util.PasswordEncryptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static blacktokkies.toquiz.domain.member.exception.MemberErrorCode.*;
import static blacktokkies.toquiz.domain.auth.exception.AuthErrorCode.*;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthService {
private final MemberRepository memberRepository;
private final ActiveInfoRepository activeInfoRepository;
private final TokenService tokenService;
private final RefreshTokenService refreshTokenService;
private final JwtTokenProvider jwtTokenProvider;

@Transactional
public void signUp(SignUpRequest signUpRequest){
checkExistDuplicateEmail(signUpRequest.getEmail());
Expand All @@ -32,38 +38,47 @@ public void signUp(SignUpRequest signUpRequest){
Member member = signUpRequest.toMemberWith(activeInfo);

memberRepository.save(member);

log.info("회원가입 - 사용자 이메일 : {}", member.getEmail());
}

public AuthenticateResponse login(LoginRequest loginRequest){
Member member = getMemberByEmail(loginRequest.getEmail());

checkCorrectPassword(loginRequest.getPassword(), member.getPassword());
String accessToken = tokenService.generateAccessToken(member.getEmail());
String accessToken = jwtTokenProvider.generateAccessToken(member.getEmail());

return AuthenticateResponse.toDto(member, accessToken);
log.info("로그인 - 사용자 이메일 : {}", member.getEmail());
return AuthenticateResponse.of(member, accessToken);
}

public void logout(Member member){
tokenService.deleteRefreshToken(member.getEmail());
@Transactional
public void logout(MemberEmailDto memberEmailDto){
String email = memberEmailDto.email();
refreshTokenService.deleteRefreshTokenByEmail(email);

log.info("로그아웃 - 사용자 이메일 : {}", email);
}

@Transactional
public void resign(Member member, String password, String activeInfoId){
public void resign(MemberEmailDto memberEmailDto, String password, ActiveInfoIdDto activeInfoIdDto){
String email = memberEmailDto.email();;
Member member = getMemberByEmail(email);
checkCorrectPassword(password, member.getPassword()); // 비밀번호가 일치하는지 검증

// RefreshToken, ActiveInfoId, Member를 DB에서 제거
tokenService.deleteRefreshToken(member.getEmail());
activeInfoRepository.deleteById(activeInfoId);
refreshTokenService.deleteRefreshTokenByEmail(email);
activeInfoRepository.deleteById(activeInfoIdDto.activeInfoId());
memberRepository.delete(member);
}

public AuthenticateResponse refresh(Member member, String refreshToken) {
checkExistRefreshToken(refreshToken);
checkValidRefreshToken(refreshToken, member);
log.info("회원탈퇴 - 사용자 이메일 : {}", email);
}

String accessToken = tokenService.generateAccessToken(member.getEmail());
public AuthenticateResponse refresh(String refreshToken) {
String email = jwtTokenProvider.extractEmailFromRefreshToken(refreshToken);
String accessToken = jwtTokenProvider.generateAccessToken(email);

return AuthenticateResponse.toDto(member, accessToken);
return AuthenticateResponse.of(getMemberByEmail(email), accessToken);
}

// ------------ [엔티티 가져오는 메서드] ------------ //
Expand All @@ -72,7 +87,7 @@ private Member getMemberByEmail(String email) {
.orElseThrow(() -> new RestApiException(NOT_EXIST_MEMBER));
}

// ------------ [검증 메서드] ------------ //
// ---------------- [검증 메서드] ---------------- //
private void checkExistDuplicateEmail(String email){
if(memberRepository.existsByEmail(email)){
throw new RestApiException(DUPLICATE_EMAIL);
Expand All @@ -90,18 +105,4 @@ private void checkCorrectPassword(String inputPassword, String memberPassword){
throw new RestApiException(NOT_MATCH_PASSWORD);
}
}

private void checkExistRefreshToken(String refreshToken){
if(refreshToken == null){
throw new RestApiException(INVALID_REFRESH_TOKEN);
}
}

// RefreshToken이 만료 되거나 저장된 사용자의 RefreshToken과 일치하지 않으면 예외처리
private void checkValidRefreshToken(String refreshToken, Member member){
if(!tokenService.isTokenValid(refreshToken, member)){
throw new RestApiException(INVALID_REFRESH_TOKEN);
}
tokenService.validateRefreshToken(refreshToken, member.getEmail());
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
package blacktokkies.toquiz.global.util.auth;
package blacktokkies.toquiz.domain.auth.application;

import blacktokkies.toquiz.domain.activeinfo.ActiveInfoRepository;
import blacktokkies.toquiz.domain.activeinfo.domain.ActiveInfo;
import blacktokkies.toquiz.domain.member.exception.MemberErrorCode;
import blacktokkies.toquiz.domain.auth.application.RefreshTokenService;
import blacktokkies.toquiz.domain.auth.domain.RefreshTokenDetail;
import blacktokkies.toquiz.domain.auth.domain.RefreshTokenRepository;
import blacktokkies.toquiz.domain.auth.jwt.JwtTokenProvider;
import blacktokkies.toquiz.global.common.error.RestApiException;
import blacktokkies.toquiz.domain.member.dao.MemberRepository;
import blacktokkies.toquiz.domain.member.domain.MemberRepository;
import blacktokkies.toquiz.domain.member.domain.Member;
import jakarta.servlet.http.Cookie;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static blacktokkies.toquiz.domain.auth.exception.AuthErrorCode.NOT_EXIST_MEMBER;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CookieService {
private final MemberRepository memberRepository;
private final TokenService tokenService;
private final ActiveInfoRepository activeInfoRepository;
private final RefreshTokenService refreshTokenService;


@Value("${application.security.cookie.active-info-id.expiration}")
private Integer ACTIVE_INFO_ID_EXPIRATION;
@Value("${application.security.cookie.refresh-token.expiration}")
private Integer REFRESH_TOKEN_EXPIRATION;

// 익명 사용자 ActiveInfoId 쿠키 발급
public Cookie issueActiveInfoIdCookie(){
ActiveInfo activeInfo = activeInfoRepository.save(new ActiveInfo());
Cookie cookie = new Cookie("active_info_id", activeInfo.getId());
cookie.setMaxAge(ACTIVE_INFO_ID_EXPIRATION);
cookie.setHttpOnly(true);
cookie.setPath("/");

return cookie;
}

// 로그인 사용자 ActiveInfoId 쿠키 발급
public Cookie issueActiveInfoIdCookieByEmail(String email){
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new RestApiException(MemberErrorCode.NOT_EXIST_MEMBER));
.orElseThrow(() -> new RestApiException(NOT_EXIST_MEMBER));

String activeInfoId = member.getActiveInfoId();

Expand All @@ -49,8 +45,9 @@ public Cookie issueActiveInfoIdCookieByEmail(String email){
return cookie;
}

public Cookie issueRefreshTokenCookie(String email){
String refreshToken = tokenService.generateRefreshToken(email);
@Transactional
public Cookie issueRefreshTokenCookieByEmail(String email){
String refreshToken = refreshTokenService.generateRefreshTokenByEmail(email);

Cookie cookie = new Cookie("refresh_token", refreshToken);
cookie.setMaxAge(REFRESH_TOKEN_EXPIRATION);
Expand Down
Loading

0 comments on commit 3e16ba7

Please sign in to comment.