Skip to content

Commit

Permalink
MATE-86 : [FEAT] 토큰 발급을 위한 로그인 기능 추가 및 회원 URI 수정 (#78)
Browse files Browse the repository at this point in the history
* MATE-86 : [CHORE] application-common.yml 네이버 키 수정

* MATE-86 : [REFACTOR] 네이버 로그인 관련 URI 수정

* MATE-86 : [FEAT] 회원 권한 에러 처리 추가

* MATE-86 : [FEAT] 네이버 state 설정 추가

* MATE-86 : [FEAT] 테스트에서 회원 인증 AuthMember 설정

* MATE-86 : [FEAT] 프로필 컨트롤러 URI 변경

* MATE-86 : [REFACTOR] 회원 컨트롤러 URI 수정 및 로그인 후 토큰 발급 기능 추가

* MATE-86 : [REFACTOR] 팔로우 컨트롤러 URI 변경

* MATE-86 : [CHORE] 크롤링 매 분 실행 자동화 테스트 주석 해제
  • Loading branch information
jooinjoo authored Dec 4, 2024
1 parent ea4359f commit 9b16776
Show file tree
Hide file tree
Showing 22 changed files with 196 additions and 230 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/example/mate/common/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum ErrorCode {
UNSUPPORTED_RESPONSE_TYPE(HttpStatus.BAD_REQUEST, "M002", "회원 프로필 조회에서 지원하지 않는 응답 타입입니다."),
ALREADY_USED_NICKNAME(HttpStatus.BAD_REQUEST, "M003", "이미 사용 중인 닉네임입니다."),
MEMBER_NOT_FOUND_BY_EMAIL(HttpStatus.NOT_FOUND, "M004", "해당 이메일의 회원 정보를 찾을 수 없습니다."),
MEMBER_UNAUTHORIZED_ACCESS(HttpStatus.FORBIDDEN, "M005", "해당 회원의 접근 권한이 없습니다."),

// Follow
ALREADY_FOLLOWED_MEMBER(HttpStatus.BAD_REQUEST, "F001", "이미 팔로우한 회원입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.mate.domain.auth.dto.response.LoginResponse;
import com.example.mate.domain.auth.service.NaverAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,8 +31,9 @@ public class AuthController {
*/
@Operation(summary = "네이버 소셜 인증 페이지 리다이렉트")
@GetMapping("/connect/naver")
public RedirectView connectNaver() {
return new RedirectView(naverAuthService.getAuthUrl());
public RedirectView connectNaver(
@Parameter(description = "네이버 로그인 요청 state") @RequestParam String state) {
return new RedirectView(naverAuthService.getAuthUrl(state));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public class NaverAuthService {
*
* @return 네이버 로그인 URL
*/
public String getAuthUrl() {
public String getAuthUrl(String state) {
return "https://nid.naver.com/oauth2.0/authorize"
+ "?client_id=" + oAuthConfig.getNaverClientId()
+ "&redirect_uri=" + oAuthConfig.getNaverRedirectUri()
+ "&response_type=code"
+ "&state=STATE_STRING";
+ "&state=" + state;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ public void eveningUpdate() {
}
}

// // 매 분 실행 자동화 테스트 (현재 off-season )
// @Scheduled(cron = "0 * * * * *", zone = "Asia/Seoul")
// public void testScheduler() {
// log.info("Test Scheduler is running!");
// try {
// // 현재 경기 크롤링
// crawlingService.crawlAllCurrentMatches();
// // 팀 순위 크롤링
// crawlingService.crawlTeamRankings();
// } catch (CustomException e) {
// log.error("Evening update failed: {}", e.getErrorCode().getMessage(), e);
// } catch (Exception e) {
// log.error("Evening update failed with unexpected error", e);
// }
// }
// 매 분 실행 자동화 테스트 (현재 off-season )
@Scheduled(cron = "0 * * * * *", zone = "Asia/Seoul")
public void testScheduler() {
log.info("Test Scheduler is running!");
try {
// 현재 경기 크롤링
crawlingService.crawlAllCurrentMatches();
// 팀 순위 크롤링
crawlingService.crawlTeamRankings();
} catch (CustomException e) {
log.error("Evening update failed: {}", e.getErrorCode().getMessage(), e);
} catch (Exception e) {
log.error("Evening update failed with unexpected error", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.example.mate.common.response.ApiResponse;
import com.example.mate.common.response.PageResponse;
import com.example.mate.common.security.auth.AuthMember;
import com.example.mate.domain.member.dto.response.MemberSummaryResponse;
import com.example.mate.domain.member.service.FollowService;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -13,12 +14,12 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -29,37 +30,29 @@ public class FollowController {

private final FollowService followService;

/*
TODO : 2024/11/29 - 회원 팔로우 기능
1. JwtToken 을 통해 사용자 정보 조회 - 현재는 임시로 @RequestParam 사용
*/
@Operation(summary = "회원 팔로우 기능")
@PostMapping("/follow/{memberId}")
public ResponseEntity<ApiResponse<Void>> followMember(
@Parameter(description = "팔로우할 회원 ID") @PathVariable Long memberId,
@Parameter(description = "팔로우하는 회원 ID") @RequestParam Long followerId) {
followService.follow(followerId, memberId);
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember) {
followService.follow(authMember.getMemberId(), memberId);
return ResponseEntity.ok(ApiResponse.success(null));
}

/*
TODO : 2024/11/29 - 회원 언팔로우 기능
1. JwtToken 을 통해 사용자 정보 조회 - 현재는 임시로 @RequestParam 사용
*/
@Operation(summary = "회원 언팔로우 기능")
@DeleteMapping("/follow/{memberId}")
public ResponseEntity<Void> unfollowMember(
@Parameter(description = "언팔로우할 회원 ID") @PathVariable Long memberId,
@Parameter(description = "언팔로우하는 회원 ID") @RequestParam Long unfollowerId) {
followService.unfollow(unfollowerId, memberId);
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember) {
followService.unfollow(authMember.getMemberId(), memberId);
return ResponseEntity.noContent().build();
}

@Operation(summary = "특정 회원의 팔로우 회원 리스트 페이징 조회")
@GetMapping("{memberId}/followings")
public ResponseEntity<ApiResponse<PageResponse<MemberSummaryResponse>>> getFollowings(
@Parameter(description = "특정 회원 ID") @PathVariable Long memberId,
@PageableDefault Pageable pageable
@Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable
) {
pageable = validatePageable(pageable);
PageResponse<MemberSummaryResponse> response = followService.getFollowingsPage(memberId, pageable);
Expand All @@ -70,7 +63,7 @@ public ResponseEntity<ApiResponse<PageResponse<MemberSummaryResponse>>> getFollo
@GetMapping("{memberId}/followers")
public ResponseEntity<ApiResponse<PageResponse<MemberSummaryResponse>>> getFollowers(
@Parameter(description = "특정 회원 ID") @PathVariable Long memberId,
@PageableDefault Pageable pageable
@Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable
) {
pageable = validatePageable(pageable);
PageResponse<MemberSummaryResponse> response = followService.getFollowersPage(memberId, pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -37,11 +36,6 @@ public class MemberController {

private final MemberService memberService;

/*
TODO : 2024/11/29 - 소셜 회원가입 후, 자체 회원가입 기능
1. 소셜 로그인 후 사용자 정보가 바로 넘어오도록 처리
2. nickname, myTeam 정보 저장
*/
@Operation(summary = "자체 회원가입 기능")
@PostMapping("/join")
public ResponseEntity<ApiResponse<JoinResponse>> join(
Expand All @@ -50,10 +44,6 @@ public ResponseEntity<ApiResponse<JoinResponse>> join(
return ResponseEntity.ok(ApiResponse.success(memberService.join(joinRequest)));
}

/*
CATCH Mi 서비스 로그인
소셜 로그인 후, 받아온 이메일을 통해 로그인 처리
*/
@Operation(summary = "CATCH Mi 서비스 로그인", description = "캐치미 서비스에 로그인합니다.")
@PostMapping("/login")
public ResponseEntity<ApiResponse<MemberLoginResponse>> catchMiLogin(
Expand All @@ -63,11 +53,11 @@ public ResponseEntity<ApiResponse<MemberLoginResponse>> catchMiLogin(
return ResponseEntity.ok(ApiResponse.success(response));
}

// TODO : 2024/11/29 - 내 프로필 조회 : 추후 @AuthenticationPrincipal Long memberId 받음
@Operation(summary = "내 프로필 조회")
@GetMapping("/me")
public ResponseEntity<ApiResponse<MyProfileResponse>> findMyInfo(@RequestParam Long memberId) {
return ResponseEntity.ok(ApiResponse.success(memberService.getMyProfile(memberId)));
public ResponseEntity<ApiResponse<MyProfileResponse>> findMyInfo(
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember) {
return ResponseEntity.ok(ApiResponse.success(memberService.getMyProfile(authMember.getMemberId())));
}

@Operation(summary = "다른 회원 프로필 조회")
Expand All @@ -77,32 +67,21 @@ public ResponseEntity<ApiResponse<MemberProfileResponse>> findMemberInfo(
return ResponseEntity.ok(ApiResponse.success(memberService.getMemberProfile(memberId)));
}

/*
TODO : 회원 정보 수정 :
1. JwtToken 을 통해 사용자 정보 조회 -> 본인만 수정 가능하도록
*/
@Operation(summary = "회원 내 정보 수정")
@PutMapping(value = "/me")
public ResponseEntity<ApiResponse<MyProfileResponse>> updateMemberInfo(
@Parameter(description = "프로필 사진") @RequestPart(value = "image", required = false) MultipartFile image,
@Parameter(description = "수정할 회원 정보") @Valid @RequestPart(value = "data") MemberInfoUpdateRequest updateRequest) {
@Parameter(description = "수정할 회원 정보") @Valid @RequestPart(value = "data") MemberInfoUpdateRequest updateRequest,
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember) {
updateRequest.setMemberId(authMember.getMemberId());
return ResponseEntity.ok(ApiResponse.success(memberService.updateMyProfile(image, updateRequest)));
}

/*
TODO : 회원 삭제 : 임시로 @RequestParam Long memberId
1. JwtToken 을 통해 사용자 정보 조회 -> 본인만 수정 가능하도록
*/
@Operation(summary = "회원 탈퇴")
@DeleteMapping("/me")
public ResponseEntity<Void> deleteMember(@RequestParam Long memberId) {
memberService.deleteMember(memberId);
public ResponseEntity<Void> deleteMember(
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember) {
memberService.deleteMember(authMember.getMemberId());
return ResponseEntity.noContent().build();
}

@GetMapping("/test")
public String test(@AuthenticationPrincipal AuthMember authMember) {
return "principal getName == " + authMember.getName() + " || " + "principal getMemberId == "
+ authMember.getMemberId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import static com.example.mate.common.response.PageResponse.validatePageable;

import com.example.mate.common.error.CustomException;
import com.example.mate.common.error.ErrorCode;
import com.example.mate.common.response.ApiResponse;
import com.example.mate.common.response.PageResponse;
import com.example.mate.common.security.auth.AuthMember;
import com.example.mate.domain.member.dto.response.MyGoodsRecordResponse;
import com.example.mate.domain.member.dto.response.MyReviewResponse;
import com.example.mate.domain.member.dto.response.MyVisitResponse;
Expand All @@ -15,6 +18,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -50,15 +54,14 @@ public ResponseEntity<ApiResponse<PageResponse<MyReviewResponse>>> getMateReview
return ResponseEntity.ok(ApiResponse.success(response));
}

// TODO : 본인만 접근할 수 있도록 @AuthenticationPrincipal Long memberId
@Operation(summary = "직관 타임라인 페이징 조회")
@GetMapping("/timeline/{memberId}")
@GetMapping("/timeline")
public ResponseEntity<ApiResponse<PageResponse<MyVisitResponse>>> getMyVisits(
@Parameter(description = "회원 ID") @PathVariable Long memberId,
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember,
@Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable
) {
validatePageable(pageable);
PageResponse<MyVisitResponse> response = profileService.getMyVisitPage(memberId, pageable);
PageResponse<MyVisitResponse> response = profileService.getMyVisitPage(authMember.getMemberId(), pageable);
return ResponseEntity.ok(ApiResponse.success(response));
}

Expand All @@ -73,13 +76,16 @@ public ResponseEntity<ApiResponse<PageResponse<MyGoodsRecordResponse>>> getSoldG
return ResponseEntity.ok(ApiResponse.success(response));
}

// TODO : 본인만 접근할 수 있도록 @AuthenticationPrincipal Long memberId
@Operation(summary = "굿즈 구매기록 페이징 조회")
@GetMapping("/{memberId}/goods/bought")
public ResponseEntity<ApiResponse<PageResponse<MyGoodsRecordResponse>>> getBoughtGoods(
@Parameter(description = "회원 ID") @PathVariable Long memberId,
@Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable
@Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable,
@Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember
) {
if (!authMember.getMemberId().equals(memberId)) {
throw new CustomException(ErrorCode.MEMBER_UNAUTHORIZED_ACCESS);
}
pageable = validatePageable(pageable);
PageResponse<MyGoodsRecordResponse> response = profileService.getBoughtGoodsPage(memberId, pageable);
return ResponseEntity.ok(ApiResponse.success(response));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.Data;

@Getter
@Data
@Builder
public class MemberInfoUpdateRequest {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.mate.domain.member.dto.response;

import com.example.mate.common.jwt.JwtToken;
import com.example.mate.domain.member.entity.Member;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand All @@ -16,13 +17,12 @@ public class MemberLoginResponse {
private final String accessToken;
private final String refreshToken;

// TODO : 파라미터로 JwtToken 추가 및 토큰 매핑
public static MemberLoginResponse from(Member member) {
public static MemberLoginResponse from(Member member, JwtToken jwtToken) {
return MemberLoginResponse.builder()
.memberId(member.getId())
.grantType("Bearer")
.accessToken("accessToken")
.refreshToken("refreshToken")
.grantType(jwtToken.getGrantType())
.accessToken(jwtToken.getAccessToken())
.refreshToken(jwtToken.getRefreshToken())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,10 @@ public JoinResponse join(JoinRequest request) {
return JoinResponse.from(savedMember);
}

// TODO : JWT 토큰 발급
// 자체 로그인 기능
public MemberLoginResponse loginByEmail(MemberLoginRequest request) {
Member member = findByEmail(request.getEmail());
// 토큰 발급한 뒤 member와 함께 넘기기
JwtToken jwtToken = makeToken(member);
return MemberLoginResponse.from(member);
return MemberLoginResponse.from(member, makeToken(member));
}

// JWT 토큰 생성
Expand All @@ -75,7 +72,6 @@ private Member findByEmail(String email) {
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_EMAIL));
}

// TODO : JWT 도입 이후 본인만 접근할 수 있도록 수정
// 내 프로필 조회
public MyProfileResponse getMyProfile(Long memberId) {
return getProfile(memberId, MyProfileResponse.class);
Expand All @@ -86,7 +82,6 @@ public MemberProfileResponse getMemberProfile(Long memberId) {
return getProfile(memberId, MemberProfileResponse.class);
}

// TODO : JWT 도입 이후 본인만 접근할 수 있도록 수정
// 회원 정보 수정
public MyProfileResponse updateMyProfile(MultipartFile image, MemberInfoUpdateRequest request) {
Member member = findByMemberId(request.getMemberId());
Expand All @@ -104,7 +99,6 @@ public MyProfileResponse updateMyProfile(MultipartFile image, MemberInfoUpdateRe
return MyProfileResponse.from(memberRepository.save(member));
}

// TODO : JWT 도입 이후 본인만 접근할 수 있도록 수정
// 회원 탈퇴
public void deleteMember(Long memberId) {
findByMemberId(memberId);
Expand Down
7 changes: 3 additions & 4 deletions src/main/resources/application-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ springdoc:


oauth:
naver:
client-id: ${NAVER_CLIENT_ID}
redirect-uri: ${NAVER_REDIRECT_URI}
client-secret: ${NAVER_CLIENT_SECRET}
naver_client_id: ${NAVER_CLIENT_ID}
naver_redirect_uri: ${NAVER_REDIRECT_URI}
naver_client_secret: ${NAVER_CLIENT_SECRET}

jwt:
secret_key: ${JWT_SECRET_KEY}
Expand Down
Loading

0 comments on commit 9b16776

Please sign in to comment.