Skip to content

Commit

Permalink
5차 코드 리뷰 요청 드립니다. (#136)
Browse files Browse the repository at this point in the history
* Deploy: PR 생성시 테스트코드 수행 로직 구현 (#94)

* deploy: PR 생성시 테스트코드 수행 로직 추가

* feat: jwt.secret키를 깃허브 시크릿에 저장 및 환경변수 설정

* feat: 환경변수 읽어오도록 수정

* feat: 도커를 사용하여 레디스 환경 구축

* Deploy: 깃허브 액션 최신버전으로 업그레이드 (#96)

* deploy: deploy.yml 버전 업그레이드

* 깃허브 testcode.yml 버전 업그레이드

* deploy: 가장 최신 버전으로 업그레이드

* deploy: 가장 최신 버전으로 업그레이드

* Fix: 안부전화 Cascade 순환참조 문제 해결 및 관계성 오류 해결 구현 (#100)

fix: 안부전화 Cascade 순환참조 문제 해결 및 관계성 오류 해결

* Refactor: 가이드라인 관련 로직 수정 {프론트 요청) (#103)

* deploy: 가이드라인 더미데이터 추가

* feat: 콜백과 가이드라인의 시니어가 같지 않을 때 발생하는 예외 구현

* refactor: 타입별 전체 가이드라인 및 특정 가이드라인 조회 시 검증 로직 추가

* refactor: 가이드라인 조회 api 매개변수 수정

* refactor: Reformat code

* refactor: 가이드라인 response dto에 id 추가 및 서비스단 수정

* refactor: 중복 주석 삭제

* refactor: 서비스단에서 사용하지 않는 레퍼지토리 선언 삭제

* fix: url 중복 오류해결 (#106)

* Refactor: 예외 처리 관련 리팩토링 (#92)

* refactor: 콜백 도메인 예외 처리 리팩토링

* refactor: Auth 도메인 예외 처리 리팩토링

* refactor: Guard 도메인 예외 처리 리팩토링

* refactor: GuardGuideline 도메인 예외 처리 리팩토링

* refactor: HelloCall 도메인 예외 처리 리팩토링

* fix: 리팩토링후 콜백 Test 코드 수정안하여 생긴 테스트 실패 수정

* refactor: Member 도메인 예외 처리 리팩토링

* refactor: Point 도메인 예외 처리 리팩토링

* refactor: Review 도메인 예외 처리 리팩토링

* refactor: Sinitto 도메인 예외 처리 리팩토링

* refactor: 사용안되는 exception class, advice class 제거,

* refactor: HelloCallPriceService 에서 IllegalArgumentException -> BadRequestException

* refactor: 포인트로그 Content 멘트 수정

* refactor: 포인트로그 Content 멘트 최종 수정

* refactor: MultiStatusException->ConflictException

* refactor: TokenService의 응답코드 다양화

* refactor: 예외 클래스 이름 더 명확하게 수정

* Feat: 가이드라인 삭제 기능 추가 (#108)

* refactor: id 속성 이름 Id로 수정

* feat: 서비스 레이어 가이드라인 삭제 메서드 추가

* feat: 컨트롤러 가이드라인 삭제 api 추가

* refactor: 사용하지 않는 가이드라인 예외클래스 삭제

* refactor: id 속성 코드컨벤션에 맞게 수정

* refactor: 삭제 api를 delete 요청으로 수정

* Feat: 콜백 단건조회 api 응답값에 boolean isAssignedToSelf, String seniorPhoneNumber 추가 (#104)

* feat: 콜백 단건 조회 응답 요소 추가

* refactor: 더 명확한 메서드 명으로 수정 getCallback->getCallbackForSinitto

* test: 시니또용 콜백 단건 조회 테스트 작성

* chore: 프론트 테스트를 더 명확히하기위해 콜백 더미 데이터에 할당된 시니어 추가

* refactor: 본인에게 할당된 콜백이 아닌것에대한 콜백 상세조회 할때는 시니어 번호 없도록 수정

* test: 테스트 통과하도록 수정

* chore: 더미데이터 개선

* Deploy: 더미데이터 실행 과정 중 initial()과 saveRefreshTokenToRedis() 메서드 실행을 별도의 트랜잭션으로 관리 (#113)

deploy: 더미데이터 실행 별도의 트랜잭션에서 수행

* Deploy: 할당되는 콜백 더미 데이터에 각각 `callbackRepository.save(callback{id});` 추가 (#117)

chore: 할당되는 콜백 더미 데이터에 각각 callbackRepository.save(callback?); 추가

* Feat: 시니또용 카테고리별 가이드라인 조회 api 추가 및 기존 api 삭제 (프론트 요청) (#114)

* refactor: 시니또용 카테고리별 가이드라인 조회 메서드 추가

* refactor: 시니또용 카테고리별 가이드라인 조회 api 추가

* refactor: Swagger @tag 수정

* refactor: 시니또용 가이드라인 조회에서 기존 state 검증로직에 회원이 배정된 시니또인지 검증하는 로직 추가

* refactor: 서비스 레이어 회원 검증 로직 추가를 위해서 memberId 파라미터 추가

* refactor: Reformat Code

* Deploy: 로컬과 개발 환경에 따라 카카오 리다이렉트 주소를 다르게 리턴하는 로직 구현 (#120)

* feat: 카카오 devRedirectUri 추출 로직 추가

* feat: httpServletRequest를 통해 Referer 또는 Origin을 확인하여 다른 프론트 주소 리다이렉트

* 카카오 로그인 요청 헤더 추출 로직에서 NULL 예외처리 구현 (#122)

fix: 카카오 로그인 요청 헤더 추출 로직에서 NULL 예외처리 추가

* Feat: 콜백 단건 조회 로직 개선 (#123)

feat: 콜백 단건 조회 로직 개선

- 상태에 따른 검증 로직 추가
- 예외 처리 추가
- 가독성 향상
- 테스트 꼼꼼히

* 더미데이터로 로그인 기능 SSR로 구현 (#128)

* feat: 저장 로직 초기세팅

* feat: DummyProperties 추가

* 비밀번호 및 오류 출력 로직 추가

* refactor: 코드 정렬

* feat: 이메일 목록에서 찾는 로직 추가, 에러 메시지 이후에도 목록 유지

* teat: findAllByEmailIn로직 테스트 코드 추가

* feat: css추가

* Feat: 시니또 회원 정보와 계좌 정보 조회 API 및 로직 분리 (#126)

* Feat: 서비스 레이어 시니또 계좌정보 조회 메서드 추가

* refactor: 사용하지 않는 api와 관련 로직 삭제

* refactor: 시니어 정보 조회 api 전달 데이터 변경과 로직 수정

* refactor: 계좌정보 삭제 api 삭제

* Feat: Swagger 멘트 수정

* Refactor: SinittoRequest email 속성 삭제 및 Member 엔터티 update 메서드 수정

* Refactor: 중복 url 수정

* Refactor: GuardRequest에서 email 삭제

* Refactor: 계좌 정보가 존재하지 않으면 예외처리 대신 null을 담은 response 반환하도록 수정

* Refactor: Reformat Code

* Feat: Member 정보 업데이트 테스트 추가

* Fix: 더미데이터 로그인 SSR input창 좌우여백 조정 (#130)

fix: css 수정

---------

Co-authored-by: JIHO LEE <[email protected]>
Co-authored-by: eunsoni <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent 25d26f4 commit e230e76
Show file tree
Hide file tree
Showing 98 changed files with 982 additions and 903 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up SSH
uses: webfactory/ssh-agent@v0.5.3
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/testcode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Run Tests on Pull Request

on:
pull_request:
branches:
- Weekly

jobs:
test:
runs-on: ubuntu-latest

services:
redis:
image: redis:alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Run Tests
env:
JWT_SECRET: ${{ secrets.JWT_SECRET }}
run: ./gradlew test
3 changes: 2 additions & 1 deletion src/main/java/com/example/sinitto/SinittoApplication.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.sinitto;

import com.example.sinitto.common.properties.DummyProperties;
import com.example.sinitto.common.properties.KakaoProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -9,7 +10,7 @@

@SpringBootApplication
@EnableJpaAuditing
@EnableConfigurationProperties(KakaoProperties.class)
@EnableConfigurationProperties({KakaoProperties.class, DummyProperties.class})
@EnableScheduling
public class SinittoApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -41,17 +42,17 @@ public ResponseEntity<TokenResponse> refreshToken(@RequestBody TokenRefreshReque

@Operation(summary = "Oauth 카카오 인증페이지 리다이렉트", description = "카카오 로그인 화면으로 이동한다.", security = @SecurityRequirement(name = "JWT제외"))
@GetMapping("/oauth/kakao")
public ResponseEntity<Void> redirectToKakaoAuth() {
String url = kakaoApiService.getAuthorizationUrl();
public ResponseEntity<Void> redirectToKakaoAuth(HttpServletRequest httpServletRequest) {
String url = kakaoApiService.getAuthorizationUrl(httpServletRequest);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(url));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}

@Operation(summary = "Oauth 카카오 로그인 콜백", description = "카카오 로그인 이후 발생하는 인가코드를 통해 AccessToken과 RefreshToken을 발급한다.", security = @SecurityRequirement(name = "JWT제외"))
@GetMapping("/oauth/kakao/callback")
public ResponseEntity<LoginResponse> kakaoCallback(@RequestParam("code") String code) {
LoginResponse loginResponse = memberService.kakaoLogin(code);
public ResponseEntity<LoginResponse> kakaoCallback(@RequestParam("code") String code, HttpServletRequest httpServletRequest) {
LoginResponse loginResponse = memberService.kakaoLogin(code, httpServletRequest);
return ResponseEntity.ok().body(loginResponse);
}

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.example.sinitto.auth.dto.KakaoTokenResponse;
import com.example.sinitto.auth.dto.KakaoUserResponse;
import com.example.sinitto.auth.exception.KakaoEmailNotFoundException;
import com.example.sinitto.common.exception.BadRequestException;
import com.example.sinitto.common.exception.NotFoundException;
import com.example.sinitto.common.properties.KakaoProperties;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
Expand All @@ -25,20 +27,48 @@ public KakaoApiService(RestTemplate restTemplate, KakaoProperties kakaoPropertie
this.kakaoProperties = kakaoProperties;
}

public String getAuthorizationUrl() {
public String getAuthorizationUrl(HttpServletRequest httpServletRequest) {
String requestUrl = httpServletRequest.getHeader("Referer");
if (requestUrl == null) {
throw new BadRequestException("해당 도메인에서는 카카오 로그인이 불가합니다.");
}
String redirectUri;

if (requestUrl.contains("localhost:5173")) {
redirectUri = kakaoProperties.devRedirectUri();
} else if (requestUrl.contains("sinitto.s3-website.ap-northeast-2.amazonaws.com")) {
redirectUri = kakaoProperties.redirectUri();
} else {
throw new BadRequestException("해당 도메인에서는 카카오 로그인이 불가합니다. requestUrl : " + requestUrl);
}

return KAKAO_AUTH_BASE_URL + "/authorize?response_type=code&client_id="
+ kakaoProperties.clientId() + "&redirect_uri=" + kakaoProperties.redirectUri();
+ kakaoProperties.clientId() + "&redirect_uri=" + redirectUri;
}

public KakaoTokenResponse getAccessToken(String authorizationCode) {
public KakaoTokenResponse getAccessToken(String authorizationCode, HttpServletRequest httpServletRequest) {
String url = KAKAO_AUTH_BASE_URL + "/token";
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);

String requestUrl = httpServletRequest.getHeader("Origin");
if (requestUrl == null) {
throw new BadRequestException("해당 도메인에서는 카카오 로그인이 불가합니다.");
}
String redirectUri;

if (requestUrl.contains("localhost:5173")) {
redirectUri = kakaoProperties.devRedirectUri();
} else if (requestUrl.contains("sinitto.s3-website.ap-northeast-2.amazonaws.com")) {
redirectUri = kakaoProperties.redirectUri();
} else {
throw new BadRequestException("해당 도메인에서는 카카오 로그인이 불가합니다. requestUrl : " + requestUrl);
}

LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", kakaoProperties.clientId());
body.add("redirect_uri", kakaoProperties.redirectUri());
body.add("redirect_uri", redirectUri);
body.add("code", authorizationCode);

RequestEntity<LinkedMultiValueMap<String, String>> request = new RequestEntity<>(body,
Expand Down Expand Up @@ -81,7 +111,7 @@ public KakaoUserResponse getUserInfo(String accessToken) {
url, HttpMethod.POST, request, KakaoUserResponse.class);

if (response.getBody().kakaoAccount().email() == null) {
throw new KakaoEmailNotFoundException("카카오 계정으로부터 전달받은 이메일이 없습니다.");
throw new NotFoundException("카카오 계정으로부터 전달받은 이메일이 없습니다.");
}

return response.getBody();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import com.example.sinitto.auth.dto.KakaoTokenResponse;
import com.example.sinitto.auth.entity.KakaoToken;
import com.example.sinitto.auth.exception.KakaoRefreshTokenExpirationException;
import com.example.sinitto.auth.exception.TokenNotFoundException;
import com.example.sinitto.auth.repository.KakaoTokenRepository;
import com.example.sinitto.common.exception.NotFoundException;
import com.example.sinitto.common.exception.UnauthorizedException;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -36,11 +36,11 @@ public void saveKakaoToken(String email, KakaoTokenResponse kakaoTokenResponse)
@Transactional
public String getValidAccessTokenInServer(String email) {
KakaoToken kakaoToken = kakaoTokenRepository.findByMemberEmail(email)
.orElseThrow(() -> new TokenNotFoundException("email에 해당하는 카카오 토큰이 없습니다."));
.orElseThrow(() -> new NotFoundException("email에 해당하는 카카오 토큰이 없습니다."));

if (kakaoToken.isAccessTokenExpired()) {
if (kakaoToken.isRefreshTokenExpired()) {
throw new KakaoRefreshTokenExpirationException("카카오 리프레쉬 토큰이 만료되었습니다. 카카오 재 로그인 필요");
throw new UnauthorizedException("카카오 리프레쉬 토큰이 만료되었습니다. 카카오 재 로그인 필요");
}
KakaoTokenResponse kakaoTokenResponse = kakaoApiService.refreshAccessToken(kakaoToken.getRefreshToken());
kakaoToken.updateKakaoToken(kakaoTokenResponse.accessToken(), kakaoTokenResponse.refreshToken(), kakaoTokenResponse.expiresIn(), kakaoTokenResponse.refreshTokenExpiresIn());
Expand Down
32 changes: 22 additions & 10 deletions src/main/java/com/example/sinitto/auth/service/TokenService.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.example.sinitto.auth.service;

import com.example.sinitto.auth.dto.TokenResponse;
import com.example.sinitto.auth.exception.JWTExpirationException;
import com.example.sinitto.auth.exception.UnauthorizedException;
import com.example.sinitto.common.exception.AccessTokenExpiredException;
import com.example.sinitto.common.exception.InvalidJwtException;
import com.example.sinitto.common.exception.RefreshTokenStolenException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -53,14 +55,19 @@ public String generateRefreshToken(String email) {


public String extractEmail(String token) {
var claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
Claims claims;
try {
claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new InvalidJwtException(e.getMessage());
}

if (claims.getExpiration().before(new Date())) {
throw new JWTExpirationException("토큰이 만료되었습니다. 재로그인이 필요합니다.");
throw new AccessTokenExpiredException("액세스 토큰이 만료되었습니다. 리프레시 토큰으로 다시 액세스 토큰을 발급받으세요.");
}

return claims.getSubject();
Expand All @@ -70,8 +77,13 @@ public TokenResponse refreshAccessToken(String refreshToken) {
String email = extractEmail(refreshToken);

String storedRefreshToken = redisTemplate.opsForValue().get(email);
if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) {
throw new UnauthorizedException("만료되거나 이미 한번 사용된 리프레쉬 토큰입니다. 재로그인이 필요합니다.");

if (storedRefreshToken == null) {
throw new InvalidJwtException("토큰이 만료되었습니다. 재로그인이 필요합니다.");
}

if (!storedRefreshToken.equals(refreshToken)) {
throw new RefreshTokenStolenException("이미 한번 사용된 리프레시 토큰입니다. 리프레시 토큰이 탈취되었을 가능성이 있습니다.");
}

redisTemplate.delete(email);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.sinitto.callback.controller;

import com.example.sinitto.callback.dto.CallbackForSinittoResponse;
import com.example.sinitto.callback.dto.CallbackResponse;
import com.example.sinitto.callback.dto.CallbackUsageHistoryResponse;
import com.example.sinitto.callback.service.CallbackService;
Expand Down Expand Up @@ -89,11 +90,12 @@ public ResponseEntity<Page<CallbackUsageHistoryResponse>> getAcceptedCallback(@M
return ResponseEntity.ok(callbackService.getCallbackHistoryOfGuard(memberId, pageable));
}

@Operation(summary = "콜백 단건 조회", description = "콜백 id 로 콜백을 단건 조회합니다.")
@Operation(summary = "콜백 단건 조회 (시니또용)", description = "콜백 id 로 콜백을 단건 조회합니다.")
@GetMapping("/{callbackId}")
public ResponseEntity<CallbackResponse> getCallback(@PathVariable("callbackId") Long callbackId) {
public ResponseEntity<CallbackForSinittoResponse> getCallback(@MemberId Long memberId,
@PathVariable("callbackId") Long callbackId) {

return ResponseEntity.ok(callbackService.getCallback(callbackId));
return ResponseEntity.ok(callbackService.getCallbackForSinitto(memberId, callbackId));
}

}
Loading

0 comments on commit e230e76

Please sign in to comment.