Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 마이페이지 조회 및 로그인 로직 추가 #23

Merged
merged 16 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package org.gachon.checkmate.domain.member.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import org.gachon.checkmate.domain.member.dto.request.EmailPostRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignInRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignUpRequestDto;
import org.gachon.checkmate.domain.member.dto.request.PasswordResetRequestDto;
import org.gachon.checkmate.domain.member.dto.response.EmailResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignInResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignUpResponseDto;
import org.gachon.checkmate.domain.member.dto.request.*;
import org.gachon.checkmate.domain.member.dto.response.*;
import org.gachon.checkmate.domain.member.service.MemberService;
import org.gachon.checkmate.global.common.SuccessResponse;
import org.gachon.checkmate.global.config.auth.UserId;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -43,4 +40,29 @@ public ResponseEntity<SuccessResponse<?>> setPassword(@RequestBody final Passwor
memberService.setPassword(passwordResetRequestDto);
return SuccessResponse.ok(null);
}

@GetMapping("/mypage")
public ResponseEntity<SuccessResponse<?>> getMypage(@UserId final Long userId){
final MypageResponseDto mypageResponseDto = memberService.getMypage(userId);
return SuccessResponse.ok(mypageResponseDto);
}

@PostMapping("/reissue")
public ResponseEntity<SuccessResponse<?>> reissue(@RequestBody final MemberReissueTokenRequestDto memberReissueTokenRequestDto) throws JsonProcessingException {
final MemberReissueTokenResponseDto memberReissueTokenResponseDto = memberService.reissue(memberReissueTokenRequestDto);
return SuccessResponse.ok(memberReissueTokenResponseDto);
}

@PostMapping("/signout")
public ResponseEntity<SuccessResponse<?>> signOut(@UserId final Long userId) {
memberService.signOut(userId);
return SuccessResponse.ok(null);
}

