Skip to content

Commit

Permalink
Merge pull request #23 from Domitory-CheckMate/feature/#22-member
Browse files Browse the repository at this point in the history
[feat] 마이페이지 조회 및 로그인 로직 추가
  • Loading branch information
ziiyouth authored Jan 5, 2024
2 parents 94b7195 + a460bf8 commit 448f767
Show file tree
Hide file tree
Showing 15 changed files with 283 additions and 23 deletions.
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

0 comments on commit 448f767

Please sign in to comment.