diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..74da825 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation=true \ No newline at end of file diff --git a/module-api/build.gradle b/module-api/build.gradle index acb168e..33bd0ba 100644 --- a/module-api/build.gradle +++ b/module-api/build.gradle @@ -1,17 +1,13 @@ dependencies { implementation project(":module-domain") - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.security:spring-security-crypto' + implementation 'org.springframework:spring-web' implementation 'io.jsonwebtoken:jjwt:0.9.1' - implementation 'org.springframework.boot:spring-boot-starter-security' - - implementation("io.springfox:springfox-swagger-ui:2.9.2") - implementation("io.springfox:springfox-swagger2:2.9.2") implementation "com.amazonaws:aws-java-sdk-s3:${awsJavaSdkVersion}" diff --git a/module-api/src/main/java/inspiration/ResultResponse.java b/module-api/src/main/java/inspiration/ResultResponse.java deleted file mode 100644 index 34cf41f..0000000 --- a/module-api/src/main/java/inspiration/ResultResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package inspiration; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class ResultResponse { - - private String message; - private T data; - - private ResultResponse(String message) { - this.message = message; - } - - private ResultResponse(String message, T data) { - this.message = message; - this.data = data; - } - - public static ResultResponse from(String message) { - - return new ResultResponse<>(message); - } - - public static ResultResponse of(String message, T data) { - - return new ResultResponse(message, data); - } -} diff --git a/module-api/src/main/java/inspiration/auth/AuthService.java b/module-api/src/main/java/inspiration/auth/AuthService.java index 425e692..535361f 100644 --- a/module-api/src/main/java/inspiration/auth/AuthService.java +++ b/module-api/src/main/java/inspiration/auth/AuthService.java @@ -1,11 +1,9 @@ package inspiration.auth; -import inspiration.ResultResponse; import inspiration.auth.jwt.JwtProvider; -import inspiration.auth.request.LoginRequest; +import inspiration.auth.request.LoginRequestVo; import inspiration.domain.member.Member; import inspiration.domain.member.MemberRepository; -import inspiration.domain.member.response.MemberInfoResponse; import inspiration.enumeration.ExceptionType; import inspiration.enumeration.ExpireTimeConstants; import inspiration.exception.PostNotFoundException; @@ -33,22 +31,18 @@ public class AuthService { private final String refreshTokenKey = "refreshToken : "; private final String accessTokenKey = "accessToken : "; - private final String issueToken = "refreshToken : "; @Transactional - public ResultResponse login(LoginRequest request) { - - Member member = checkEmail(request.getEmail()); - verifyPassword(request.getPassword(), member.getPassword()); - - TokenResponse tokenResponse = TokenResponse.builder() - .accessToken(resolveAccessToken(member.getId())) - .refreshToken(resolveRefreshToken(member.getId())) - .memberId(member.getId()) - .accessTokenExpireDate(ExpireTimeConstants.accessTokenValidMillisecond) - .build(); - - return ResultResponse.of(issueToken, tokenResponse); + public TokenResponseVo login(LoginRequestVo loginRequestVo) { + Member member = checkEmail(loginRequestVo.getEmail()); + verifyPassword(loginRequestVo.getPassword(), member.getPassword()); + + return TokenResponseVo.builder() + .accessToken(resolveAccessToken(member.getId())) + .refreshToken(resolveRefreshToken(member.getId())) + .accessTokenExpireDate(ExpireTimeConstants.accessTokenValidMillisecond) + .memberId(member.getId()) + .build(); } private Member checkEmail(String email) { @@ -93,7 +87,7 @@ private void saveRefreshToken(Long memberId, String refreshToken) { } @Transactional - public ResultResponse reissue(String refreshToken) { + public TokenResponseVo reissue(String refreshToken) { Long memberId = jwtProvider.resolveMemberId(refreshToken) .orElseThrow(RefreshTokenException::new); Member member = memberRepository.findById(memberId) @@ -109,18 +103,9 @@ public ResultResponse reissue(String refreshToken) { throw new RefreshTokenException(ExceptionType.VALID_NOT_REFRESH_TOKEN.getMessage()); } - TokenResponse newTokenResponse = jwtProvider.createTokenDto(member.getId()); - - saveRefreshToken(member.getId(), newTokenResponse.getRefreshToken()); - saveAccessToken(member.getId(), newTokenResponse.getAccessToken()); - - return ResultResponse.of(issueToken, newTokenResponse); - } - - @Transactional(readOnly = true) - public MemberInfoResponse getUserInfo(Long memberId) { - return memberRepository.findById(memberId) - .map(MemberInfoResponse::of) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); + TokenResponseVo newTokenResponseVo = jwtProvider.createTokenVo(member.getId()); + saveRefreshToken(member.getId(), newTokenResponseVo.getRefreshToken()); + saveAccessToken(member.getId(), newTokenResponseVo.getAccessToken()); + return newTokenResponseVo; } } diff --git a/module-api/src/main/java/inspiration/auth/TokenResponseVo.java b/module-api/src/main/java/inspiration/auth/TokenResponseVo.java new file mode 100644 index 0000000..d0f0172 --- /dev/null +++ b/module-api/src/main/java/inspiration/auth/TokenResponseVo.java @@ -0,0 +1,16 @@ +package inspiration.auth; + +import lombok.*; + +@Builder +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("ClassCanBeRecord") +public class TokenResponseVo { + private final String accessToken; + private final String refreshToken; + private final Long accessTokenExpireDate; + private final long memberId; +} diff --git a/module-api/src/main/java/inspiration/auth/jwt/JwtProvider.java b/module-api/src/main/java/inspiration/auth/jwt/JwtProvider.java index ce76278..143cb42 100644 --- a/module-api/src/main/java/inspiration/auth/jwt/JwtProvider.java +++ b/module-api/src/main/java/inspiration/auth/jwt/JwtProvider.java @@ -1,6 +1,6 @@ package inspiration.auth.jwt; -import inspiration.auth.TokenResponse; +import inspiration.auth.TokenResponseVo; import inspiration.enumeration.ExpireTimeConstants; import io.jsonwebtoken.*; import io.jsonwebtoken.impl.Base64UrlCodec; @@ -65,13 +65,13 @@ private String createRefreshTokenInner(Long memberId) { .compact(); } - public TokenResponse createTokenDto(Long memberId) { - return TokenResponse.builder() - .accessToken(createAccessTokenInner(memberId)) - .refreshToken(createRefreshTokenInner(memberId)) - .memberId(memberId) - .accessTokenExpireDate(ExpireTimeConstants.accessTokenValidMillisecond) - .build(); + public TokenResponseVo createTokenVo(Long memberId) { + return TokenResponseVo.builder() + .accessToken(createAccessTokenInner(memberId)) + .refreshToken(createRefreshTokenInner(memberId)) + .accessTokenExpireDate(ExpireTimeConstants.accessTokenValidMillisecond) + .memberId(memberId) + .build(); } public Optional resolveMemberId(String token) { diff --git a/module-api/src/main/java/inspiration/auth/request/LoginRequestVo.java b/module-api/src/main/java/inspiration/auth/request/LoginRequestVo.java new file mode 100644 index 0000000..8b3b39c --- /dev/null +++ b/module-api/src/main/java/inspiration/auth/request/LoginRequestVo.java @@ -0,0 +1,10 @@ +package inspiration.auth.request; + +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class LoginRequestVo { + String email; + String password; +} diff --git a/module-api/src/main/java/inspiration/auth/request/TokenRequestVo.java b/module-api/src/main/java/inspiration/auth/request/TokenRequestVo.java new file mode 100644 index 0000000..cdbfd85 --- /dev/null +++ b/module-api/src/main/java/inspiration/auth/request/TokenRequestVo.java @@ -0,0 +1,10 @@ +package inspiration.auth.request; + +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class TokenRequestVo { + String accessToken; + String refreshToken; +} diff --git a/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java b/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java index 234635b..eeddbfe 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java @@ -1,14 +1,13 @@ package inspiration.domain.emailauth; -import inspiration.ResultResponse; +import inspiration.domain.member.MemberRepository; +import inspiration.domain.passwordauth.PasswordAuth; +import inspiration.domain.passwordauth.PasswordAuthRepository; import inspiration.enumeration.ExceptionType; import inspiration.enumeration.ExpireTimeConstants; import inspiration.enumeration.RedisKey; import inspiration.exception.EmailAuthenticatedTimeExpiredException; import inspiration.exception.PostNotFoundException; -import inspiration.domain.member.MemberRepository; -import inspiration.domain.passwordauth.PasswordAuth; -import inspiration.domain.passwordauth.PasswordAuthRepository; import inspiration.redis.RedisService; import inspiration.utils.AuthTokenUtil; import lombok.RequiredArgsConstructor; @@ -19,6 +18,7 @@ @Slf4j @RequiredArgsConstructor @Service +@SuppressWarnings("ClassCanBeRecord") public class EmailAuthService { private final EmailAuthRepository emailAuthRepository; @@ -33,7 +33,7 @@ public void signUpEmailSend(String email) { verifyEmail(email); - if(email.contains("+")) { + if (email.contains("+")) { throw new PostNotFoundException("잘못된 이메일 형식입니다."); } @@ -58,9 +58,9 @@ public void authenticateEmailOfSignUp(String email) { emailAuthRepository.save( EmailAuth.builder() - .email(email) - .isAuth(true) - .build()); + .email(email) + .isAuth(true) + .build()); } @Transactional @@ -70,11 +70,11 @@ public void resetPasswordForAuthEmailSend(String email) { throw new PostNotFoundException(ExceptionType.MEMBER_NOT_FOUND.getMessage()); } - if(passwordAuthRepository.existsByEmail(email)) { + if (passwordAuthRepository.existsByEmail(email)) { throw new PostNotFoundException(ExceptionType.EMAIL_ALREADY_AUTHENTICATED.getMessage()); } - if(email.contains("+")) { + if (email.contains("+")) { throw new PostNotFoundException("잘못된 이메일 형식입니다."); } @@ -98,29 +98,19 @@ public void authenticateEmailOfResetPasswordForAuth(String email) { passwordAuthRepository.save( PasswordAuth.builder() - .email(email) - .isAuth(true) - .build()); + .email(email) + .isAuth(true) + .build()); } @Transactional(readOnly = true) - public ResultResponse validAuthenticateEmailStatusOfSignup(String email) { - - if (emailAuthRepository.existsByEmail(email)) { - return ResultResponse.of(ExceptionType.EMAIL_ALREADY_AUTHENTICATED.getMessage(), true); - } - - return ResultResponse.of(ExceptionType.EMAIL_NOT_AUTHENTICATED.getMessage(), false); + public boolean validAuthenticateEmailStatusOfSignup(String email) { + return emailAuthRepository.existsByEmail(email); } @Transactional(readOnly = true) - public ResultResponse validAuthenticateEmailStatusOfResetPassword(String email) { - - if (passwordAuthRepository.existsByEmail(email)) { - return ResultResponse.of(ExceptionType.EMAIL_ALREADY_AUTHENTICATED.getMessage(), true); - } - - return ResultResponse.of(ExceptionType.EMAIL_NOT_AUTHENTICATED.getMessage(), false); + public boolean isValidAuthenticateEmailStatusOfResetPassword(String email) { + return passwordAuthRepository.existsByEmail(email); } private void verifyEmail(String email) { diff --git a/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordEmailSendService.java b/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordEmailSendService.java index 7d0b8f6..09f2a4b 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordEmailSendService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordEmailSendService.java @@ -11,11 +11,12 @@ @Service @RequiredArgsConstructor @Slf4j +@SuppressWarnings("ClassCanBeRecord") public class ResetPasswordEmailSendService implements EmailSendService { + private static final String SUBJECT = "비빌번호 초기화"; + private static final String CREATED_RESET_PASSWORD = "임시 비밀번호 발급: "; private final JavaMailSender mailSender; - private final static String SUBJECT = "비빌번호 초기화"; - private final static String CREATED_RESET_PASSWORD = "임시 비밀번호 발급: "; @Override public void send(String email, String restPassword) { diff --git a/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordForAuthSendService.java b/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordForAuthSendService.java index cfe0eaf..ddd96e5 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordForAuthSendService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/ResetPasswordForAuthSendService.java @@ -12,13 +12,14 @@ @Service @RequiredArgsConstructor @Slf4j +@SuppressWarnings("ClassCanBeRecord") public class ResetPasswordForAuthSendService implements EmailSendService { + private static final String SUBJECT = "비밀번호 초기화를 위한 이메일 인증"; + private static final String OPEN_HREF = ""; private final JavaMailSender mailSender; private final MailProperties mailProperties; - private final static String SUBJECT = "비밀번호 초기화를 위한 이메일 인증"; - private final static String OPEN_HREF = ""; @Override public void send(String email, String authToken) { diff --git a/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java b/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java index d3470d6..3c7d194 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java @@ -13,11 +13,12 @@ @Service @RequiredArgsConstructor @Slf4j +@SuppressWarnings("ClassCanBeRecord") public class SignUpEmailSendService implements EmailSendService { + private static final String SUBJECT = "이메일 인증"; private final JavaMailSender mailSender; private final MailProperties mailProperties; - private final static String SUBJECT = "이메일 인증"; @Override public void send(String email, String authToken) { diff --git a/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java b/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java index 375acb0..8e99165 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java +++ b/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java @@ -1,14 +1,10 @@ package inspiration.domain.inspiration; -import inspiration.RestPage; import inspiration.aws.AwsS3Service; -import inspiration.domain.inspiration.opengraph.OpenGraphService; -import inspiration.domain.inspiration.opengraph.OpenGraphVo; -import inspiration.domain.inspiration.request.InspirationAddRequest; -import inspiration.domain.inspiration.request.InspirationModifyRequest; -import inspiration.domain.inspiration.request.InspirationTagRequest; -import inspiration.domain.inspiration.response.InspirationResponse; -import inspiration.domain.inspiration.response.OpenGraphResponse; +import inspiration.domain.inspiration.request.InspirationAddRequestVo; +import inspiration.domain.inspiration.request.InspirationModifyRequestVo; +import inspiration.domain.inspiration.request.InspirationTagRequestVo; +import inspiration.domain.inspiration.response.InspirationResponseVo; import inspiration.domain.inspiration_tag.InspirationTag; import inspiration.domain.inspiration_tag.InspirationTagRepository; import inspiration.domain.inspiration_tag.InspirationTagService; @@ -24,8 +20,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -33,15 +27,13 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @Service @Transactional +@SuppressWarnings("ClassCanBeRecord") public class InspirationService { private final InspirationRepository inspirationRepository; @@ -51,95 +43,73 @@ public class InspirationService { private final InspirationTagService inspirationTagService; private final InspirationTagRepository inspirationTagRepository; private final TagRepository tagRepository; - private final OpenGraphService openGraphService; - private final ThreadPoolTaskExecutor threadPoolTaskExecutor; @Transactional(readOnly = true) - public RestPage findInspirations(Pageable pageable, Long memberId) { + public Page findInspirations(Pageable pageable, Long memberId) { Member member = memberService.findById(memberId); - - Page inspirationPage = inspirationRepository.findAllByMember(member, pageable); - return toRestPage(inspirationPage); + return inspirationRepository.findAllByMember(member, pageable) + .map(it -> InspirationResponseVo.of(it, awsS3Service)); } @Transactional(readOnly = true) - public InspirationResponse findInspiration(Long id, Long memberId) { - + public InspirationResponseVo findInspiration(Long id, Long memberId) { Member member = memberService.findById(memberId); Inspiration inspiration = inspirationRepository.findAllByMemberAndId(member, id) .orElseThrow(ResourceNotFoundException::new); - - inspiration.setFilePath(getFilePath(inspiration.getType(), inspiration.getContent())); - return InspirationResponse.of(inspiration, getOpenGraphResponse(inspiration.getType(), inspiration.getContent())); + return InspirationResponseVo.of(inspiration, awsS3Service); } - private OpenGraphResponse getOpenGraphResponse(InspirationType inspirationType, String link) { - if (inspirationType != InspirationType.LINK) { - return OpenGraphResponse.from(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } - Optional openGraphVoOptional = openGraphService.getMetadata(link); - if (openGraphVoOptional.isEmpty()) { - return OpenGraphResponse.from(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } - OpenGraphVo openGraphVo = openGraphVoOptional.get(); - return OpenGraphResponse.of( - HttpStatus.OK.value(), - openGraphVo.getImage(), - openGraphVo.getSiteName(), - openGraphVo.getTitle(), - openGraphVo.getUrl() != null ? openGraphVo.getUrl() : link, - openGraphVo.getDescription() - ); - } - - public OpenGraphResponse getOpenGraphResponse(String link) { - return getOpenGraphResponse(InspirationType.LINK, link); - } - - public Long addInspiration(InspirationAddRequest request, Long memberId) { + public Long addInspiration(InspirationAddRequestVo requestVo, Long memberId) { Member member = memberService.findById(memberId); - Inspiration tmpInspiration = request.toEntity(); + Inspiration tmpInspiration = requestVo.toEntity(); tmpInspiration.writeBy(member); - if (request.getType() == InspirationType.IMAGE) { - if (request.getFile() == null) { + if (requestVo.getType() == InspirationType.IMAGE) { + if (requestVo.getFile() == null) { throw new IllegalArgumentException("IMAGE 타입은 파일을 업로드 해야합니다."); } - fileUpload(tmpInspiration, List.of(request.getFile())); + fileUpload(tmpInspiration, List.of(requestVo.getFile())); } Inspiration inspiration = inspirationRepository.save(tmpInspiration); - if (request.getTagIds() != null) { - List tags = request.getTagIds().stream() - .map(tagService::getTag) - .collect(Collectors.toList()); + if (requestVo.getTagIds() != null) { + List tags = requestVo.getTagIds().stream() + .map(tagService::getTag) + .collect(Collectors.toList()); tags.forEach(tag -> inspirationTagService.save(InspirationTag.of(inspiration, tag))); } return inspiration.getId(); } @Transactional(readOnly = true) - public RestPage findInspirationsByTags(List tagIds, List types, - LocalDateTime createdDateTimeFrom, LocalDateTime createdDateTimeTo, - Long memberId, Pageable pageable) { - - Page inspirationPage = inspirationRepository.findDistinctByMemberIdAndTagIdInAndTypeAndCreatedDateTimeBetween(memberId, tagIds, types, createdDateTimeFrom, createdDateTimeTo, pageable); - - return toRestPage(inspirationPage); - } - - - public Long modifyMemo(InspirationModifyRequest request, Long memberId) { - - Inspiration inspiration = getInspiration(request.getId()); - + public Page findInspirationsByTags( + List tagIds, + List types, + LocalDateTime createdDateTimeFrom, + LocalDateTime createdDateTimeTo, + Long memberId, + Pageable pageable + ) { + return inspirationRepository.findDistinctByMemberIdAndTagIdInAndTypeAndCreatedDateTimeBetween( + memberId, + tagIds, + types, + createdDateTimeFrom, + createdDateTimeTo, + pageable + ).map(it -> InspirationResponseVo.of(it, awsS3Service)); + } + + + public Long modifyMemo(InspirationModifyRequestVo requestVo, Long memberId) { + Inspiration inspiration = getInspiration(requestVo.getId()); if (!inspiration.getMember().isSameMember(memberId)) { throw new NoAccessAuthorizationException(); } - inspiration.modifyMemo(request.getMemo()); + inspiration.modifyMemo(requestVo.getMemo()); return inspiration.getId(); } @@ -189,15 +159,15 @@ public void removeAllInspiration(Long memberId) { } @Transactional(isolation = Isolation.SERIALIZABLE) - public Long tagInspiration(InspirationTagRequest request, Long memberId) { + public Long tagInspiration(InspirationTagRequestVo requestVo, Long memberId) { - Inspiration inspiration = getInspiration(request.getId()); + Inspiration inspiration = getInspiration(requestVo.getInspirationId()); if (!inspiration.getMember().isSameMember(memberId)) { throw new NoAccessAuthorizationException(); } - Tag tag = tagService.getTag(request.getTagId()); + Tag tag = tagService.getTag(requestVo.getTagId()); if (!tag.getMember().isSameMember(memberId)) { throw new NoAccessAuthorizationException(); @@ -258,30 +228,8 @@ private void fileUpload(Inspiration inspiration, List multipartFi } } - private String getFilePath(InspirationType type, String content) { - if (type == InspirationType.IMAGE) { - return awsS3Service.getFilePath(content); - } - return content; - } - private Inspiration getInspiration(Long id) { return inspirationRepository.findById(id) .orElseThrow(ResourceNotFoundException::new); } - - private RestPage toRestPage(Page inspirationPage) { - return new RestPage<>( - inspirationPage.stream() - .parallel() - .peek(it -> it.setFilePath(getFilePath(it.getType(), it.getContent()))) - .map(it -> (Callable) () -> InspirationResponse.of(it, getOpenGraphResponse(it.getType(), it.getContent()))) - .map(it -> threadPoolTaskExecutor.submitListenable(it).completable()) - .map(CompletableFuture::join) - .collect(Collectors.toList()), - inspirationPage.getPageable().getPageNumber(), - inspirationPage.getPageable().getPageSize(), - inspirationPage.getTotalElements() - ); - } } diff --git a/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphService.java b/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphService.java index 7cf2df1..5970ac4 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphService.java +++ b/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphService.java @@ -1,7 +1,8 @@ package inspiration.domain.inspiration.opengraph; -import java.util.Optional; +import inspiration.domain.inspiration.InspirationType; +import inspiration.domain.inspiration.response.OpenGraphResponseVo; public interface OpenGraphService { - Optional getMetadata(String url); + OpenGraphResponseVo getOpenGraphResponseVo(InspirationType inspirationType, String link); } \ No newline at end of file diff --git a/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphServiceImpl.java b/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphServiceImpl.java index df63aa1..66b58cd 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphServiceImpl.java +++ b/module-api/src/main/java/inspiration/domain/inspiration/opengraph/OpenGraphServiceImpl.java @@ -2,13 +2,35 @@ import com.github.siyoon210.ogparser4j.OgParser; import com.github.siyoon210.ogparser4j.OpenGraph; +import inspiration.domain.inspiration.InspirationType; +import inspiration.domain.inspiration.response.OpenGraphResponseVo; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.util.Optional; @Service public class OpenGraphServiceImpl implements OpenGraphService { - public Optional getMetadata(String url) { + public OpenGraphResponseVo getOpenGraphResponseVo(InspirationType inspirationType, String link) { + if (inspirationType != InspirationType.LINK) { + return OpenGraphResponseVo.from(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + Optional openGraphVoOptional = getMetadata(link); + if (openGraphVoOptional.isEmpty()) { + return OpenGraphResponseVo.from(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + OpenGraphVo openGraphVo = openGraphVoOptional.get(); + return OpenGraphResponseVo.of( + HttpStatus.OK.value(), + openGraphVo.getImage(), + openGraphVo.getSiteName(), + openGraphVo.getTitle(), + openGraphVo.getUrl() != null ? openGraphVo.getUrl() : link, + openGraphVo.getDescription() + ); + } + + private Optional getMetadata(String url) { // FIXME: DI 적용 (모듈 의존성 개선 후 api 모듈에서 bean 생성해야함) OgParser ogParser = new OgParser(new YgtangOgMetaElementHtmlParser()); OpenGraph openGraph = ogParser.getOpenGraphOf(url); diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequestVo.java b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequestVo.java new file mode 100644 index 0000000..8e92894 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequestVo.java @@ -0,0 +1,26 @@ +package inspiration.domain.inspiration.request; + +import inspiration.domain.inspiration.Inspiration; +import inspiration.domain.inspiration.InspirationType; +import lombok.Value; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class InspirationAddRequestVo { + InspirationType type; + String content; + String memo; + MultipartFile file; + List tagIds; + + public Inspiration toEntity() { + return Inspiration.builder() + .content(this.content) + .type(this.type) + .memo(this.memo) + .build(); + } +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequest.java b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequest.java deleted file mode 100644 index be506d7..0000000 --- a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package inspiration.domain.inspiration.request; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.NotNull; - - -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class InspirationModifyRequest { - - @NotNull(message = "영감 id는 필수 입력 입니다.") - private Long id; - - private String memo; - -} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequestVo.java b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequestVo.java new file mode 100644 index 0000000..7e148c4 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationModifyRequestVo.java @@ -0,0 +1,10 @@ +package inspiration.domain.inspiration.request; + +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class InspirationModifyRequestVo { + Long id; + String memo; +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequestVo.java b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequestVo.java new file mode 100644 index 0000000..1920283 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequestVo.java @@ -0,0 +1,10 @@ +package inspiration.domain.inspiration.request; + +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class InspirationTagRequestVo { + Long inspirationId; + Long tagId; +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponse.java b/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponse.java deleted file mode 100644 index eeca778..0000000 --- a/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponse.java +++ /dev/null @@ -1,58 +0,0 @@ -package inspiration.domain.inspiration.response; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import inspiration.domain.inspiration.Inspiration; -import inspiration.domain.inspiration.InspirationType; -import inspiration.domain.member.response.MemberResponse; -import inspiration.domain.tag.response.TagResponse; -import lombok.*; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class InspirationResponse { - - private Long id; - private MemberResponse memberResponse; - private List tagResponses; - private InspirationType type; - private String content; - private String memo; - private OpenGraphResponse openGraphResponse; - - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - private LocalDateTime createdDatetime; - - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") - private LocalDateTime updatedDatetime; - - public static InspirationResponse of(Inspiration inspiration, OpenGraphResponse openGraphResponse) { - return InspirationResponse.builder() - .id(inspiration.getId()) - .type(inspiration.getType()) - .content(inspiration.getContent()) - .memo(inspiration.getMemo()) - .createdDatetime(inspiration.getCreatedDateTime()) - .updatedDatetime(inspiration.getUpdatedDateTime()) - .memberResponse(MemberResponse.of(inspiration.getMember())) - .tagResponses(inspiration.getInspirationTags().stream() - .map(inspirationTag -> TagResponse.from(inspirationTag.getTag())) - .collect(Collectors.toList())) - .openGraphResponse(openGraphResponse) - .build(); - } - - -} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponseVo.java b/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponseVo.java new file mode 100644 index 0000000..c4ea6a4 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/inspiration/response/InspirationResponseVo.java @@ -0,0 +1,53 @@ +package inspiration.domain.inspiration.response; + +import inspiration.aws.AwsS3Service; +import inspiration.domain.inspiration.Inspiration; +import inspiration.domain.inspiration.InspirationType; +import inspiration.domain.member.response.MemberResponseVo; +import inspiration.domain.tag.response.TagResponseVo; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Builder +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("ClassCanBeRecord") +public class InspirationResponseVo { + private final Long id; + private final MemberResponseVo memberResponseVo; + private final List tagResponseVoList; + private final InspirationType type; + private final String content; + private final String memo; + private final LocalDateTime createdDateTime; + private final LocalDateTime updatedDateTime; + + public static InspirationResponseVo of( + Inspiration inspiration, + AwsS3Service awsS3Service + ) { + return InspirationResponseVo.builder() + .id(inspiration.getId()) + .type(inspiration.getType()) + .content(getFilePath(inspiration, awsS3Service)) + .memo(inspiration.getMemo()) + .createdDateTime(inspiration.getCreatedDateTime()) + .updatedDateTime(inspiration.getUpdatedDateTime()) + .memberResponseVo(MemberResponseVo.of(inspiration.getMember())) + .tagResponseVoList(inspiration.getInspirationTags().stream() + .map(it -> TagResponseVo.from(it.getTag())) + .toList()) + .build(); + } + + private static String getFilePath(Inspiration inspiration, AwsS3Service awsS3Service) { + return inspiration.getType() == InspirationType.IMAGE + ? awsS3Service.getFilePath(inspiration.getContent()) + : inspiration.getContent(); + } +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponse.java b/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponse.java deleted file mode 100644 index de4f282..0000000 --- a/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package inspiration.domain.inspiration.response; - -import lombok.*; - - -@Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class OpenGraphResponse { - - private int code; - private String description; - private String siteName; - private String title; - private String url; - private String image; - - - public static OpenGraphResponse of(int code, String image, String siteName, String title, String url, String description) { - return OpenGraphResponse.builder() - .code(code) - .image(image) - .siteName(siteName) - .title(title) - .url(url) - .description(description) - .build(); - } - - public static OpenGraphResponse from(int code) { - return OpenGraphResponse.builder() - .code(code) - .build(); - } -} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponseVo.java b/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponseVo.java new file mode 100644 index 0000000..e52990b --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/inspiration/response/OpenGraphResponseVo.java @@ -0,0 +1,43 @@ +package inspiration.domain.inspiration.response; + +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class OpenGraphResponseVo { + int code; + String description; + String siteName; + String title; + String url; + String image; + + public static OpenGraphResponseVo of( + int code, + String image, + String siteName, + String title, + String url, + String description + ) { + return new OpenGraphResponseVo( + code, + image, + siteName, + title, + url, + description + ); + } + + public static OpenGraphResponseVo from(int code) { + return new OpenGraphResponseVo( + code, + null, + null, + null, + null, + null + ); + } +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration_tag/InspirationTagService.java b/module-api/src/main/java/inspiration/domain/inspiration_tag/InspirationTagService.java index c498353..c327ab8 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration_tag/InspirationTagService.java +++ b/module-api/src/main/java/inspiration/domain/inspiration_tag/InspirationTagService.java @@ -12,6 +12,7 @@ @RequiredArgsConstructor @Service @Transactional +@SuppressWarnings("ClassCanBeRecord") public class InspirationTagService { private final InspirationTagRepository inspirationTagRepository; diff --git a/module-api/src/main/java/inspiration/domain/member/MemberService.java b/module-api/src/main/java/inspiration/domain/member/MemberService.java index db4a2fe..912a32d 100644 --- a/module-api/src/main/java/inspiration/domain/member/MemberService.java +++ b/module-api/src/main/java/inspiration/domain/member/MemberService.java @@ -2,12 +2,13 @@ import inspiration.domain.emailauth.EmailAuthRepository; import inspiration.domain.emailauth.ResetPasswordEmailSendService; +import inspiration.domain.member.response.MemberInfoVo; +import inspiration.domain.member.response.MemberResponseVo; +import inspiration.domain.passwordauth.PasswordAuth; +import inspiration.domain.passwordauth.PasswordAuthRepository; import inspiration.enumeration.ExceptionType; import inspiration.exception.PostNotFoundException; import inspiration.exception.UnauthorizedAccessRequestException; -import inspiration.domain.member.response.MemberResponse; -import inspiration.domain.passwordauth.PasswordAuth; -import inspiration.domain.passwordauth.PasswordAuthRepository; import inspiration.utils.GetResetPasswordUtil; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; @@ -19,6 +20,7 @@ @Service @RequiredArgsConstructor +@SuppressWarnings("ClassCanBeRecord") public class MemberService { private final EmailAuthRepository emailAuthRepository; @@ -33,7 +35,7 @@ public void changePassword(Long memberId, String confirmPassword, String passwor confirmPasswordCheck(confirmPassword, password); Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); member.updatePassword(passwordEncoder.encode(password)); } @@ -42,7 +44,7 @@ public void changePassword(Long memberId, String confirmPassword, String passwor public void resetPasswordEmailSend(String email) { Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); String resetPassword = GetResetPasswordUtil.getResetPassword(); @@ -51,7 +53,7 @@ public void resetPasswordEmailSend(String email) { member.updatePassword(passwordEncoder.encode(resetPassword)); PasswordAuth passwordAuth = passwordAuthRepository.findByEmail(member.getEmail()) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.EMAIL_NOT_AUTHENTICATED.getMessage())); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.EMAIL_NOT_AUTHENTICATED.getMessage())); passwordAuthRepository.delete(passwordAuth); } @@ -60,7 +62,7 @@ public void resetPasswordEmailSend(String email) { public void changeNickname(Long memberId, String nickname) { Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_EXISTS.getMessage())); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_EXISTS.getMessage())); member.updateNickname(nickname); } @@ -69,7 +71,7 @@ public void changeNickname(Long memberId, String nickname) { public void removeUser(Long memberId) { Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); emailAuthRepository.deleteByEmail(member.getEmail()); @@ -86,14 +88,21 @@ private void confirmPasswordCheck(String confirmPasswordCheck, String password) public Member findById(Long id) { return memberRepository.findById(id) - .orElseThrow(UnauthorizedAccessRequestException::new); + .orElseThrow(UnauthorizedAccessRequestException::new); } - public List findAll() { + public List findAll() { return memberRepository.findAll() - .stream() - .map(MemberResponse::of) - .collect(Collectors.toList()); + .stream() + .map(MemberResponseVo::of) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public MemberInfoVo getMemberInfo(Long memberId) { + return memberRepository.findById(memberId) + .map(MemberInfoVo::from) + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); } } diff --git a/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequestVo.java b/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequestVo.java new file mode 100644 index 0000000..57a32de --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequestVo.java @@ -0,0 +1,13 @@ +package inspiration.domain.member.request; + +import inspiration.domain.member.AgeGroupType; +import inspiration.domain.member.GenderType; +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class ExtraInfoRequestVo { + GenderType gender; + AgeGroupType age; + String job; +} diff --git a/module-api/src/main/java/inspiration/domain/member/request/SignUpRequestVo.java b/module-api/src/main/java/inspiration/domain/member/request/SignUpRequestVo.java new file mode 100644 index 0000000..fc69291 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/member/request/SignUpRequestVo.java @@ -0,0 +1,22 @@ +package inspiration.domain.member.request; + +import inspiration.domain.member.Member; +import lombok.*; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class SignUpRequestVo { + String email; + String nickName; + String password; + String confirmPassword; + + public Member toEntity(PasswordEncoder passwordEncoder) { + return Member.builder() + .email(email) + .password(passwordEncoder.encode(password)) + .nickname(nickName) + .build(); + } +} diff --git a/module-api/src/main/java/inspiration/domain/member/request/UpdateNicknameRequest.java b/module-api/src/main/java/inspiration/domain/member/request/UpdateNicknameRequest.java deleted file mode 100644 index c3bdb92..0000000 --- a/module-api/src/main/java/inspiration/domain/member/request/UpdateNicknameRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package inspiration.domain.member.request; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class UpdateNicknameRequest { - private String nickname; -} diff --git a/module-api/src/main/java/inspiration/domain/member/response/MemberInfoResponse.java b/module-api/src/main/java/inspiration/domain/member/response/MemberInfoResponse.java deleted file mode 100644 index 7cf090b..0000000 --- a/module-api/src/main/java/inspiration/domain/member/response/MemberInfoResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package inspiration.domain.member.response; - -import inspiration.domain.member.Member; -import lombok.*; - -@Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class MemberInfoResponse { - - private String nickName; - private String email; - - public static MemberInfoResponse of(Member member) { - return MemberInfoResponse.builder() - .nickName(member.getNickname()) - .email(member.getEmail()) - .build(); - } -} diff --git a/module-api/src/main/java/inspiration/domain/member/response/MemberInfoVo.java b/module-api/src/main/java/inspiration/domain/member/response/MemberInfoVo.java new file mode 100644 index 0000000..5f82e27 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/member/response/MemberInfoVo.java @@ -0,0 +1,21 @@ +package inspiration.domain.member.response; + +import inspiration.domain.member.Member; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("ClassCanBeRecord") +public class MemberInfoVo { + String nickName; + String email; + + public static MemberInfoVo from(Member member) { + return new MemberInfoVo( + member.getNickname(), + member.getEmail() + ); + } +} diff --git a/module-api/src/main/java/inspiration/domain/member/response/MemberResponseVo.java b/module-api/src/main/java/inspiration/domain/member/response/MemberResponseVo.java new file mode 100644 index 0000000..2ec47e3 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/member/response/MemberResponseVo.java @@ -0,0 +1,24 @@ +package inspiration.domain.member.response; + +import inspiration.domain.member.Member; +import lombok.Value; + +import java.time.LocalDateTime; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class MemberResponseVo { + Long id; + String nickName; + String email; + LocalDateTime createdDateTime; + + public static MemberResponseVo of(Member member) { + return new MemberResponseVo( + member.getId(), + member.getNickname(), + member.getEmail(), + member.getCreatedDateTime() + ); + } +} diff --git a/module-api/src/main/java/inspiration/domain/member/response/NicknameResponse.java b/module-api/src/main/java/inspiration/domain/member/response/NicknameResponse.java deleted file mode 100644 index 8f473fb..0000000 --- a/module-api/src/main/java/inspiration/domain/member/response/NicknameResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package inspiration.domain.member.response; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class NicknameResponse { - - String message; - - public static NicknameResponse of() { - - return new NicknameResponse("사용 가능한 닉네임 입니다."); - } -} diff --git a/module-api/src/main/java/inspiration/domain/tag/TagService.java b/module-api/src/main/java/inspiration/domain/tag/TagService.java index 1f24b50..ee2ca00 100644 --- a/module-api/src/main/java/inspiration/domain/tag/TagService.java +++ b/module-api/src/main/java/inspiration/domain/tag/TagService.java @@ -1,16 +1,13 @@ package inspiration.domain.tag; -import inspiration.RestPage; +import inspiration.domain.member.Member; +import inspiration.domain.member.MemberService; +import inspiration.domain.tag.request.TagAddRequestVo; +import inspiration.domain.tag.response.TagResponseVo; import inspiration.exception.ConflictRequestException; import inspiration.exception.NoAccessAuthorizationException; import inspiration.exception.ResourceNotFoundException; -import inspiration.domain.member.Member; -import inspiration.domain.member.MemberService; -import inspiration.domain.tag.request.TagAddRequest; -import inspiration.domain.tag.response.TagResponse; import lombok.RequiredArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -21,60 +18,52 @@ @RequiredArgsConstructor @Service @Transactional +@SuppressWarnings("ClassCanBeRecord") public class TagService { private final TagRepository tagRepository; private final MemberService memberService; @Transactional(readOnly = true) - public RestPage findTags(Pageable pageable, Long memberId) { - + public Page findTags(Pageable pageable, Long memberId) { Member member = memberService.findById(memberId); - - Page tagPage = tagRepository.findAllByMember(member, pageable); - return new RestPage<>(tagPage.map(TagResponse::from)); + return tagRepository.findAllByMember(member, pageable) + .map(TagResponseVo::from); } @Transactional(readOnly = true) - public RestPage indexTags(Pageable pageable, String keyword, Long memberId) { - + public Page indexTags(Pageable pageable, String keyword, Long memberId) { Member member = memberService.findById(memberId); - - Page tagPage = tagRepository.findAllByMemberAndContentContaining(member, keyword, pageable); - return new RestPage<>(tagPage.map(TagResponse::from)); + return tagRepository.findAllByMemberAndContentContaining(member, keyword, pageable) + .map(TagResponseVo::from); } @Transactional(readOnly = true) - public Page searchTags(Pageable pageable, String keyword, Long memberId) { - + public Page searchTags(Pageable pageable, String keyword, Long memberId) { Member member = memberService.findById(memberId); - - Page tagPage = tagRepository.findAllByMemberAndContent(member, keyword, pageable); - return new RestPage<>(tagPage.map(TagResponse::from)); + return tagRepository.findAllByMemberAndContent(member, keyword, pageable) + .map(TagResponseVo::from); } - public TagResponse addTag(TagAddRequest request, Long memberId) { - + public TagResponseVo addTag(TagAddRequestVo requestVo, Long memberId) { Member member = memberService.findById(memberId); - - if (tagRepository.findAllByMemberAndContent(member, request.getContent()).isPresent()) { + if (tagRepository.findAllByMemberAndContent(member, requestVo.getContent()).isPresent()) { throw new ConflictRequestException(); } - Tag tag = request.toEntity(); + Tag tag = requestVo.toEntity(); tag.writeBy(member); - Tag savedTag = tagRepository.save(tag); - return TagResponse.from(savedTag); + return TagResponseVo.from(savedTag); } public Tag getTag(Long id) { return tagRepository.findById(id) - .orElseThrow(ResourceNotFoundException::new); + .orElseThrow(ResourceNotFoundException::new); } public void removeTag(Long id, Long memberId) { Tag tag = tagRepository.findById(id) - .orElseThrow(ResourceNotFoundException::new); + .orElseThrow(ResourceNotFoundException::new); if (!tag.getMember().isSameMember(memberId)) { throw new NoAccessAuthorizationException(); diff --git a/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequest.java b/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequest.java deleted file mode 100644 index 53fed07..0000000 --- a/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package inspiration.domain.tag.request; - -import inspiration.domain.tag.Tag; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - - -@Getter -@ApiModel("Sample Request") -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class TagAddRequest { - - @ApiModelProperty(example = "태그등록 테스트", value = "태그등록 테스트") - @Size(max = 100) - @NotNull - private String content; - - public Tag toEntity() { - return Tag.builder() - .content(this.content) - .build(); - } - -} diff --git a/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequestVo.java b/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequestVo.java new file mode 100644 index 0000000..d4bd8d7 --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/tag/request/TagAddRequestVo.java @@ -0,0 +1,16 @@ +package inspiration.domain.tag.request; + +import inspiration.domain.tag.Tag; +import lombok.Value; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class TagAddRequestVo { + String content; + + public Tag toEntity() { + return Tag.builder() + .content(this.content) + .build(); + } +} diff --git a/module-api/src/main/java/inspiration/domain/tag/response/TagResponseVo.java b/module-api/src/main/java/inspiration/domain/tag/response/TagResponseVo.java new file mode 100644 index 0000000..6ec142c --- /dev/null +++ b/module-api/src/main/java/inspiration/domain/tag/response/TagResponseVo.java @@ -0,0 +1,27 @@ +package inspiration.domain.tag.response; + +import inspiration.domain.member.response.MemberResponseVo; +import inspiration.domain.tag.Tag; +import lombok.Value; + +import java.time.LocalDateTime; + +@Value +@SuppressWarnings("ClassCanBeRecord") +public class TagResponseVo { + Long id; + MemberResponseVo memberResponseVo; + String content; + LocalDateTime createdDatetime; + LocalDateTime updatedDatetime; + + public static TagResponseVo from(Tag tag) { + return new TagResponseVo( + tag.getId(), + MemberResponseVo.of(tag.getMember()), + tag.getContent(), + tag.getCreatedDateTime(), + tag.getUpdatedDateTime() + ); + } +} diff --git a/module-api/src/main/java/inspiration/signup/SignupService.java b/module-api/src/main/java/inspiration/signup/SignupService.java index eb8b72d..03b3051 100644 --- a/module-api/src/main/java/inspiration/signup/SignupService.java +++ b/module-api/src/main/java/inspiration/signup/SignupService.java @@ -1,17 +1,16 @@ package inspiration.signup; -import inspiration.ResultResponse; +import inspiration.auth.TokenResponseVo; import inspiration.auth.jwt.JwtProvider; -import inspiration.auth.TokenResponse; import inspiration.domain.emailauth.EmailAuthRepository; +import inspiration.domain.member.Member; +import inspiration.domain.member.MemberRepository; +import inspiration.domain.member.request.ExtraInfoRequestVo; +import inspiration.domain.member.request.SignUpRequestVo; import inspiration.enumeration.ExceptionType; import inspiration.enumeration.ExpireTimeConstants; import inspiration.exception.ConflictRequestException; import inspiration.exception.PostNotFoundException; -import inspiration.domain.member.Member; -import inspiration.domain.member.MemberRepository; -import inspiration.domain.member.request.SignUpRequest; -import inspiration.domain.member.request.ExtraInfoRequest; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; @@ -22,55 +21,44 @@ @Service @RequiredArgsConstructor +@SuppressWarnings("ClassCanBeRecord") public class SignupService { + private static final String REFRESH_TOKEN_KEY = "refreshToken : "; private final MemberRepository memberRepository; private final EmailAuthRepository emailAuthRepository; private final PasswordEncoder passwordEncoder; private final JwtProvider jwtProvider; private final StringRedisTemplate redisTemplate; - private final String REFRESH_TOKEN_KEY = "refreshToken : "; - @Transactional - public ResultResponse signUp(SignUpRequest request) { - verifyEmail(request.getEmail()); - isValidEmail(request.getEmail()); - isValidNickName(request.getNickName()); - confirmPasswordCheck(request.getConfirmPassword(), request.getPassword()); - - Member member = memberRepository.save(request.toEntity(passwordEncoder)); - TokenResponse tokenResponse = jwtProvider.createTokenDto(member.getId()); - saveRefreshToken(member.getId(), tokenResponse.getRefreshToken()); - - return ResultResponse.of(REFRESH_TOKEN_KEY, tokenResponse); + @Transactional + public TokenResponseVo signUp(SignUpRequestVo signUpRequestVo) { + verifyEmail(signUpRequestVo.getEmail()); + isValidEmail(signUpRequestVo.getEmail()); + isValidNickName(signUpRequestVo.getNickName()); + confirmPasswordCheck(signUpRequestVo.getConfirmPassword(), signUpRequestVo.getPassword()); + + Member member = memberRepository.save(signUpRequestVo.toEntity(passwordEncoder)); + TokenResponseVo tokenResponseVo = jwtProvider.createTokenVo(member.getId()); + saveRefreshToken(member.getId(), tokenResponseVo.getRefreshToken()); + return tokenResponseVo; } @Transactional(readOnly = true) - public ResultResponse checkNickName(String nickname) { - + public void checkNickName(String nickname) { isValidNickName(nickname); - - return ResultResponse.from("사용할 수 있는 닉네임입니다."); } @Transactional - public void updateExtraInfo(String email, ExtraInfoRequest request) { - + public void updateExtraInfo(String email, ExtraInfoRequestVo requestVo) { Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); - - member.updateExtraInfo(request.getGender(), request.getAge(), request.getJob()); + .orElseThrow(() -> new PostNotFoundException(ExceptionType.USER_NOT_EXISTS.getMessage())); + member.updateExtraInfo(requestVo.getGender(), requestVo.getAge(), requestVo.getJob()); } - public ResultResponse validSignUpEmailStatus(String email) { - - if (memberRepository.existsByEmail(email)) { - - return ResultResponse.of(ExceptionType.EMAIL_ALREADY_AUTHENTICATED.getMessage(), true); - } - - return ResultResponse.of(ExceptionType.EMAIL_NOT_AUTHENTICATED.getMessage(), false); + public boolean existsMemberByEmail(String email) { + return memberRepository.existsByEmail(email); } private void confirmPasswordCheck(String confirmPasswordCheck, String password) { diff --git a/module-domain/src/main/java/inspiration/domain/inspiration/Inspiration.java b/module-domain/src/main/java/inspiration/domain/inspiration/Inspiration.java index f517eb2..5491285 100644 --- a/module-domain/src/main/java/inspiration/domain/inspiration/Inspiration.java +++ b/module-domain/src/main/java/inspiration/domain/inspiration/Inspiration.java @@ -6,7 +6,8 @@ import lombok.*; import javax.persistence.*; -import java.util.*; +import java.util.ArrayList; +import java.util.List; @Entity @Getter diff --git a/module-web/src/main/java/inspiration/infrastructure/WebConfig.java b/module-web/src/main/java/inspiration/infrastructure/WebConfig.java index f07829f..14aa2fe 100644 --- a/module-web/src/main/java/inspiration/infrastructure/WebConfig.java +++ b/module-web/src/main/java/inspiration/infrastructure/WebConfig.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import inspiration.enumeration.HttpHeaderType; import inspiration.enumeration.TokenType; +import inspiration.infrastructure.security.AuthenticationPrincipalArgumentResolver; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Getter; diff --git a/module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipal.java b/module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipal.java similarity index 85% rename from module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipal.java rename to module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipal.java index 1774f20..e11a72b 100644 --- a/module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipal.java +++ b/module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipal.java @@ -1,4 +1,4 @@ -package inspiration.infrastructure; +package inspiration.infrastructure.security; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipalArgumentResolver.java b/module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipalArgumentResolver.java similarity index 95% rename from module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipalArgumentResolver.java rename to module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipalArgumentResolver.java index b7a84a5..c48e2e8 100644 --- a/module-web/src/main/java/inspiration/infrastructure/AuthenticationPrincipalArgumentResolver.java +++ b/module-web/src/main/java/inspiration/infrastructure/security/AuthenticationPrincipalArgumentResolver.java @@ -1,4 +1,4 @@ -package inspiration.infrastructure; +package inspiration.infrastructure.security; import org.springframework.core.MethodParameter; import org.springframework.security.core.Authentication; diff --git a/module-api/src/main/java/inspiration/auth/jwt/JwtAuthenticationProvider.java b/module-web/src/main/java/inspiration/infrastructure/security/JwtAuthenticationProvider.java similarity index 96% rename from module-api/src/main/java/inspiration/auth/jwt/JwtAuthenticationProvider.java rename to module-web/src/main/java/inspiration/infrastructure/security/JwtAuthenticationProvider.java index e449ec1..66d0765 100644 --- a/module-api/src/main/java/inspiration/auth/jwt/JwtAuthenticationProvider.java +++ b/module-web/src/main/java/inspiration/infrastructure/security/JwtAuthenticationProvider.java @@ -1,5 +1,6 @@ -package inspiration.auth.jwt; +package inspiration.infrastructure.security; +import inspiration.auth.jwt.JwtProvider; import inspiration.exception.UnauthorizedAccessRequestException; import inspiration.domain.member.Member; import inspiration.domain.member.MemberService; diff --git a/module-api/src/main/java/inspiration/auth/jwt/JwtPreAuthenticatedProcessingFilter.java b/module-web/src/main/java/inspiration/infrastructure/security/JwtPreAuthenticatedProcessingFilter.java similarity index 93% rename from module-api/src/main/java/inspiration/auth/jwt/JwtPreAuthenticatedProcessingFilter.java rename to module-web/src/main/java/inspiration/infrastructure/security/JwtPreAuthenticatedProcessingFilter.java index c3cc0c1..9e79622 100644 --- a/module-api/src/main/java/inspiration/auth/jwt/JwtPreAuthenticatedProcessingFilter.java +++ b/module-web/src/main/java/inspiration/infrastructure/security/JwtPreAuthenticatedProcessingFilter.java @@ -1,4 +1,4 @@ -package inspiration.auth.jwt; +package inspiration.infrastructure.security; import inspiration.enumeration.TokenType; import lombok.extern.slf4j.Slf4j; diff --git a/module-web/src/main/java/inspiration/infrastructure/SecurityConfiguration.java b/module-web/src/main/java/inspiration/infrastructure/security/SecurityConfiguration.java similarity index 96% rename from module-web/src/main/java/inspiration/infrastructure/SecurityConfiguration.java rename to module-web/src/main/java/inspiration/infrastructure/security/SecurityConfiguration.java index c1d7782..5f2b1c9 100644 --- a/module-web/src/main/java/inspiration/infrastructure/SecurityConfiguration.java +++ b/module-web/src/main/java/inspiration/infrastructure/security/SecurityConfiguration.java @@ -1,11 +1,9 @@ -package inspiration.infrastructure; +package inspiration.infrastructure.security; import com.fasterxml.jackson.databind.ObjectMapper; -import inspiration.ResultResponse; -import inspiration.auth.jwt.JwtAuthenticationProvider; -import inspiration.auth.jwt.JwtPreAuthenticatedProcessingFilter; import inspiration.auth.jwt.JwtProvider; import inspiration.domain.member.MemberService; +import inspiration.v1.ResultResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -31,7 +29,7 @@ @EnableWebSecurity @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { - private static final String[] ALLOWED_URI_PATTERN = new String[] { + private static final String[] ALLOWED_URI_PATTERN = new String[]{ "/api/v1/signup/**", "/api/v1/auth/**", "/api/v1/reissue", diff --git a/module-api/src/main/java/inspiration/RestPage.java b/module-web/src/main/java/inspiration/v1/RestPage.java similarity index 97% rename from module-api/src/main/java/inspiration/RestPage.java rename to module-web/src/main/java/inspiration/v1/RestPage.java index acc2e7a..7e80bef 100644 --- a/module-api/src/main/java/inspiration/RestPage.java +++ b/module-web/src/main/java/inspiration/v1/RestPage.java @@ -1,4 +1,4 @@ -package inspiration; +package inspiration.v1; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/module-web/src/main/java/inspiration/v1/ResultResponse.java b/module-web/src/main/java/inspiration/v1/ResultResponse.java index 9492590..b239893 100644 --- a/module-web/src/main/java/inspiration/v1/ResultResponse.java +++ b/module-web/src/main/java/inspiration/v1/ResultResponse.java @@ -1,30 +1,27 @@ package inspiration.v1; -import lombok.*; +import inspiration.enumeration.ExceptionType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ResultResponse { +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ResultResponse { private String message; - private Object data; + private T data; - private ResultResponse(String message) { - this.message = message; + public static ResultResponse from(String message) { + return new ResultResponse<>(message, null); } - private ResultResponse(Object data){ - this.data = data; + public static ResultResponse success(T data) { + return new ResultResponse<>("SUCCESS", data); } - public static ResultResponse from(String message) { - - return new ResultResponse(message); - } - - public static ResultResponse from(Object data) { - - return new ResultResponse(data); + public static ResultResponse of(ExceptionType exceptionType, T data) { + return new ResultResponse<>(exceptionType.getMessage(), data); } } diff --git a/module-web/src/main/java/inspiration/v1/advice/GlobalExceptionController.java b/module-web/src/main/java/inspiration/v1/advice/GlobalExceptionController.java index 9cafcd0..e6e8f70 100644 --- a/module-web/src/main/java/inspiration/v1/advice/GlobalExceptionController.java +++ b/module-web/src/main/java/inspiration/v1/advice/GlobalExceptionController.java @@ -6,6 +6,7 @@ import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.validation.Errors; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -22,7 +23,7 @@ public class GlobalExceptionController { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) - protected ResultResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + protected ResultResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.warn("MethodArgumentNotValid", e); return ResultResponse.from( Optional.of(e.getBindingResult()) @@ -37,9 +38,10 @@ protected ResultResponse handleMethodArgumentNotValidException(MethodArgumentNot HttpRequestMethodNotSupportedException.class, MethodArgumentTypeMismatchException.class, ResourceNotFoundException.class, - MaxUploadSizeExceededException.class + MaxUploadSizeExceededException.class, + HttpMediaTypeNotSupportedException.class }) - protected ResultResponse handleBadRequestException(Exception e) { + protected ResultResponse handleBadRequestException(Exception e) { log.warn("BAD_REQUEST", e); return ResultResponse.from(e.getMessage()); } @@ -53,28 +55,28 @@ protected ResultResponse handleBadRequestException(Exception e) { NoAccessAuthorizationException.class, RefreshTokenException.class, }) - public ResultResponse handleUnauthorizedException(Exception e) { + public ResultResponse handleUnauthorizedException(Exception e) { log.warn("UNAUTHORIZED", e); return ResultResponse.from(e.getMessage()); } @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(PostNotFoundException.class) - protected ResultResponse handleNotFoundException(PostNotFoundException e) { + protected ResultResponse handleNotFoundException(PostNotFoundException e) { log.warn("NOT_FOUND", e); return ResultResponse.from(e.getMessage()); } @ResponseStatus(HttpStatus.CONFLICT) @ExceptionHandler(ConflictRequestException.class) - protected ResultResponse handleConflictRequestException(ConflictRequestException e) { + protected ResultResponse handleConflictRequestException(ConflictRequestException e) { log.warn("CONFLICT", e); return ResultResponse.from(e.getMessage()); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) - protected ResultResponse handleException(Exception e) { + protected ResultResponse handleException(Exception e) { log.error("INTERNAL_SERVER_ERROR", e); return ResultResponse.from("서버 에러가 발생했습니다. 잠시 후 다시 시도해주세요."); } diff --git a/module-web/src/main/java/inspiration/v1/auth/AuthAssembler.java b/module-web/src/main/java/inspiration/v1/auth/AuthAssembler.java new file mode 100644 index 0000000..9c83c2f --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/auth/AuthAssembler.java @@ -0,0 +1,32 @@ +package inspiration.v1.auth; + +import inspiration.auth.TokenResponseVo; +import inspiration.auth.request.LoginRequestVo; +import inspiration.auth.request.TokenRequestVo; +import org.springframework.stereotype.Component; + +@Component +public class AuthAssembler { + public TokenRequestVo toTokenRequestVo(TokenRequest tokenRequest) { + return new TokenRequestVo( + tokenRequest.getAccessToken(), + tokenRequest.getRefreshToken() + ); + } + + public LoginRequestVo toLoginRequestVo(LoginRequest loginRequest) { + return new LoginRequestVo( + loginRequest.getEmail(), + loginRequest.getPassword() + ); + } + + public TokenResponse toTokenResponse(TokenResponseVo tokenResponseVo) { + return new TokenResponse( + tokenResponseVo.getAccessToken(), + tokenResponseVo.getRefreshToken(), + tokenResponseVo.getAccessTokenExpireDate(), + tokenResponseVo.getMemberId() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/auth/AuthController.java b/module-web/src/main/java/inspiration/v1/auth/AuthController.java index ea1ff8f..8ac1223 100644 --- a/module-web/src/main/java/inspiration/v1/auth/AuthController.java +++ b/module-web/src/main/java/inspiration/v1/auth/AuthController.java @@ -1,12 +1,10 @@ package inspiration.v1.auth; -import inspiration.ResultResponse; import inspiration.auth.AuthService; -import inspiration.auth.TokenResponse; -import inspiration.auth.request.LoginRequest; +import inspiration.auth.TokenResponseVo; +import inspiration.auth.request.LoginRequestVo; import inspiration.domain.emailauth.EmailAuthService; -import inspiration.domain.emailauth.request.AuthenticateEmailRequest; -import inspiration.domain.emailauth.request.SendEmailRequest; +import inspiration.v1.ResultResponse; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -16,6 +14,8 @@ import javax.validation.Valid; +import static inspiration.enumeration.ExceptionType.*; + @RestController @RequiredArgsConstructor @RequestMapping("api/v1/auth") @@ -24,6 +24,7 @@ public class AuthController { private final EmailAuthService emailAuthService; private final AuthService authService; + private final AuthAssembler authAssembler; @Value("${ygtang.redirect-url.password-auth}") private String passwordAuthRedirectUrl; @@ -36,8 +37,10 @@ public class AuthController { public ResultResponse login( @RequestBody LoginRequest request ) { - - return authService.login(request); + LoginRequestVo loginRequestVo = authAssembler.toLoginRequestVo(request); + TokenResponseVo tokenResponseVo = authService.login(loginRequestVo); + TokenResponse tokenResponse = authAssembler.toTokenResponse(tokenResponseVo); + return ResultResponse.success(tokenResponse); } @PostMapping("/sends-email/signup") @@ -74,15 +77,21 @@ public RedirectView authenticateEmailOfResetPasswordForAuth(@ModelAttribute Auth @GetMapping("/signup/email/{email}/status") @ApiOperation(value = "회원가입을 위한 해당 이메일의 인증 상태 여부 반환", notes = "회원가입을 위한 해당 이메일의 인증 상태 여부를 반환한다.") - public ResultResponse validAuthenticateEmailStatusOfSignup(@PathVariable String email) { - - return emailAuthService.validAuthenticateEmailStatusOfSignup(email); + public ResultResponse validAuthenticateEmailStatusOfSignup(@PathVariable String email) { + boolean isValid = emailAuthService.validAuthenticateEmailStatusOfSignup(email); + return ResultResponse.of( + isValid ? EMAIL_ALREADY_AUTHENTICATED : EMAIL_NOT_AUTHENTICATED, + isValid + ); } @GetMapping("/passwords/reset/email/{email}/status") @ApiOperation(value = "비밀번호 초기화를 위한 이메일 인증 상태 여부 반환", notes = "비밀번호 초기화를 위한 해당 이메일의 인증 상태 여부를 반환한다.") - public ResultResponse validAuthenticateEmailStatusOfResetPassword(@PathVariable String email) { - - return emailAuthService.validAuthenticateEmailStatusOfResetPassword(email); + public ResultResponse validAuthenticateEmailStatusOfResetPassword(@PathVariable String email) { + boolean isValid = emailAuthService.isValidAuthenticateEmailStatusOfResetPassword(email); + return ResultResponse.of( + isValid ? EMAIL_ALREADY_AUTHENTICATED : EMAIL_NOT_AUTHENTICATED, + isValid + ); } } diff --git a/module-api/src/main/java/inspiration/domain/emailauth/request/AuthenticateEmailRequest.java b/module-web/src/main/java/inspiration/v1/auth/AuthenticateEmailRequest.java similarity index 67% rename from module-api/src/main/java/inspiration/domain/emailauth/request/AuthenticateEmailRequest.java rename to module-web/src/main/java/inspiration/v1/auth/AuthenticateEmailRequest.java index 60dbcc9..80e1ca1 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/request/AuthenticateEmailRequest.java +++ b/module-web/src/main/java/inspiration/v1/auth/AuthenticateEmailRequest.java @@ -1,4 +1,4 @@ -package inspiration.domain.emailauth.request; +package inspiration.v1.auth; import lombok.*; diff --git a/module-api/src/main/java/inspiration/auth/request/LoginRequest.java b/module-web/src/main/java/inspiration/v1/auth/LoginRequest.java similarity index 94% rename from module-api/src/main/java/inspiration/auth/request/LoginRequest.java rename to module-web/src/main/java/inspiration/v1/auth/LoginRequest.java index 08ef859..e3fd743 100644 --- a/module-api/src/main/java/inspiration/auth/request/LoginRequest.java +++ b/module-web/src/main/java/inspiration/v1/auth/LoginRequest.java @@ -1,4 +1,4 @@ -package inspiration.auth.request; +package inspiration.v1.auth; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/module-api/src/main/java/inspiration/domain/emailauth/request/SendEmailRequest.java b/module-web/src/main/java/inspiration/v1/auth/SendEmailRequest.java similarity index 87% rename from module-api/src/main/java/inspiration/domain/emailauth/request/SendEmailRequest.java rename to module-web/src/main/java/inspiration/v1/auth/SendEmailRequest.java index 98a5a18..be2aa34 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/request/SendEmailRequest.java +++ b/module-web/src/main/java/inspiration/v1/auth/SendEmailRequest.java @@ -1,4 +1,4 @@ -package inspiration.domain.emailauth.request; +package inspiration.v1.auth; import lombok.Data; diff --git a/module-api/src/main/java/inspiration/auth/request/TokenRequest.java b/module-web/src/main/java/inspiration/v1/auth/TokenRequest.java similarity index 82% rename from module-api/src/main/java/inspiration/auth/request/TokenRequest.java rename to module-web/src/main/java/inspiration/v1/auth/TokenRequest.java index d4b78bc..8055aca 100644 --- a/module-api/src/main/java/inspiration/auth/request/TokenRequest.java +++ b/module-web/src/main/java/inspiration/v1/auth/TokenRequest.java @@ -1,4 +1,4 @@ -package inspiration.auth.request; +package inspiration.v1.auth; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/module-api/src/main/java/inspiration/auth/TokenResponse.java b/module-web/src/main/java/inspiration/v1/auth/TokenResponse.java similarity index 65% rename from module-api/src/main/java/inspiration/auth/TokenResponse.java rename to module-web/src/main/java/inspiration/v1/auth/TokenResponse.java index 3b0d794..526ea2f 100644 --- a/module-api/src/main/java/inspiration/auth/TokenResponse.java +++ b/module-web/src/main/java/inspiration/v1/auth/TokenResponse.java @@ -1,22 +1,17 @@ -package inspiration.auth; +package inspiration.v1.auth; +import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +import lombok.Data; import lombok.NoArgsConstructor; -@Getter -@Builder -@NoArgsConstructor +@Data +@NoArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor public class TokenResponse { - private String accessToken; - private String refreshToken; - private Long accessTokenExpireDate; - private long memberId; } diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequest.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationAddRequest.java similarity index 65% rename from module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequest.java rename to module-web/src/main/java/inspiration/v1/inspiration/InspirationAddRequest.java index b92e3d8..a71f1c6 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationAddRequest.java +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationAddRequest.java @@ -1,6 +1,5 @@ -package inspiration.domain.inspiration.request; +package inspiration.v1.inspiration; -import inspiration.domain.inspiration.Inspiration; import inspiration.domain.inspiration.InspirationType; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -20,24 +19,15 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class InspirationAddRequest { - @ApiModelProperty( notes = "IMAGE | LINK | TEXT") + @ApiModelProperty(notes = "IMAGE | LINK | TEXT") @NotNull private InspirationType type; @ApiModelProperty(notes = "LINK, TEXT의 경우 해당 데이터 입력") private String content; - @ApiModelProperty( notes = "메모 입력") + @ApiModelProperty(notes = "메모 입력") private String memo; private MultipartFile file; private List tagIds; - - public Inspiration toEntity() { - return Inspiration.builder() - .content(this.content) - .type(this.type) - .memo(this.memo) - .build(); - } - } diff --git a/module-web/src/main/java/inspiration/v1/inspiration/InspirationAssembler.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationAssembler.java new file mode 100644 index 0000000..fde07eb --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationAssembler.java @@ -0,0 +1,89 @@ +package inspiration.v1.inspiration; + +import inspiration.domain.inspiration.opengraph.OpenGraphService; +import inspiration.domain.inspiration.request.InspirationAddRequestVo; +import inspiration.domain.inspiration.request.InspirationModifyRequestVo; +import inspiration.domain.inspiration.request.InspirationTagRequestVo; +import inspiration.domain.inspiration.response.InspirationResponseVo; +import inspiration.v1.RestPage; +import inspiration.v1.inspiration.opengraph.OpenGraphAssembler; +import inspiration.v1.member.MemberAssembler; +import inspiration.v1.tag.TagAssembler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + +@Component +@RequiredArgsConstructor +@SuppressWarnings("ClassCanBeRecord") +public class InspirationAssembler { + private final MemberAssembler memberAssembler; + private final TagAssembler tagAssembler; + private final OpenGraphAssembler openGraphAssembler; + private final ThreadPoolTaskExecutor threadPoolTaskExecutor; + private final OpenGraphService openGraphService; + + public InspirationAddRequestVo toInspirationAddRequestVo(InspirationAddRequest inspirationAddRequest) { + return new InspirationAddRequestVo( + inspirationAddRequest.getType(), + inspirationAddRequest.getContent(), + inspirationAddRequest.getMemo(), + inspirationAddRequest.getFile(), + inspirationAddRequest.getTagIds() + ); + } + + public InspirationModifyRequestVo toInspirationModifyRequestVo(InspirationModifyRequest inspirationModifyRequest) { + return new InspirationModifyRequestVo( + inspirationModifyRequest.getId(), + inspirationModifyRequest.getMemo() + ); + } + + public InspirationResponse toInspirationResponse(InspirationResponseVo inspirationResponseVo) { + return new InspirationResponse( + inspirationResponseVo.getId(), + memberAssembler.toMemberResponse(inspirationResponseVo.getMemberResponseVo()), + inspirationResponseVo.getTagResponseVoList() + .stream() + .map(tagAssembler::toTagResponse) + .toList(), + inspirationResponseVo.getType(), + inspirationResponseVo.getContent(), + inspirationResponseVo.getMemo(), + openGraphAssembler.toOpenGraphResponse( + openGraphService.getOpenGraphResponseVo( + inspirationResponseVo.getType(), + inspirationResponseVo.getContent() + ) + ), + inspirationResponseVo.getCreatedDateTime(), + inspirationResponseVo.getUpdatedDateTime() + ); + } + + public RestPage toInspirationResponseRestPage(Page inspirationResponseVoPage) { + return new RestPage<>( + inspirationResponseVoPage.stream() + .parallel() + .map(it -> (Callable) () -> toInspirationResponse(it)) + .map(it -> threadPoolTaskExecutor.submitListenable(it).completable()) + .map(CompletableFuture::join) + .toList(), + inspirationResponseVoPage.getPageable().getPageNumber(), + inspirationResponseVoPage.getPageable().getPageSize(), + inspirationResponseVoPage.getTotalElements() + ); + } + + public InspirationTagRequestVo toInspirationTagRequestVo(InspirationTagRequest request) { + return new InspirationTagRequestVo( + request.getId(), + request.getTagId() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/inspiration/InspirationController.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationController.java index fd1a043..255b557 100644 --- a/module-web/src/main/java/inspiration/v1/inspiration/InspirationController.java +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationController.java @@ -1,14 +1,18 @@ package inspiration.v1.inspiration; -import inspiration.domain.inspiration.InspirationType; -import inspiration.infrastructure.AuthenticationPrincipal; +import inspiration.domain.inspiration.opengraph.OpenGraphService; +import inspiration.v1.RestPage; import inspiration.domain.inspiration.InspirationService; -import inspiration.domain.inspiration.request.InspirationAddRequest; -import inspiration.domain.inspiration.request.InspirationModifyRequest; -import inspiration.domain.inspiration.request.InspirationTagRequest; -import inspiration.domain.inspiration.response.InspirationResponse; -import inspiration.domain.inspiration.response.OpenGraphResponse; +import inspiration.domain.inspiration.InspirationType; +import inspiration.domain.inspiration.request.InspirationAddRequestVo; +import inspiration.domain.inspiration.request.InspirationModifyRequestVo; +import inspiration.domain.inspiration.request.InspirationTagRequestVo; +import inspiration.domain.inspiration.response.InspirationResponseVo; +import inspiration.domain.inspiration.response.OpenGraphResponseVo; +import inspiration.infrastructure.security.AuthenticationPrincipal; import inspiration.v1.ResultResponse; +import inspiration.v1.inspiration.opengraph.OpenGraphAssembler; +import inspiration.v1.inspiration.opengraph.OpenGraphResponse; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; @@ -19,6 +23,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; import springfox.documentation.annotations.ApiIgnore; import javax.servlet.http.HttpServletRequest; @@ -32,44 +37,62 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/inspiration") +@SuppressWarnings({"ClassCanBeRecord", "unused"}) public class InspirationController { - private final InspirationService inspirationService; + private final InspirationAssembler inspirationAssembler; + private final OpenGraphService openGraphService; + private final OpenGraphAssembler openGraphAssembler; @GetMapping("/list") @ApiOperation(value = "영감 조회", notes = "영감 리스트를 조회한다") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity inspirationList(Pageable pageable, @ApiIgnore @AuthenticationPrincipal Long memberId ) { - Page inspirationResponsePage = inspirationService.findInspirations(pageable, memberId); - return ResponseEntity.ok().body(ResultResponse.from(inspirationResponsePage)); + public ResponseEntity>> inspirationList( + Pageable pageable, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + Page inspirationResponseVoPage = inspirationService.findInspirations(pageable, memberId); + RestPage inspirationResponseRestPage = + inspirationAssembler.toInspirationResponseRestPage(inspirationResponseVoPage); + return ResponseEntity.ok().body( + ResultResponse.success(inspirationResponseRestPage) + ); } @GetMapping("/{id}") @ApiOperation(value = "영감 조회", notes = "영감 id로 조회한다") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity findInspiration(@PathVariable @NotNull Long id, - @ApiIgnore @AuthenticationPrincipal Long memberId ) { - InspirationResponse inspirationResponse = inspirationService.findInspiration(id, memberId); - return ResponseEntity.ok().body(ResultResponse.from(inspirationResponse)); + public ResponseEntity> findInspiration( + @PathVariable @NotNull Long id, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + InspirationResponseVo inspirationResponseVo = inspirationService.findInspiration(id, memberId); + InspirationResponse inspirationResponse = inspirationAssembler.toInspirationResponse(inspirationResponseVo); + return ResponseEntity.ok().body(ResultResponse.success(inspirationResponse)); } @PostMapping("/add") @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "영감 등록", notes = "영감 등록을 요청한다") @ApiResponses({ - @ApiResponse(code = 201, message = "정상적으로 등록되었습니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 201, message = "정상적으로 등록되었습니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity inspirationAdd(HttpServletRequest httpServletRequest, @ModelAttribute @Valid InspirationAddRequest request, @ApiIgnore @AuthenticationPrincipal Long memberId ) { - Long id = inspirationService.addInspiration(request, memberId); - - final URI uri = URI.create(httpServletRequest.getRequestURI() + "/" + id); + public ResponseEntity inspirationAdd( + @ModelAttribute @Valid InspirationAddRequest request, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + InspirationAddRequestVo inspirationAddRequestVo = inspirationAssembler.toInspirationAddRequestVo(request); + Long id = inspirationService.addInspiration(inspirationAddRequestVo, memberId); + final URI uri = UriComponentsBuilder.newInstance() + .path("/api/v1/inspiration/{inspirationId}") + .build(id); return ResponseEntity.created(uri).build(); } @@ -77,13 +100,17 @@ public ResponseEntity inspirationAdd(HttpServletRequest httpServ @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "영감 수정(메모 수정)", notes = "영감 수정을 요청한다") @ApiResponses({ - @ApiResponse(code = 201, message = "정상적으로 등록되었습니다.") - ,@ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 201, message = "정상적으로 등록되었습니다."), + @ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity inspirationModify(HttpServletRequest httpServletRequest, @RequestBody @Valid InspirationModifyRequest request, @ApiIgnore @AuthenticationPrincipal Long memberId) { - Long id = inspirationService.modifyMemo(request, memberId); - + public ResponseEntity inspirationModify( + HttpServletRequest httpServletRequest, + @RequestBody @Valid InspirationModifyRequest request, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + InspirationModifyRequestVo inspirationModifyRequestVo = inspirationAssembler.toInspirationModifyRequestVo(request); + Long id = inspirationService.modifyMemo(inspirationModifyRequestVo, memberId); final URI uri = URI.create(httpServletRequest.getRequestURI() + "/" + id); return ResponseEntity.created(uri).build(); } @@ -92,43 +119,56 @@ public ResponseEntity inspirationModify(HttpServletRequest httpS @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "영감 태깅", notes = "영감 태깅을 요청한다") @ApiResponses({ - @ApiResponse(code = 201, message = "정상적으로 등록되었습니다.") - ,@ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다. | 존재하지 않는 태그ID 입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 201, message = "정상적으로 등록되었습니다."), + @ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다. | 존재하지 않는 태그ID 입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity inspirationTagging(HttpServletRequest httpServletRequest, @RequestBody @Valid InspirationTagRequest request, @ApiIgnore @AuthenticationPrincipal Long memberId) { - Long id = inspirationService.tagInspiration(request, memberId); - + public ResponseEntity inspirationTagging( + HttpServletRequest httpServletRequest, + @RequestBody @Valid InspirationTagRequest request, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + InspirationTagRequestVo inspirationTagRequestVo = inspirationAssembler.toInspirationTagRequestVo(request); + Long id = inspirationService.tagInspiration(inspirationTagRequestVo, memberId); final URI uri = URI.create(httpServletRequest.getRequestURI() + "/" + id); return ResponseEntity.created(uri).build(); } - @PostMapping ("/tag/") + @PostMapping("/tag/") @ApiOperation(value = "영감 필터링", notes = "조건(태그, 영감 타입, 영감 생성 일자)에 맞는 영감 조회를 요청한다") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity findInspirationByTag(Pageable pageable, @RequestBody List tagIds, - @RequestParam(required = false) List types, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime createdDateTimeFrom, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime createdDateTimeTo, - @ApiIgnore @AuthenticationPrincipal Long memberId) { - Page inspirationResponsePage = inspirationService.findInspirationsByTags(tagIds, types, createdDateTimeFrom, createdDateTimeTo, memberId, pageable); - - return ResponseEntity.ok().body(ResultResponse.from(inspirationResponsePage)); + public ResponseEntity>> findInspirationByTag( + Pageable pageable, + @RequestBody List tagIds, + @RequestParam(required = false) List types, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime createdDateTimeFrom, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime createdDateTimeTo, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + Page inspirationResponseVoPage = + inspirationService.findInspirationsByTags(tagIds, types, createdDateTimeFrom, createdDateTimeTo, memberId, pageable); + RestPage inspirationResponseRestPage = + inspirationAssembler.toInspirationResponseRestPage(inspirationResponseVoPage); + return ResponseEntity.ok().body( + ResultResponse.success(inspirationResponseRestPage) + ); } @DeleteMapping("/untag/{id}/{tagId}") @ApiOperation(value = "영감 태깅 해제", notes = "영감 태깅을 해제한다") @ApiResponses({ - @ApiResponse(code = 2010, message = "정상적으로 해제되었습니다.") - ,@ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다. | 존재하지 않는 태그ID 입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 201, message = "정상적으로 해제되었습니다."), + @ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다. | 존재하지 않는 태그ID 입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity inspirationUntagging(@PathVariable @NotNull Long id, - @PathVariable @NotNull Long tagId, - @ApiIgnore @AuthenticationPrincipal Long memberId) { + public ResponseEntity inspirationUntagging( + @PathVariable @NotNull Long id, + @PathVariable @NotNull Long tagId, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { inspirationService.unTagInspiration(id, tagId, memberId); return ResponseEntity.ok().build(); } @@ -136,36 +176,35 @@ public ResponseEntity inspirationUntagging(@PathVariable @NotNul @DeleteMapping("/remove/{id}") @ApiOperation(value = "영감 삭제", notes = "영감 삭제를 요청한다.") @ApiResponses({ - @ApiResponse(code = 200, message = "정상적으로 삭제되었습니다.") - ,@ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 200, message = "정상적으로 삭제되었습니다."), + @ApiResponse(code = 400, message = "존재하지 않는 영감ID 입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity inspirationRemove(@PathVariable @NotNull Long id, - @ApiIgnore @AuthenticationPrincipal Long memberId) { - + public ResponseEntity inspirationRemove( + @PathVariable @NotNull Long id, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { inspirationService.unTagInspirationByInspiration(id, memberId); - inspirationService.removeInspiration(id, memberId); - return ResponseEntity.ok().build(); } @DeleteMapping("/remove/all") @ApiOperation(value = "영감 전체 삭제", notes = "해당 사용자의 전체 영감을 삭제한다.") public void inspirationRemoveAll(@ApiIgnore @AuthenticationPrincipal Long memberId) { - inspirationService.removeAllInspiration(memberId); } @GetMapping("/link/availiable") @ApiOperation(value = "링크 적합성 판단", notes = "링크 적합성을 판단한다") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - ,@ApiResponse(code = 400, message = "적합하지 않은 링크입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 400, message = "적합하지 않은 링크입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity inspirationList(@RequestParam @NotBlank String link) { - OpenGraphResponse openGraphResponse = inspirationService.getOpenGraphResponse(link); - return ResponseEntity.ok().body(ResultResponse.from(openGraphResponse)); + public ResponseEntity> inspirationList(@RequestParam @NotBlank String link) { + OpenGraphResponseVo openGraphResponseVo = openGraphService.getOpenGraphResponseVo(InspirationType.LINK, link); + OpenGraphResponse openGraphResponse = openGraphAssembler.toOpenGraphResponse(openGraphResponseVo); + return ResponseEntity.ok().body(ResultResponse.success(openGraphResponse)); } } diff --git a/module-web/src/main/java/inspiration/v1/inspiration/InspirationModifyRequest.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationModifyRequest.java new file mode 100644 index 0000000..050ec4c --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationModifyRequest.java @@ -0,0 +1,15 @@ +package inspiration.v1.inspiration; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +public class InspirationModifyRequest { + + @NotNull(message = "영감 id는 필수 입력 입니다.") + private Long id; + + private String memo; + +} diff --git a/module-web/src/main/java/inspiration/v1/inspiration/InspirationResponse.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationResponse.java new file mode 100644 index 0000000..d712f72 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationResponse.java @@ -0,0 +1,37 @@ +package inspiration.v1.inspiration; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import inspiration.domain.inspiration.InspirationType; +import inspiration.v1.member.MemberResponse; +import inspiration.v1.tag.TagResponse; +import inspiration.v1.inspiration.opengraph.OpenGraphResponse; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@SuppressWarnings("ClassCanBeRecord") +public class InspirationResponse { + + private final Long id; + private final MemberResponse memberResponse; + private final List tagResponses; + private final InspirationType type; + private final String content; + private final String memo; + private final OpenGraphResponse openGraphResponse; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private final LocalDateTime createdDatetime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private final LocalDateTime updatedDatetime; +} diff --git a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequest.java b/module-web/src/main/java/inspiration/v1/inspiration/InspirationTagRequest.java similarity index 56% rename from module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequest.java rename to module-web/src/main/java/inspiration/v1/inspiration/InspirationTagRequest.java index f9abd72..47730bc 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/request/InspirationTagRequest.java +++ b/module-web/src/main/java/inspiration/v1/inspiration/InspirationTagRequest.java @@ -1,18 +1,12 @@ -package inspiration.domain.inspiration.request; +package inspiration.v1.inspiration; import io.swagger.annotations.ApiModel; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.Data; import javax.validation.constraints.NotNull; - -@Getter @ApiModel("Sample Request InspirationTagRequest") -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Data public class InspirationTagRequest { @NotNull(message = "영감 id는 필수 입력 입니다.") diff --git a/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphAssembler.java b/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphAssembler.java new file mode 100644 index 0000000..60bb397 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphAssembler.java @@ -0,0 +1,21 @@ +package inspiration.v1.inspiration.opengraph; + +import inspiration.domain.inspiration.response.OpenGraphResponseVo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OpenGraphAssembler { + + public OpenGraphResponse toOpenGraphResponse(OpenGraphResponseVo openGraphResponseVo) { + return new OpenGraphResponse( + openGraphResponseVo.getCode(), + openGraphResponseVo.getDescription(), + openGraphResponseVo.getSiteName(), + openGraphResponseVo.getTitle(), + openGraphResponseVo.getUrl(), + openGraphResponseVo.getImage() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphResponse.java b/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphResponse.java new file mode 100644 index 0000000..cf96a77 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/inspiration/opengraph/OpenGraphResponse.java @@ -0,0 +1,14 @@ +package inspiration.v1.inspiration.opengraph; + +import lombok.Data; + +@Data +@SuppressWarnings("ClassCanBeRecord") +public class OpenGraphResponse { + private final int code; + private final String description; + private final String siteName; + private final String title; + private final String url; + private final String image; +} diff --git a/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java b/module-web/src/main/java/inspiration/v1/member/ExtraInfoRequest.java similarity index 77% rename from module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java rename to module-web/src/main/java/inspiration/v1/member/ExtraInfoRequest.java index 6199666..82e20d2 100644 --- a/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java +++ b/module-web/src/main/java/inspiration/v1/member/ExtraInfoRequest.java @@ -1,16 +1,13 @@ -package inspiration.domain.member.request; +package inspiration.v1.member; import inspiration.domain.member.AgeGroupType; import inspiration.domain.member.GenderType; import io.swagger.annotations.ApiModelProperty; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.Data; import javax.validation.constraints.NotNull; -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Data public class ExtraInfoRequest { @ApiModelProperty( notes = "F | M | N") diff --git a/module-web/src/main/java/inspiration/v1/member/MemberAssembler.java b/module-web/src/main/java/inspiration/v1/member/MemberAssembler.java new file mode 100644 index 0000000..78f272a --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/member/MemberAssembler.java @@ -0,0 +1,24 @@ +package inspiration.v1.member; + +import inspiration.domain.member.response.MemberInfoVo; +import inspiration.domain.member.response.MemberResponseVo; +import org.springframework.stereotype.Component; + +@Component +public class MemberAssembler { + public MemberInfoResponse toMemberInfoResponse(MemberInfoVo memberInfoVo) { + return new MemberInfoResponse( + memberInfoVo.getNickName(), + memberInfoVo.getEmail() + ); + } + + public MemberResponse toMemberResponse(MemberResponseVo memberResponseVo) { + return new MemberResponse( + memberResponseVo.getId(), + memberResponseVo.getNickName(), + memberResponseVo.getEmail(), + memberResponseVo.getCreatedDateTime() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/member/MemberController.java b/module-web/src/main/java/inspiration/v1/member/MemberController.java index ac3370b..c2b0a89 100644 --- a/module-web/src/main/java/inspiration/v1/member/MemberController.java +++ b/module-web/src/main/java/inspiration/v1/member/MemberController.java @@ -1,15 +1,12 @@ package inspiration.v1.member; -import inspiration.auth.AuthService; -import inspiration.domain.emailauth.request.SendEmailRequest; import inspiration.domain.inspiration.InspirationService; import inspiration.domain.member.MemberService; -import inspiration.domain.member.request.UpdateNicknameRequest; -import inspiration.domain.member.request.UpdatePasswordRequest; -import inspiration.domain.member.response.MemberInfoResponse; +import inspiration.domain.member.response.MemberInfoVo; import inspiration.domain.tag.TagService; -import inspiration.infrastructure.AuthenticationPrincipal; +import inspiration.infrastructure.security.AuthenticationPrincipal; import inspiration.signup.SignupService; +import inspiration.v1.auth.SendEmailRequest; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -21,27 +18,30 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/members") +@SuppressWarnings("ClassCanBeRecord") public class MemberController { - private final MemberService memberService; - private final AuthService authService; private final SignupService signupService; private final InspirationService inspirationService; private final TagService tagService; + private final MemberAssembler memberAssembler; @PutMapping("/passwords/change") @ApiOperation(value = "패스워드를 변경.", notes = "패스워드를 변경한다.") - public void changePassword(@ApiIgnore @AuthenticationPrincipal Long memberId, @RequestBody @Valid UpdatePasswordRequest request) { - + public void changePassword( + @ApiIgnore @AuthenticationPrincipal Long memberId, + @RequestBody @Valid UpdatePasswordRequest request + ) { memberService.changePassword(memberId, request.getConfirmPassword(), request.getPassword()); } @PutMapping("/nickname/change") @ApiOperation(value = "닉네임 변경", notes = "닉네임을 변경한다.") - public void changeNickname(@ApiIgnore @AuthenticationPrincipal Long memberId, @RequestBody @Valid UpdateNicknameRequest request) { - + public void changeNickname( + @ApiIgnore @AuthenticationPrincipal Long memberId, + @RequestBody @Valid UpdateNicknameRequest request + ) { signupService.checkNickName(request.getNickname()); - memberService.changeNickname(memberId, request.getNickname()); } @@ -49,25 +49,21 @@ public void changeNickname(@ApiIgnore @AuthenticationPrincipal Long memberId, @R @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "초기화된 비밀번호를 이메일로 전송", notes = "초기화된 비밀번호를 이메일로 전송한다.") public void resetPasswordEmailSend(@RequestBody @Valid SendEmailRequest request) { - memberService.resetPasswordEmailSend(request.getEmail()); } @GetMapping("/info") @ApiOperation(value = "사용자 정보 조회", notes = "사용자 정보를 조회한다.") - public MemberInfoResponse getUserInfo(@ApiIgnore @AuthenticationPrincipal Long memberId) { - - return authService.getUserInfo(memberId); + public MemberInfoResponse getMemberInfo(@ApiIgnore @AuthenticationPrincipal Long memberId) { + MemberInfoVo memberInfoVo = memberService.getMemberInfo(memberId); + return memberAssembler.toMemberInfoResponse(memberInfoVo); } @DeleteMapping("/remove") @ApiOperation(value = "계정 삭제", notes = "계정을 삭제한다.") public void removeMember(@ApiIgnore @AuthenticationPrincipal Long memberId) { - inspirationService.removeAllInspiration(memberId); - tagService.removeAllTag(memberId); - memberService.removeUser(memberId); } } diff --git a/module-web/src/main/java/inspiration/v1/member/MemberInfoResponse.java b/module-web/src/main/java/inspiration/v1/member/MemberInfoResponse.java new file mode 100644 index 0000000..6a683a7 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/member/MemberInfoResponse.java @@ -0,0 +1,10 @@ +package inspiration.v1.member; + +import lombok.Data; + +@Data +@SuppressWarnings("ClassCanBeRecord") +public class MemberInfoResponse { + private final String nickName; + private final String email; +} diff --git a/module-api/src/main/java/inspiration/domain/member/response/MemberResponse.java b/module-web/src/main/java/inspiration/v1/member/MemberResponse.java similarity index 56% rename from module-api/src/main/java/inspiration/domain/member/response/MemberResponse.java rename to module-web/src/main/java/inspiration/v1/member/MemberResponse.java index 45b9bd7..1cb53b7 100644 --- a/module-api/src/main/java/inspiration/domain/member/response/MemberResponse.java +++ b/module-web/src/main/java/inspiration/v1/member/MemberResponse.java @@ -1,20 +1,20 @@ -package inspiration.domain.member.response; +package inspiration.v1.member; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import inspiration.domain.member.Member; -import lombok.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; -@Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Data @NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor public class MemberResponse { - private Long id; private String nickName; private String email; @@ -23,13 +23,4 @@ public class MemberResponse { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @JsonDeserialize(using = LocalDateTimeDeserializer.class) private LocalDateTime createdDateTime; - - public static MemberResponse of(Member member) { - return MemberResponse.builder() - .id(member.getId()) - .nickName(member.getNickname()) - .email(member.getEmail()) - .createdDateTime(member.getCreatedDateTime()) - .build(); - } } diff --git a/module-web/src/main/java/inspiration/v1/member/UpdateNicknameRequest.java b/module-web/src/main/java/inspiration/v1/member/UpdateNicknameRequest.java new file mode 100644 index 0000000..9b37a56 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/member/UpdateNicknameRequest.java @@ -0,0 +1,8 @@ +package inspiration.v1.member; + +import lombok.Data; + +@Data +public class UpdateNicknameRequest { + private String nickname; +} diff --git a/module-api/src/main/java/inspiration/domain/member/request/UpdatePasswordRequest.java b/module-web/src/main/java/inspiration/v1/member/UpdatePasswordRequest.java similarity index 75% rename from module-api/src/main/java/inspiration/domain/member/request/UpdatePasswordRequest.java rename to module-web/src/main/java/inspiration/v1/member/UpdatePasswordRequest.java index 5ccaf6e..59067b6 100644 --- a/module-api/src/main/java/inspiration/domain/member/request/UpdatePasswordRequest.java +++ b/module-web/src/main/java/inspiration/v1/member/UpdatePasswordRequest.java @@ -1,14 +1,11 @@ -package inspiration.domain.member.request; +package inspiration.v1.member; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Data public class UpdatePasswordRequest { @NotBlank(message = "올바른 비밀번호를 입력해주세요.") diff --git a/module-web/src/main/java/inspiration/v1/reissue/ReissueController.java b/module-web/src/main/java/inspiration/v1/reissue/ReissueController.java index 5e34e48..34b415d 100644 --- a/module-web/src/main/java/inspiration/v1/reissue/ReissueController.java +++ b/module-web/src/main/java/inspiration/v1/reissue/ReissueController.java @@ -1,7 +1,10 @@ package inspiration.v1.reissue; -import inspiration.ResultResponse; import inspiration.auth.AuthService; +import inspiration.auth.TokenResponseVo; +import inspiration.v1.ResultResponse; +import inspiration.v1.auth.AuthAssembler; +import inspiration.v1.auth.TokenResponse; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -10,15 +13,18 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/reissue") +@SuppressWarnings("ClassCanBeRecord") public class ReissueController { private final AuthService authService; + private final AuthAssembler authAssembler; @PostMapping @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "리프레쉬 토큰 재발급", notes = "리프레쉬 토큰을 재발급합니다.") - public ResultResponse reissue(@RequestHeader(value = "REFRESH-TOKEN") String refreshToken) { - - return authService.reissue(refreshToken); + public ResultResponse reissue(@RequestHeader(value = "REFRESH-TOKEN") String refreshToken) { + TokenResponseVo tokenResponseVo = authService.reissue(refreshToken); + TokenResponse tokenResponse = authAssembler.toTokenResponse(tokenResponseVo); + return ResultResponse.success(tokenResponse); } } diff --git a/module-web/src/main/java/inspiration/v1/signup/SignUpAssembler.java b/module-web/src/main/java/inspiration/v1/signup/SignUpAssembler.java new file mode 100644 index 0000000..a6daee2 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/signup/SignUpAssembler.java @@ -0,0 +1,26 @@ +package inspiration.v1.signup; + +import inspiration.v1.member.ExtraInfoRequest; +import inspiration.domain.member.request.ExtraInfoRequestVo; +import inspiration.domain.member.request.SignUpRequestVo; +import org.springframework.stereotype.Component; + +@Component +public class SignUpAssembler { + public SignUpRequestVo toSignUpRequestVo(SignUpRequest signUpRequest) { + return new SignUpRequestVo( + signUpRequest.getEmail(), + signUpRequest.getNickName(), + signUpRequest.getPassword(), + signUpRequest.getConfirmPassword() + ); + } + + public ExtraInfoRequestVo toExtraInfoRequestVo(ExtraInfoRequest extraInfoRequest) { + return new ExtraInfoRequestVo( + extraInfoRequest.getGender(), + extraInfoRequest.getAge(), + extraInfoRequest.getJob() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/signup/SignUpController.java b/module-web/src/main/java/inspiration/v1/signup/SignUpController.java index 19296ee..58daa2c 100644 --- a/module-web/src/main/java/inspiration/v1/signup/SignUpController.java +++ b/module-web/src/main/java/inspiration/v1/signup/SignUpController.java @@ -1,10 +1,14 @@ package inspiration.v1.signup; -import inspiration.ResultResponse; -import inspiration.auth.TokenResponse; -import inspiration.domain.member.request.ExtraInfoRequest; -import inspiration.domain.member.request.SignUpRequest; +import inspiration.v1.ResultResponse; +import inspiration.domain.member.request.ExtraInfoRequestVo; +import inspiration.enumeration.ExceptionType; +import inspiration.v1.auth.TokenResponse; +import inspiration.auth.TokenResponseVo; +import inspiration.v1.member.ExtraInfoRequest; +import inspiration.domain.member.request.SignUpRequestVo; import inspiration.signup.SignupService; +import inspiration.v1.auth.AuthAssembler; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -18,35 +22,44 @@ @SuppressWarnings("ClassCanBeRecord") public class SignUpController { private final SignupService signupService; + private final SignUpAssembler signUpAssembler; + private final AuthAssembler authAssembler; @PostMapping @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "회원가입", notes = "회원가입을 합니다.") public ResultResponse signUp(@RequestBody @Valid SignUpRequest request) { - - return signupService.signUp(request); + SignUpRequestVo signUpRequestVo = signUpAssembler.toSignUpRequestVo(request); + TokenResponseVo tokenResponseVo = signupService.signUp(signUpRequestVo); + TokenResponse tokenResponse = authAssembler.toTokenResponse(tokenResponseVo); + return ResultResponse.success(tokenResponse); } @PatchMapping("/extra-informations") @ResponseStatus(HttpStatus.OK) @ApiOperation(value = "사용자 추가 정보", notes = "사용자 추가 정보를 저장합니다.") - public void updateExtraInfo(@RequestBody @Valid ExtraInfoRequest request, - @RequestParam(value = "email") String email) { - - signupService.updateExtraInfo(email, request); + public void updateExtraInfo( + @RequestBody @Valid ExtraInfoRequest request, + @RequestParam(value = "email") String email + ) { + ExtraInfoRequestVo extraInfoRequestVo = signUpAssembler.toExtraInfoRequestVo(request); + signupService.updateExtraInfo(email, extraInfoRequestVo); } @GetMapping("/nicknames/{nickname}/exists") @ApiOperation(value = "닉네임 중복 확인", notes = "중복된 닉네임이 있는지 검사합니다.") - public ResultResponse checkNickname(@PathVariable String nickname) { - - return signupService.checkNickName(nickname); + public ResultResponse checkNickname(@PathVariable String nickname) { + signupService.checkNickName(nickname); + return ResultResponse.from("사용할 수 있는 닉네임입니다."); } @GetMapping("/{email}/status") @ApiOperation(value = "해당 유저가 가입되어 있는 유저인지 상태 여부 반환", notes = "해당 유저가 가입되어 있는 유저인지 상태 여부를 반환한다.") - public ResultResponse validSignUpEmailStatus(@PathVariable String email) { - - return signupService.validSignUpEmailStatus(email); + public ResultResponse validSignUpEmailStatus(@PathVariable String email) { + boolean exist = signupService.existsMemberByEmail(email); + return ResultResponse.of( + exist ? ExceptionType.EMAIL_ALREADY_AUTHENTICATED : ExceptionType.EMAIL_NOT_AUTHENTICATED, + exist + ); } } diff --git a/module-api/src/main/java/inspiration/domain/member/request/SignUpRequest.java b/module-web/src/main/java/inspiration/v1/signup/SignUpRequest.java similarity index 69% rename from module-api/src/main/java/inspiration/domain/member/request/SignUpRequest.java rename to module-web/src/main/java/inspiration/v1/signup/SignUpRequest.java index 7f0557d..d3d9b43 100644 --- a/module-api/src/main/java/inspiration/domain/member/request/SignUpRequest.java +++ b/module-web/src/main/java/inspiration/v1/signup/SignUpRequest.java @@ -1,8 +1,6 @@ -package inspiration.domain.member.request; +package inspiration.v1.signup; -import inspiration.domain.member.Member; import lombok.*; -import org.springframework.security.crypto.password.PasswordEncoder; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; @@ -26,13 +24,4 @@ public class SignUpRequest { @NotBlank(message = "올바른 비밀번호를 입력해주세요.") @Size(min = 6, max = 20, message = "비밀번호는 6자 이상 20자 이하로 입력해주세요.") private String confirmPassword; - - @Builder - public Member toEntity(PasswordEncoder passwordEncoder) { - return Member.builder() - .email(email) - .password(passwordEncoder.encode(password)) - .nickname(nickName) - .build(); - } } diff --git a/module-web/src/main/java/inspiration/v1/tag/TagAddRequest.java b/module-web/src/main/java/inspiration/v1/tag/TagAddRequest.java new file mode 100644 index 0000000..3885c88 --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/tag/TagAddRequest.java @@ -0,0 +1,16 @@ +package inspiration.v1.tag; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Data +public class TagAddRequest { + + @ApiModelProperty(example = "태그등록 테스트", value = "태그등록 테스트") + @Size(max = 100) + @NotNull + private String content; +} diff --git a/module-web/src/main/java/inspiration/v1/tag/TagAssembler.java b/module-web/src/main/java/inspiration/v1/tag/TagAssembler.java new file mode 100644 index 0000000..0fd8dad --- /dev/null +++ b/module-web/src/main/java/inspiration/v1/tag/TagAssembler.java @@ -0,0 +1,37 @@ +package inspiration.v1.tag; + +import inspiration.domain.tag.request.TagAddRequestVo; +import inspiration.domain.tag.response.TagResponseVo; +import inspiration.v1.member.MemberAssembler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +@SuppressWarnings("ClassCanBeRecord") +public class TagAssembler { + private final MemberAssembler memberAssembler; + + public TagAddRequestVo toTagAddRequestVo(TagAddRequest tagAddRequest) { + return new TagAddRequestVo( + tagAddRequest.getContent() + ); + } + + public TagResponse toTagResponse(TagResponseVo tagResponseVo) { + if (tagResponseVo == null) { + return null; + } + return new TagResponse( + tagResponseVo.getId(), + Optional.ofNullable(tagResponseVo.getMemberResponseVo()) + .map(memberAssembler::toMemberResponse) + .orElse(null), + tagResponseVo.getContent(), + tagResponseVo.getCreatedDatetime(), + tagResponseVo.getUpdatedDatetime() + ); + } +} diff --git a/module-web/src/main/java/inspiration/v1/tag/TagController.java b/module-web/src/main/java/inspiration/v1/tag/TagController.java index 01c7a40..0705751 100644 --- a/module-web/src/main/java/inspiration/v1/tag/TagController.java +++ b/module-web/src/main/java/inspiration/v1/tag/TagController.java @@ -1,10 +1,11 @@ package inspiration.v1.tag; -import inspiration.infrastructure.AuthenticationPrincipal; +import inspiration.v1.RestPage; import inspiration.domain.inspiration.InspirationService; import inspiration.domain.tag.TagService; -import inspiration.domain.tag.request.TagAddRequest; -import inspiration.domain.tag.response.TagResponse; +import inspiration.domain.tag.request.TagAddRequestVo; +import inspiration.domain.tag.response.TagResponseVo; +import inspiration.infrastructure.security.AuthenticationPrincipal; import inspiration.v1.ResultResponse; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; @@ -26,62 +27,78 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/tag") +@SuppressWarnings("ClassCanBeRecord") public class TagController { private final TagService tagService; private final InspirationService inspirationService; - + private final TagAssembler tagAssembler; @GetMapping("/list") @ApiOperation(value = "태그 조회", notes = "태그 리스트를 조회한다") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity tagList(Pageable pageable, - @ApiIgnore @AuthenticationPrincipal Long memberId ) { - Page tagResponsePage = tagService.findTags(pageable, memberId); - return ResponseEntity.ok().body(ResultResponse.from(tagResponsePage)); + public ResponseEntity>> tagList( + Pageable pageable, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + Page tagResponsePage = tagService.findTags(pageable, memberId) + .map(tagAssembler::toTagResponse); + RestPage tagResponseRestPage = new RestPage<>(tagResponsePage); + return ResponseEntity.ok().body(ResultResponse.success(tagResponseRestPage)); } @GetMapping("/index/{keyword}") @ApiOperation(value = "태그 검색(LIKE)", notes = "태그를 키워드로 검색한다.(LIKE)") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity indexTag(Pageable pageable, - @PathVariable @NotBlank String keyword, - @ApiIgnore @AuthenticationPrincipal Long memberId ) { - Page tagResponsePage = tagService.indexTags(pageable, keyword, memberId); - return ResponseEntity.ok().body(ResultResponse.from(tagResponsePage)); + public ResponseEntity>> indexTag( + Pageable pageable, + @PathVariable @NotBlank String keyword, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + Page tagResponsePage = tagService.indexTags(pageable, keyword, memberId) + .map(tagAssembler::toTagResponse); + RestPage tagResponseRestPage = new RestPage<>(tagResponsePage); + return ResponseEntity.ok().body(ResultResponse.success(tagResponseRestPage)); } @GetMapping("/search/{keyword}") @ApiOperation(value = "태그 검색(일치)", notes = "태그를 키워드로 검색한다.(일치)") @ApiResponses({ - @ApiResponse(code = 200, message = "성공입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 200, message = "성공입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity searchTag(Pageable pageable, - @PathVariable @NotBlank String keyword, - @ApiIgnore @AuthenticationPrincipal Long memberId ) { - Page tagResponsePage = tagService.searchTags(pageable, keyword, memberId); - return ResponseEntity.ok().body(ResultResponse.from(tagResponsePage)); + public ResponseEntity>> searchTag( + Pageable pageable, + @PathVariable @NotBlank String keyword, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + Page tagResponsePage = tagService.searchTags(pageable, keyword, memberId) + .map(tagAssembler::toTagResponse); + RestPage tagResponseRestPage = new RestPage<>(tagResponsePage); + return ResponseEntity.ok().body(ResultResponse.success(tagResponseRestPage)); } - + @PostMapping("/add") @ResponseStatus(HttpStatus.CREATED) @ApiOperation(value = "태그 등록", notes = "태그 등록을 요청한다") @ApiResponses({ - @ApiResponse(code = 201, message = "정상적으로 등록되었습니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") + @ApiResponse(code = 201, message = "정상적으로 등록되었습니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다.") }) - public ResponseEntity tagAdd(HttpServletRequest httpServletRequest, - @RequestBody @Valid TagAddRequest request, - @ApiIgnore @AuthenticationPrincipal Long memberId ) { - TagResponse tagResponse = tagService.addTag(request, memberId); - + public ResponseEntity tagAdd( + HttpServletRequest httpServletRequest, + @RequestBody @Valid TagAddRequest request, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { + TagAddRequestVo tagAddRequestVo = tagAssembler.toTagAddRequestVo(request); + TagResponseVo tagResponseVo = tagService.addTag(tagAddRequestVo, memberId); + TagResponse tagResponse = tagAssembler.toTagResponse(tagResponseVo); final URI uri = URI.create(httpServletRequest.getRequestURI() + "/" + tagResponse.getId()); return ResponseEntity.created(uri).body(tagResponse); } @@ -89,17 +106,16 @@ public ResponseEntity tagAdd(HttpServletRequest httpServletRequest, @DeleteMapping("/remove/{id}") @ApiOperation(value = "태그 삭제", notes = "태그 삭제를 요청한다.") @ApiResponses({ - @ApiResponse(code = 200, message = "정상적으로 삭제되었습니다.") - ,@ApiResponse(code = 400, message = "존재하지 않는 태그ID 입니다.") - , @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") + @ApiResponse(code = 200, message = "정상적으로 삭제되었습니다."), + @ApiResponse(code = 400, message = "존재하지 않는 태그ID 입니다."), + @ApiResponse(code = 401, message = "토큰이 정상적으로 인증되지 않았습니다. | 해당 리소스 수정권한이 없습니다.") }) - public ResponseEntity tagRemove(@PathVariable @NotNull Long id, - @ApiIgnore @AuthenticationPrincipal Long memberId) { - + public ResponseEntity tagRemove( + @PathVariable @NotNull Long id, + @ApiIgnore @AuthenticationPrincipal Long memberId + ) { inspirationService.unTagInspirationByTag(id, memberId); - tagService.removeTag(id, memberId); - return ResponseEntity.ok().build(); } diff --git a/module-api/src/main/java/inspiration/domain/tag/response/TagResponse.java b/module-web/src/main/java/inspiration/v1/tag/TagResponse.java similarity index 58% rename from module-api/src/main/java/inspiration/domain/tag/response/TagResponse.java rename to module-web/src/main/java/inspiration/v1/tag/TagResponse.java index 7b31355..db22598 100644 --- a/module-api/src/main/java/inspiration/domain/tag/response/TagResponse.java +++ b/module-web/src/main/java/inspiration/v1/tag/TagResponse.java @@ -1,21 +1,21 @@ -package inspiration.domain.tag.response; +package inspiration.v1.tag; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import inspiration.domain.member.response.MemberResponse; -import inspiration.domain.tag.Tag; -import lombok.*; +import inspiration.v1.member.MemberResponse; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; -@Getter -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Data @NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor public class TagResponse { - private Long id; private MemberResponse memberResponse; private String content; @@ -29,16 +29,4 @@ public class TagResponse { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @JsonDeserialize(using = LocalDateTimeDeserializer.class) private LocalDateTime updatedDatetime; - - public static TagResponse from(Tag tag) { - return TagResponse.builder() - .id(tag.getId()) - .content(tag.getContent()) - .createdDatetime(tag.getCreatedDateTime()) - .updatedDatetime(tag.getUpdatedDateTime()) - .memberResponse(MemberResponse.of(tag.getMember())) - .build(); - } - - } diff --git a/module-web/src/test/java/inspiration/TestRedisConfiguration.java b/module-web/src/test/java/inspiration/TestRedisConfiguration.java index 0302dfd..cac6d97 100644 --- a/module-web/src/test/java/inspiration/TestRedisConfiguration.java +++ b/module-web/src/test/java/inspiration/TestRedisConfiguration.java @@ -7,6 +7,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +/** + * FIXME: embedded redis 시작 실패해서 테스트 실패하는 현상 발생함. + * @DirtiesContext 붙이면 실패하지는않지만 실행시간이 늘어남. + */ @TestConfiguration public class TestRedisConfiguration { diff --git a/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java b/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java index c7400ae..b7ae5ec 100644 --- a/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java +++ b/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inspiration.TestRedisConfiguration; import inspiration.domain.emailauth.SignUpEmailSendService; -import inspiration.domain.emailauth.request.SendEmailRequest; import inspiration.enumeration.RedisKey; import inspiration.redis.RedisService; import org.junit.jupiter.api.DisplayName; @@ -13,6 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +24,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@DirtiesContext @SpringBootTest(classes = TestRedisConfiguration.class) @AutoConfigureMockMvc @Transactional diff --git a/module-web/src/test/java/inspiration/v1/member/MemberControllerTest.java b/module-web/src/test/java/inspiration/v1/member/MemberControllerTest.java new file mode 100644 index 0000000..1c31c49 --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/member/MemberControllerTest.java @@ -0,0 +1,176 @@ +package inspiration.v1.member; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.TestRedisConfiguration; +import inspiration.aws.AwsS3Service; +import inspiration.domain.emailauth.ResetPasswordEmailSendService; +import inspiration.domain.emailauth.SignUpEmailSendService; +import inspiration.domain.member.Member; +import inspiration.domain.member.MemberRepository; +import inspiration.domain.passwordauth.PasswordAuth; +import inspiration.domain.passwordauth.PasswordAuthRepository; +import inspiration.v1.ResultResponse; +import inspiration.v1.auth.SendEmailRequest; +import inspiration.v1.auth.TokenResponse; +import inspiration.v1.util.MvcResultHelper; +import inspiration.v1.util.SignUpApiTestHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DirtiesContext +@SpringBootTest(classes = TestRedisConfiguration.class) +@AutoConfigureMockMvc +@Transactional +class MemberControllerTest { + private static final String EMAIL = "localpart@domain.com"; + private static final String NICKNAME = "nickname"; + private static final String PASSWORD = "password"; + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private MemberRepository memberRepository; + @MockBean + private SignUpEmailSendService signUpEmailSendService; + @MockBean + private ResetPasswordEmailSendService resetPasswordEmailSendService; + @MockBean + private PasswordAuthRepository passwordAuthRepository; + @MockBean + private AwsS3Service awsS3Service; + + private String accessToken; + + @BeforeEach + void setUp() throws Exception { + var signUpApiTestHelper = new SignUpApiTestHelper(mockMvc, objectMapper, signUpEmailSendService); + MvcResult signUpResult = signUpApiTestHelper.signUp(EMAIL, NICKNAME, PASSWORD); + var response = MvcResultHelper.parse( + signUpResult, + new TypeReference>() { + } + ); + accessToken = response.getData().getAccessToken(); + } + + @DisplayName("패스워드 변경") + @Test + void changePassword() throws Exception { + // given + var changedPassword = "changedPassword"; + var updatePasswordRequest = new UpdatePasswordRequest(); + updatePasswordRequest.setPassword(changedPassword); + updatePasswordRequest.setConfirmPassword(changedPassword); + // when + mockMvc.perform( + put("/api/v1/members/passwords/change") + .header("accessToken", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(updatePasswordRequest))) + // then + .andExpect(status().isOk()); + } + + @DisplayName("닉네임 변경: 성공") + @Test + void changeNickname() throws Exception { + // given + String updatedNickname = "updatedNickname"; + var updateNicknameRequest = new UpdateNicknameRequest(); + updateNicknameRequest.setNickname(updatedNickname); + // when + mockMvc.perform( + put("/api/v1/members/nickname/change") + .header("accessToken", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(updateNicknameRequest))) + // then 1 + .andExpect(status().isOk()); + // then 2 + Member member = memberRepository.findByEmail(EMAIL).orElseThrow(AssertionError::new); + assertThat(member.getNickname()).isEqualTo(updatedNickname); + } + + @DisplayName("닉네임 변경: 이미 사용중인 닉네임인 경우 실패") + @Test + void changeNickname_duplicated() throws Exception { + // given + String duplicatedNickname = "nickname"; + var updateNicknameRequest = new UpdateNicknameRequest(); + updateNicknameRequest.setNickname(duplicatedNickname); + // when + mockMvc.perform( + put("/api/v1/members/nickname/change") + .header("accessToken", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(updateNicknameRequest))) + // then 1 + .andExpect(status().isConflict()); + // then 2 + Member member = memberRepository.findByEmail(EMAIL).orElseThrow(AssertionError::new); + assertThat(member.getNickname()).isEqualTo(NICKNAME); + } + + @DisplayName("초기화된 비밀번호 이메일 전송") + @Test + void sendEmailContainingResetPassword() throws Exception { + // given + var sendEmailRequest = new SendEmailRequest(); + sendEmailRequest.setEmail(EMAIL); + doNothing().when(resetPasswordEmailSendService).send(anyString(), anyString()); + when(passwordAuthRepository.findByEmail(anyString())) + .thenReturn(Optional.of(new PasswordAuth(EMAIL, true))); + // when + mockMvc.perform( + post("/api/v1/members/sends-email/reset-passwords") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(sendEmailRequest))) + // then 1 + .andExpect(status().isCreated()); + // then 2 + verify(resetPasswordEmailSendService, only()).send(anyString(), anyString()); + verify(passwordAuthRepository, times(1)).findByEmail(anyString()); + verify(passwordAuthRepository, times(1)).delete(any()); + } + + @DisplayName("사용자 정보 조회") + @Test + void getMemberInfo() throws Exception { + mockMvc.perform( + get("/api/v1/members/info") + .header("accessToken", accessToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.nickName").value(NICKNAME)) + .andExpect(jsonPath("$.email").value(EMAIL)); + } + + @DisplayName("계정 삭제") + @Test + void removeMember() throws Exception { + mockMvc.perform( + delete("/api/v1/members/remove") + .header("accessToken", accessToken)) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/module-web/src/test/java/inspiration/v1/reissue/ReissueControllerTest.java b/module-web/src/test/java/inspiration/v1/reissue/ReissueControllerTest.java new file mode 100644 index 0000000..138cc6e --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/reissue/ReissueControllerTest.java @@ -0,0 +1,85 @@ +package inspiration.v1.reissue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.TestRedisConfiguration; +import inspiration.auth.TokenResponseVo; +import inspiration.auth.jwt.JwtProvider; +import inspiration.domain.emailauth.EmailAuthRepository; +import inspiration.domain.member.MemberRepository; +import inspiration.domain.member.MemberService; +import inspiration.domain.member.request.SignUpRequestVo; +import inspiration.signup.SignupService; +import inspiration.v1.auth.TokenResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DirtiesContext +@SpringBootTest(classes = TestRedisConfiguration.class) +@AutoConfigureMockMvc +@Transactional +class ReissueControllerTest { + private static final String EMAIL = "localpart@domain.com"; + private static final String NICKNAME = "nickname"; + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private SignupService signupService; + @Autowired + private MemberService memberService; + @Autowired + private MemberRepository memberRepository; + @MockBean + private EmailAuthRepository emailAuthRepository; + private Long memberId; + private String refreshToken; + + @BeforeEach + void setUp() throws Exception { + when(emailAuthRepository.existsByEmail(any())).thenReturn(true); + TokenResponseVo tokenResponseVo = signupService.signUp( + new SignUpRequestVo( + EMAIL, + "nickname", + "password", + "password" + ) + ); + memberId = tokenResponseVo.getMemberId(); + refreshToken = tokenResponseVo.getRefreshToken(); + verify(emailAuthRepository, only()).existsByEmail(any()); + } + + @DisplayName("리프레시 토큰 재발급") + @Test + void reissue() throws Exception { + // given + // when + mockMvc.perform( + post("/api/v1/reissue") + .header("REFRESH-TOKEN", refreshToken)) + // then + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.data.accessToken").isNotEmpty()) + .andExpect(jsonPath("$.data.refreshToken").isNotEmpty()) + .andExpect(jsonPath("$.data.accessTokenExpireDate").isNotEmpty()) + .andExpect(jsonPath("$.data.memberId").value(memberId)); + } +} \ No newline at end of file diff --git a/module-web/src/test/java/inspiration/v1/signup/SignUpController2Test.java b/module-web/src/test/java/inspiration/v1/signup/SignUpController2Test.java new file mode 100644 index 0000000..be86edd --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/signup/SignUpController2Test.java @@ -0,0 +1,136 @@ +package inspiration.v1.signup; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.TestRedisConfiguration; +import inspiration.domain.emailauth.EmailAuthRepository; +import inspiration.domain.member.*; +import inspiration.domain.member.request.SignUpRequestVo; +import inspiration.signup.SignupService; +import inspiration.v1.member.ExtraInfoRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DirtiesContext +@SpringBootTest(classes = TestRedisConfiguration.class) +@AutoConfigureMockMvc +@Transactional +class SignUpController2Test { + private static final String EMAIL = "localpart@domain.com"; + private static final String NICKNAME = "nickname"; + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private EmailAuthRepository emailAuthRepository; + @Autowired + private SignupService signupService; + @Autowired + private MemberService memberService; + @Autowired + private MemberRepository memberRepository; + private Long memberId; + + @BeforeEach + void setUp() throws Exception { + when(emailAuthRepository.existsByEmail(any())).thenReturn(true); + memberId = signupService.signUp( + new SignUpRequestVo( + EMAIL, + "nickname", + "password", + "password" + ) + ).getMemberId(); + verify(emailAuthRepository, only()).existsByEmail(any()); + } + + @DisplayName("사용자 추가 정보를 저장") + @Test + void updateExtraInfo() throws Exception { + // given + GenderType genderType = GenderType.MALE; + AgeGroupType ageGroupType = AgeGroupType.EARLY_30S; + String job = "job"; + ExtraInfoRequest extraInfoRequest = new ExtraInfoRequest(); + extraInfoRequest.setGender(genderType); + extraInfoRequest.setAge(ageGroupType); + extraInfoRequest.setJob(job); + // when + mockMvc.perform( + patch("/api/v1/signup/extra-informations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(extraInfoRequest)) + .queryParam("email", EMAIL)) + // then 1 + .andExpect(status().isOk()); + // then 2 + Member member = memberRepository.findById(memberId).orElseThrow(AssertionError::new); + assertThat(member.getGender()).isEqualTo(genderType); + assertThat(member.getAge_group()).isEqualTo(ageGroupType); + assertThat(member.getJob()).isEqualTo(job); + } + + @DisplayName("닉네임 중복 확인: 사용가능") + @Test + void checkNickname_available() throws Exception { + // given + String availableNickname = "availableNickname"; + // when + mockMvc.perform(get("/api/v1/signup/nicknames/{nickname}/exists", availableNickname)) + // then + .andExpect(status().isOk()); + } + + @SuppressWarnings("UnnecessaryLocalVariable") + @DisplayName("닉네임 중복 확인: 사용중") + @Test + void checkNickname_duplicated() throws Exception { + // given + String duplicatedNickname = NICKNAME; + // when + mockMvc.perform(get("/api/v1/signup/nicknames/{nickname}/exists", duplicatedNickname)) + // then + .andExpect(status().isConflict()); + } + + @DisplayName("이메일 가입 여부 확인: 가입 가능") + @Test + void validSignUpEmailStatus_available() throws Exception { + // given + String availableEmail = "availableEmail@domain.com"; + // when + mockMvc.perform(get("/api/v1/signup/{email}/status", availableEmail)) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(false)); + } + + @DisplayName("이메일 가입 여부 확인: 사용중") + @Test + void validSignUpEmailStatus_alreadyUsed() throws Exception { + // given + String alreadyUsedEmail = EMAIL; + // when + mockMvc.perform(get("/api/v1/signup/{email}/status", alreadyUsedEmail)) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + } +} diff --git a/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java b/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java index fdb84e1..5a999bd 100644 --- a/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java +++ b/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java @@ -3,16 +3,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import inspiration.TestRedisConfiguration; import inspiration.domain.emailauth.SignUpEmailSendService; -import inspiration.domain.emailauth.request.SendEmailRequest; +import inspiration.v1.auth.SendEmailRequest; import inspiration.domain.member.Member; import inspiration.domain.member.MemberRepository; -import inspiration.domain.member.request.SignUpRequest; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +26,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@DirtiesContext @SpringBootTest(classes = TestRedisConfiguration.class) @AutoConfigureMockMvc @Transactional @@ -40,6 +42,7 @@ class SignUpControllerTest { @Autowired private MemberRepository memberRepository; + @DisplayName("회원 가입") @Test void signUp() throws Exception { // given diff --git a/module-web/src/test/java/inspiration/v1/tag/TagControllerTest.java b/module-web/src/test/java/inspiration/v1/tag/TagControllerTest.java new file mode 100644 index 0000000..1e180a1 --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/tag/TagControllerTest.java @@ -0,0 +1,144 @@ +package inspiration.v1.tag; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.TestRedisConfiguration; +import inspiration.domain.emailauth.SignUpEmailSendService; +import inspiration.domain.tag.TagRepository; +import inspiration.v1.ResultResponse; +import inspiration.v1.auth.TokenResponse; +import inspiration.v1.util.MvcResultHelper; +import inspiration.v1.util.SignUpApiTestHelper; +import inspiration.v1.util.TagApiTestHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@SpringBootTest(classes = TestRedisConfiguration.class) +@AutoConfigureMockMvc +@Transactional +class TagControllerTest { + private static final String EMAIL = "localpart@domain.com"; + private static final String NICKNAME = "nickname"; + private static final String PASSWORD = "password"; + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private SignUpEmailSendService signUpEmailSendService; + + private String accessToken; + @Autowired + private TagRepository tagRepository; + + @BeforeEach + void setUp() throws Exception { + var signUpApiTestHelper = new SignUpApiTestHelper(mockMvc, objectMapper, signUpEmailSendService); + MvcResult signUpResult = signUpApiTestHelper.signUp(EMAIL, NICKNAME, PASSWORD); + var response = MvcResultHelper.parse( + signUpResult, + new TypeReference>() { + } + ); + accessToken = response.getData().getAccessToken(); + } + + @DisplayName("태그 조회") + @Test + void listTag() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + List tagIds = tagApiTestHelper.addTags(accessToken, "tag1", "tag2", "tag3"); + // when + var actual = tagApiTestHelper.listTags(accessToken, PageRequest.of(0, 10)); + // then + assertThat(actual.getData().getTotalElements()).isEqualTo(3L); + assertThat(actual.getData().getContent()).hasSize(3); + assertThat(actual.getData().getContent()).map(TagResponse::getId).containsAll(tagIds); + assertThat(actual.getData().getContent()).map(TagResponse::getContent).contains("tag1", "tag2", "tag3"); + } + + @DisplayName("태그 like 검색") + @Test + void indexTag() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + tagApiTestHelper.addTag(accessToken, "notStartWithTag"); + List tagIds = tagApiTestHelper.addTags(accessToken, "tag1", "tag2", "tag3"); + // when + var actual = tagApiTestHelper.indexTag(accessToken, "tag", PageRequest.of(0, 10)); + // then + assertThat(actual.getData().getTotalElements()).isEqualTo(3L); + assertThat(actual.getData().getContent()).hasSize(3); + assertThat(actual.getData().getContent()).map(TagResponse::getId).containsAll(tagIds); + assertThat(actual.getData().getContent()).map(TagResponse::getContent).contains("tag1", "tag2", "tag3"); + } + + @DisplayName("태그 like 검색") + @Test + void searchTag() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + List tagIds = tagApiTestHelper.addTags(accessToken, "tag1", "tag2", "tag3"); + // when + var actual = tagApiTestHelper.searchTag(accessToken, "tag1", PageRequest.of(0, 10)); + // then + assertThat(actual.getData().getTotalElements()).isEqualTo(1L); + assertThat(actual.getData().getContent()).hasSize(1); + assertThat(actual.getData().getContent()).map(TagResponse::getId).contains(tagIds.get(0)); + assertThat(actual.getData().getContent()).map(TagResponse::getContent).contains("tag1"); + } + + @DisplayName("태그 등록") + @Test + void addTag() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + var tagName = "tagName"; + // when + TagResponse actual = tagApiTestHelper.addTag(accessToken, tagName); + // then + assertThat(actual.getContent()).isEqualTo(tagName); + } + + @DisplayName("태그 삭제") + @Test + void removeTag() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + var tagResponse = tagApiTestHelper.addTag(accessToken, "tagName"); + var tagId = tagResponse.getId(); + // when + tagApiTestHelper.removeTag(accessToken, tagId); + // then + assertThat(tagRepository.findById(tagId)).isEmpty(); + } + + @DisplayName("태그 전체 삭제") + @Test + void removeAllTags() throws Exception { + // given + var tagApiTestHelper = new TagApiTestHelper(mockMvc, objectMapper); + List tagIds = tagApiTestHelper.addTags(accessToken, "tag1", "tag2", "tag3"); + // when + tagApiTestHelper.removeAllTags(accessToken); + // then + assertThat(tagRepository.findAllById(tagIds)).isEmpty(); + } +} \ No newline at end of file diff --git a/module-web/src/test/java/inspiration/v1/util/MvcResultHelper.java b/module-web/src/test/java/inspiration/v1/util/MvcResultHelper.java new file mode 100644 index 0000000..1087dd8 --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/util/MvcResultHelper.java @@ -0,0 +1,27 @@ +package inspiration.v1.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.test.web.servlet.MvcResult; + +import java.io.IOException; + +public class MvcResultHelper { + private MvcResultHelper() { + } + + public static T parse( + MvcResult mvcResult, + Class clazz + ) throws IOException { + return new ObjectMapper().readValue(mvcResult.getResponse().getContentAsByteArray(), clazz); + } + + public static T parse( + MvcResult mvcResult, + TypeReference typeReference + ) throws IOException { + return new ObjectMapper().readValue(mvcResult.getResponse().getContentAsByteArray(), typeReference); + } + +} diff --git a/module-web/src/test/java/inspiration/v1/util/SignUpApiTestHelper.java b/module-web/src/test/java/inspiration/v1/util/SignUpApiTestHelper.java new file mode 100644 index 0000000..4fdf6aa --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/util/SignUpApiTestHelper.java @@ -0,0 +1,78 @@ +package inspiration.v1.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.domain.emailauth.SignUpEmailSendService; +import inspiration.v1.auth.SendEmailRequest; +import inspiration.v1.signup.SignUpRequest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SuppressWarnings("ClassCanBeRecord") +public class SignUpApiTestHelper { + private final MockMvc mockMvc; + private final ObjectMapper objectMapper; + private final SignUpEmailSendService signUpEmailSendService; + + public SignUpApiTestHelper( + MockMvc mockMvc, + ObjectMapper objectMapper, + SignUpEmailSendService signUpEmailSendService + ) { + this.mockMvc = mockMvc; + this.objectMapper = objectMapper; + this.signUpEmailSendService = signUpEmailSendService; + } + + public MvcResult signUp( + String email, + String nickname, + String password + ) throws Exception { + sendVerificationEmail(email); + confirmEmail(email); + return submitSignUpRequest(email, nickname, password); + } + + private void sendVerificationEmail(String email) throws Exception { + SendEmailRequest sendEmailRequest = new SendEmailRequest(); + sendEmailRequest.setEmail(email); + doNothing().when(signUpEmailSendService).send(any(), any()); + mockMvc.perform( + post("/api/v1/auth/sends-email/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(sendEmailRequest))) + .andExpect(status().isCreated()); + } + + private void confirmEmail(String email) throws Exception { + mockMvc.perform( + get("/api/v1/auth/email/signup") + .queryParam("email", email)) + .andExpect(status().isFound()); + } + + private MvcResult submitSignUpRequest( + String email, + String nickname, + String password + ) throws Exception { + SignUpRequest signUpRequest = new SignUpRequest(); + signUpRequest.setEmail(email); + signUpRequest.setNickName(nickname); + signUpRequest.setPassword(password); + signUpRequest.setConfirmPassword(password); + return mockMvc.perform( + post("/api/v1/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(signUpRequest))) + .andExpect(status().isCreated()) + .andReturn(); + } +} diff --git a/module-web/src/test/java/inspiration/v1/util/TagApiTestHelper.java b/module-web/src/test/java/inspiration/v1/util/TagApiTestHelper.java new file mode 100644 index 0000000..ef4f62c --- /dev/null +++ b/module-web/src/test/java/inspiration/v1/util/TagApiTestHelper.java @@ -0,0 +1,121 @@ +package inspiration.v1.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import inspiration.v1.RestPage; +import inspiration.v1.ResultResponse; +import inspiration.v1.tag.TagAddRequest; +import inspiration.v1.tag.TagResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.Arrays; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SuppressWarnings("ClassCanBeRecord") +public class TagApiTestHelper { + private final MockMvc mockMvc; + private final ObjectMapper objectMapper; + + public TagApiTestHelper(MockMvc mockMvc, ObjectMapper objectMapper) { + this.mockMvc = mockMvc; + this.objectMapper = objectMapper; + } + + public ResultResponse> listTags( + String accessToken, + Pageable pageable + ) throws Exception { + MvcResult mvcResult = mockMvc.perform( + get("/api/v1/tag/list") + .header("accessToken", accessToken) + .queryParam("page", String.valueOf(pageable.getPageNumber())) + .queryParam("size", String.valueOf(pageable.getPageSize()))) + .andReturn(); + return MvcResultHelper.parse(mvcResult, new TypeReference<>() { + }); + } + + public ResultResponse> indexTag( + String accessToken, + String keyword, + Pageable pageable + ) throws Exception { + MvcResult mvcResult = mockMvc.perform( + get("/api/v1/tag/index/{keyword}", keyword) + .header("accessToken", accessToken) + .queryParam("page", String.valueOf(pageable.getPageNumber())) + .queryParam("size", String.valueOf(pageable.getPageSize()))) + .andReturn(); + return MvcResultHelper.parse(mvcResult, new TypeReference<>() { + }); + } + + public ResultResponse> searchTag( + String accessToken, + String keyword, + Pageable pageable + ) throws Exception { + MvcResult mvcResult = mockMvc.perform( + get("/api/v1/tag/search/{keyword}", keyword) + .header("accessToken", accessToken) + .queryParam("page", String.valueOf(pageable.getPageNumber())) + .queryParam("size", String.valueOf(pageable.getPageSize()))) + .andReturn(); + return MvcResultHelper.parse(mvcResult, new TypeReference<>() { + }); + } + + public TagResponse addTag( + String accessToken, + String content + ) throws Exception { + var tagAddRequest = new TagAddRequest(); + tagAddRequest.setContent(content); + MvcResult mvcResult = mockMvc.perform( + post("/api/v1/tag/add") + .header("accessToken", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(tagAddRequest))) + .andReturn(); + return MvcResultHelper.parse(mvcResult, TagResponse.class); + } + + public List addTags( + String accessToken, + String... tagNames + ) { + return Arrays.stream(tagNames) + .map(it -> { + try { + return addTag(accessToken, it); + } catch (Exception e) { + throw new AssertionError(e); + } + }) + .map(TagResponse::getId) + .toList(); + } + + public void removeTag( + String accessToken, + Long tagId + ) throws Exception { + mockMvc.perform( + delete("/api/v1/tag/remove/{id}", tagId) + .header("accessToken", accessToken)) + .andExpect(status().isOk()); + } + + public void removeAllTags(String accessToken) throws Exception { + mockMvc.perform( + delete("/api/v1/tag/remove/all") + .header("accessToken", accessToken)) + .andExpect(status().isOk()); + } +}