@PatchMapping("/profile")
public ResponseEntity<SuccessResponse<?>> changeProfileImg(@UserId final Long userId,
@RequestBody ProfileImgRequestDto profileImgRequestDto){
memberService.changeProfileImg(userId, profileImgRequestDto);
return SuccessResponse.ok(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.gachon.checkmate.domain.member.dto.request;

public record MemberReissueTokenRequestDto(
String accessToken,
String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.gachon.checkmate.domain.member.dto.request;

import org.gachon.checkmate.domain.member.entity.ProfileImageType;

public record ProfileImgRequestDto(
ProfileImageType profileImageType
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.gachon.checkmate.domain.member.dto.response;

import lombok.Builder;

@Builder
public record MemberReissueTokenResponseDto(
String accessToken,
String refreshToken
) {
public static MemberReissueTokenResponseDto of(String accessToken, String refreshToken){
return MemberReissueTokenResponseDto.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.gachon.checkmate.domain.member.dto.response;

import lombok.Builder;

@Builder
public record MypageResponseDto(
String profileImg,
String name,
String major,
String gender,
String mbti
) {
public static MypageResponseDto of(String profileImg,
String name,
String major,
String gender,
String mbti){
return MypageResponseDto.builder()
.profileImg(profileImg)
.name(name)
.major(major)
.gender(gender)
.mbti(mbti)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
public enum ProfileImageType {

PROFILE_1("https://checkmate-dormitory-bucket.s3.ap-northeast-2.amazonaws.com/checkmate-profile1.png"),
PROFILE_2("https://example.com/profile2.jpg"),
PROFILE_3("https://example.com/profile3.jpg");
PROFILE_2("https://checkmate-dormitory-bucket.s3.ap-northeast-2.amazonaws.com/checkmate-profile2.png"),
PROFILE_3("https://checkmate-dormitory-bucket.s3.ap-northeast-2.amazonaws.com/checkmate-profile3.png");

private final String imageUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.gachon.checkmate.domain.member.entity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(access = AccessLevel.PRIVATE)
@Getter
@RedisHash(value = "refreshToken", timeToLive = 60*60*24*7)
public class RefreshToken {
@Id
private Long id;
private String refreshToken;

public static RefreshToken createRefreshToken(Long userId, String refreshToken) {
return RefreshToken.builder()
.id(userId)
.refreshToken(refreshToken)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public class User extends BaseTimeEntity {
@Builder.Default
private List<Scrap> scrapList = new ArrayList<>();

public static User createUser(String email, String storedPassword, String name, String school, String major, MbtiType mbti, GenderType gender){
public static User createUser(String email, String storedPassword, String name, String school, String major, MbtiType mbti, GenderType gender, String profile){
return User.builder()
.email(email)
.password(storedPassword)
.name(name)
.profile(ProfileImageType.PROFILE_1.getImageUrl())
.profile(profile)
.school(school)
.major(major)
.mbtiType(mbti)
Expand All @@ -63,4 +63,8 @@ public void setPassword(String newPassword) {
public void setCheckList(CheckList checkList) {
this.checkList = checkList;
}

public void setProfile(String profile){
this.profile = profile;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.gachon.checkmate.domain.member.repository;

import org.gachon.checkmate.domain.member.entity.RefreshToken;
import org.springframework.data.repository.CrudRepository;

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package org.gachon.checkmate.domain.member.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.gachon.checkmate.domain.member.dto.request.EmailPostRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignInRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignUpRequestDto;
import org.gachon.checkmate.domain.member.dto.request.PasswordResetRequestDto;
import org.gachon.checkmate.domain.member.dto.response.EmailResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignInResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignUpResponseDto;
import org.gachon.checkmate.domain.member.dto.request.*;
import org.gachon.checkmate.domain.member.dto.response.*;
import org.gachon.checkmate.domain.member.entity.ProfileImageType;
import org.gachon.checkmate.domain.member.entity.RefreshToken;
import org.gachon.checkmate.domain.member.entity.User;
import org.gachon.checkmate.domain.member.repository.RefreshTokenRepository;
import org.gachon.checkmate.domain.member.repository.UserRepository;
import org.gachon.checkmate.global.config.auth.jwt.JwtProvider;
import org.gachon.checkmate.global.config.mail.MailProvider;
import org.gachon.checkmate.global.error.exception.ConflictException;
import org.gachon.checkmate.global.error.exception.EntityNotFoundException;
import org.gachon.checkmate.global.error.exception.InvalidValueException;
import org.gachon.checkmate.global.error.exception.UnauthorizedException;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.regex.Pattern;
import java.util.Random;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static org.gachon.checkmate.domain.member.entity.RefreshToken.createRefreshToken;
import static org.gachon.checkmate.global.error.ErrorCode.*;

@Slf4j
Expand All @@ -32,6 +35,9 @@ public class MemberService {
private final MailProvider mailProvider;
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
private static final String PASSWORD_REGEX = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,20}$";
private static final Random random = new Random();

public EmailResponseDto sendMail(EmailPostRequestDto emailPostRequestDto) {
checkDuplicateEmail(emailPostRequestDto.email());
Expand All @@ -40,9 +46,11 @@ public EmailResponseDto sendMail(EmailPostRequestDto emailPostRequestDto) {
}

public MemberSignUpResponseDto signUp(MemberSignUpRequestDto memberSignUpRequestDto) {
validatePassword(memberSignUpRequestDto.password());
Long newMemberId = createMember(memberSignUpRequestDto);
String accessToken = issueNewAccessToken(newMemberId);
String refreshToken = issueNewRefreshToken(newMemberId);
saveTokenInfo(newMemberId, refreshToken);
return MemberSignUpResponseDto.of(newMemberId, memberSignUpRequestDto.name(), accessToken, refreshToken);
}

Expand All @@ -51,17 +59,39 @@ public MemberSignInResponseDto signIn(MemberSignInRequestDto memberSignInRequest
validatePassword(memberSignInRequestDto.password(), user.getPassword());
String accessToken = issueNewAccessToken(user.getId());
String refreshToken = issueNewRefreshToken(user.getId());
saveTokenInfo(user.getId(), refreshToken);
return MemberSignInResponseDto.of(user.getId(), accessToken, refreshToken);
}

public void setPassword(PasswordResetRequestDto passwordResetRequestDto){
public void setPassword(PasswordResetRequestDto passwordResetRequestDto) {
User user = getUserFromEmail(passwordResetRequestDto.email());
user.setPassword(encodedPassword(passwordResetRequestDto.newPassword()));
}

public MypageResponseDto getMypage(Long userId) {
User user = findByIdOrThrow(userId);
return MypageResponseDto.of(user.getProfile(),
user.getName(),
user.getMajor(),
user.getGender().getDesc(),
user.getMbtiType().getDesc()
);
}

private void validatePassword(String password) {
if (!Pattern.matches(PASSWORD_REGEX, password)) {
throw new InvalidValueException(INVALID_PASSWORD);
}
}

private User findByIdOrThrow(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND));
}

private void validatePassword(String enteredPassword, String storedPassword) {
if (!authenticatePassword(enteredPassword, storedPassword)) {
throw new UnauthorizedException(INVALID_PASSWORD);
throw new UnauthorizedException(NOT_MATCH_PASSWORD);
}
}

Expand All @@ -80,18 +110,31 @@ private String issueNewRefreshToken(Long memberId) {
}

private Long createMember(MemberSignUpRequestDto memberSignUpRequestDto) {
ProfileImageType randomProfile = getRandomProfileImage();
User newUser = User.createUser(
memberSignUpRequestDto.email(),
encodedPassword(memberSignUpRequestDto.password()),
memberSignUpRequestDto.name(),
memberSignUpRequestDto.school(),
memberSignUpRequestDto.major(),
memberSignUpRequestDto.mbtiType(),
memberSignUpRequestDto.genderType()
memberSignUpRequestDto.genderType(),
randomProfile.getImageUrl()
);
return userRepository.save(newUser).getId();
}

public void changeProfileImg(Long userId, ProfileImgRequestDto profileImgRequestDto){
User user = findByIdOrThrow(userId);
String imageUrl = switch (profileImgRequestDto.profileImageType()) {
case PROFILE_1 -> ProfileImageType.PROFILE_1.getImageUrl();
case PROFILE_2 -> ProfileImageType.PROFILE_2.getImageUrl();
case PROFILE_3 -> ProfileImageType.PROFILE_3.getImageUrl();
};
user.setProfile(imageUrl);
userRepository.save(user);
}

private String encodedPassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}
Expand All @@ -105,5 +148,49 @@ private User getUserFromEmail(String email) {
.orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND));
}

public void saveTokenInfo(Long userId, String refreshToken) {
refreshTokenRepository.save(createRefreshToken(userId, refreshToken));
}

public MemberReissueTokenResponseDto reissue(MemberReissueTokenRequestDto memberReissueTokenRequestDto) throws JsonProcessingException {
Long userId = Long.valueOf(jwtProvider.decodeJwtPayloadSubject(memberReissueTokenRequestDto.accessToken()));
validateRefreshToken(memberReissueTokenRequestDto.refreshToken(), userId);
String accessToken = issueNewAccessToken(userId);
String refreshToken = issueNewRefreshToken(userId);
saveTokenInfo(userId, refreshToken);
return MemberReissueTokenResponseDto.of(accessToken, refreshToken);
}

private void validateRefreshToken(String refreshToken, Long userId) {
try {
jwtProvider.validateRefreshToken(refreshToken);
String storedRefreshToken = getRefreshTokenFromRedis(userId);
jwtProvider.equalsRefreshToken(refreshToken, storedRefreshToken);
} catch (UnauthorizedException e) {
signOut(userId);
throw e;
}
}

private String getRefreshTokenFromRedis(Long userId) {
RefreshToken storedRefreshToken = refreshTokenRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException(REFRESH_TOKEN_NOT_FOUND));
return storedRefreshToken.getRefreshToken();
}

private void deleteRefreshToken(User user) {
refreshTokenRepository.deleteById(user.getId());
}

public void signOut(Long userId) {
User user = findByIdOrThrow(userId);
deleteRefreshToken(user);
}

private static ProfileImageType getRandomProfileImage() {
ProfileImageType[] values = ProfileImageType.values();
int randomIndex = random.nextInt(values.length);
return values[randomIndex];
}

}
Loading