Skip to content

Commit aeca5c5

Browse files
authored
Merge pull request #86 from prgrms-web-devcourse-final-project/develop
chore[배포]: work
2 parents dbe6e1c + cd0ea8c commit aeca5c5

File tree

17 files changed

+293
-152
lines changed

17 files changed

+293
-152
lines changed

.github/workflows/CI-CD_Pipeline.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ jobs:
4444

4545
steps:
4646
- uses: actions/checkout@v4
47-
4847
- name: Set up JDK 21
4948
uses: actions/setup-java@v4
5049
with:

backend/.env.default

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ SPRING__SECURITY__OAUTH2__CLIENT__REGISTRATION__KAKAO__CLIENT_SECRET=NEED_TO_SET
77
SPRING__SECURITY__OAUTH2__CLIENT__REGISTRATION__NAVER__CLIENT_ID=NEED_TO_SET
88
SPRING__SECURITY__OAUTH2__CLIENT__REGISTRATION__NAVER__CLIENT_SECRET=NEED_TO_SET
99

10-
CUSTOM__JWT__SECRET_KEY=NEED_TO_SET
10+
CUSTOM_JWT_SECRET_KEY=NEED_TO_SET
1111
CUSTOM_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS=NEED_TO_SET
1212

1313
PROD_DATASOURCE_URL=NEED_TO_SET

backend/src/main/java/com/ai/lawyer/domain/law/repository/HangRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ai.lawyer.domain.law.repository;
22

33
import com.ai.lawyer.domain.law.entity.Hang;
4+
import com.ai.lawyer.domain.law.entity.Jo;
45
import org.springframework.data.jpa.repository.EntityGraph;
56
import org.springframework.data.jpa.repository.JpaRepository;
67
import org.springframework.stereotype.Repository;
@@ -13,4 +14,6 @@ public interface HangRepository extends JpaRepository<Hang, Long> {
1314
// Hang + Ho만 페치
1415
@EntityGraph(attributePaths = "hoList")
1516
List<Hang> findByJoId(Long joId);
17+
18+
List<Hang> findByJo(Jo jo);
1619
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.ai.lawyer.domain.law.repository;
22

3+
import com.ai.lawyer.domain.law.entity.Hang;
34
import com.ai.lawyer.domain.law.entity.Ho;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.stereotype.Repository;
67

8+
import java.util.List;
9+
710
@Repository
811
public interface HoRepository extends JpaRepository<Ho, Long> {
12+
List<Ho> findByHang(Hang hang);
913
}

backend/src/main/java/com/ai/lawyer/domain/law/repository/JangRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ai.lawyer.domain.law.repository;
22

33
import com.ai.lawyer.domain.law.entity.Jang;
4+
import com.ai.lawyer.domain.law.entity.Law;
45
import org.springframework.data.jpa.repository.EntityGraph;
56
import org.springframework.data.jpa.repository.JpaRepository;
67
import org.springframework.stereotype.Repository;
@@ -12,4 +13,6 @@ public interface JangRepository extends JpaRepository<Jang, Long> {
1213
// Jang + Jo만 페치
1314
@EntityGraph(attributePaths = "joList")
1415
List<Jang> findByLawId(Long lawId);
16+
17+
List<Jang> findByLaw(Law law);
1518
}

backend/src/main/java/com/ai/lawyer/domain/law/repository/JoRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ai.lawyer.domain.law.repository;
22

3+
import com.ai.lawyer.domain.law.entity.Jang;
34
import com.ai.lawyer.domain.law.entity.Jo;
45
import org.springframework.data.jpa.repository.EntityGraph;
56
import org.springframework.data.jpa.repository.JpaRepository;
@@ -13,4 +14,6 @@ public interface JoRepository extends JpaRepository<Jo, Long> {
1314
// Jo + Hang만 페치
1415
@EntityGraph(attributePaths = "hangList")
1516
List<Jo> findByJangId(Long jangId);
17+
18+
List<Jo> findByJang(Jang jang);
1619
}

backend/src/main/java/com/ai/lawyer/domain/member/controller/MemberController.java

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.ai.lawyer.domain.member.dto.MemberLoginRequest;
44
import com.ai.lawyer.domain.member.dto.MemberResponse;
55
import com.ai.lawyer.domain.member.dto.MemberSignupRequest;
6-
import com.ai.lawyer.domain.member.entity.Member;
76
import com.ai.lawyer.domain.member.service.MemberService;
87
import io.swagger.v3.oas.annotations.Operation;
98
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -37,14 +36,9 @@ public class MemberController {
3736
public ResponseEntity<MemberResponse> signup(@Valid @RequestBody MemberSignupRequest request) {
3837
log.info("회원가입 요청: email={}, name={}", request.getLoginId(), request.getName());
3938

40-
try {
41-
MemberResponse response = memberService.signup(request);
42-
log.info("회원가입 성공: memberId={}", response.getMemberId());
43-
return ResponseEntity.status(HttpStatus.CREATED).body(response);
44-
} catch (IllegalArgumentException e) {
45-
log.warn("회원가입 실패: {}", e.getMessage());
46-
return ResponseEntity.badRequest().build();
47-
}
39+
MemberResponse response = memberService.signup(request);
40+
log.info("회원가입 성공: memberId={}", response.getMemberId());
41+
return ResponseEntity.status(HttpStatus.CREATED).body(response);
4842
}
4943

5044
@PostMapping("/login")
@@ -57,14 +51,9 @@ public ResponseEntity<MemberResponse> login(@Valid @RequestBody MemberLoginReque
5751
HttpServletResponse response) {
5852
log.info("로그인 요청: email={}", request.getLoginId());
5953

60-
try {
61-
MemberResponse memberResponse = memberService.login(request, response);
62-
log.info("로그인 성공: memberId={}", memberResponse.getMemberId());
63-
return ResponseEntity.ok(memberResponse);
64-
} catch (IllegalArgumentException e) {
65-
log.warn("로그인 실패: {}", e.getMessage());
66-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
67-
}
54+
MemberResponse memberResponse = memberService.login(request, response);
55+
log.info("로그인 성공: memberId={}", memberResponse.getMemberId());
56+
return ResponseEntity.ok(memberResponse);
6857
}
6958

7059
@PostMapping("/logout")
@@ -78,9 +67,9 @@ public ResponseEntity<Void> logout(Authentication authentication, HttpServletRes
7867
if (authentication != null && authentication.getName() != null) {
7968
String loginId = authentication.getName();
8069
memberService.logout(loginId, response);
81-
log.info("로그아웃 완료: email={}", loginId);
70+
log.info("로그아웃 완료: memberId={}", loginId);
8271
} else {
83-
// 인증 정보가 없어도 쿠키는 클리어
72+
// 인증되지 않은 상태에서도 클라이언트 쿠키 클리어 처리
8473
memberService.logout("", response);
8574
log.info("인증 정보 없이 로그아웃 완료");
8675
}
@@ -98,22 +87,16 @@ public ResponseEntity<MemberResponse> refreshToken(HttpServletRequest request,
9887
HttpServletResponse response) {
9988
log.info("토큰 재발급 요청");
10089

101-
// 쿠키에서 리프레시 토큰 추출 (간단한 방법)
90+
// HTTP 쿠키에서 리프레시 토큰 추출
10291
String refreshToken = extractRefreshTokenFromCookies(request);
10392

10493
if (refreshToken == null) {
105-
log.warn("리프레시 토큰이 없음");
106-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
94+
throw new com.ai.lawyer.domain.member.exception.MemberAuthenticationException("리프레시 토큰이 없습니다.");
10795
}
10896

109-
try {
110-
MemberResponse memberResponse = memberService.refreshToken(refreshToken, response);
111-
log.info("토큰 재발급 성공: memberId={}", memberResponse.getMemberId());
112-
return ResponseEntity.ok(memberResponse);
113-
} catch (IllegalArgumentException e) {
114-
log.warn("토큰 재발급 실패: {}", e.getMessage());
115-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
116-
}
97+
MemberResponse memberResponse = memberService.refreshToken(refreshToken, response);
98+
log.info("토큰 재발급 성공: memberId={}", memberResponse.getMemberId());
99+
return ResponseEntity.ok(memberResponse);
117100
}
118101

119102
@DeleteMapping("/withdraw")
@@ -124,25 +107,18 @@ public ResponseEntity<MemberResponse> refreshToken(HttpServletRequest request,
124107
@ApiResponse(responseCode = "404", description = "존재하지 않는 회원")
125108
})
126109
public ResponseEntity<Void> withdraw(Authentication authentication, HttpServletResponse response) {
127-
if (authentication == null || authentication.getName() == null) {
128-
log.warn("인증되지 않은 회원탈퇴 요청");
129-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
110+
if (authentication == null || authentication.getPrincipal() == null) {
111+
throw new com.ai.lawyer.domain.member.exception.MemberAuthenticationException("인증이 필요합니다.");
130112
}
131113

132-
String loginId = authentication.getName();
133-
log.info("회원탈퇴 요청: email={}", loginId);
134-
135-
try {
136-
// loginId로 Member를 조회하여 실제 memberId 사용
137-
Member member = memberService.findByLoginId(loginId);
138-
memberService.withdraw(member.getMemberId());
139-
memberService.logout(loginId, response); // 탈퇴 후 로그아웃 처리
140-
log.info("회원탈퇴 성공: email={}, memberId={}", loginId, member.getMemberId());
141-
return ResponseEntity.ok().build();
142-
} catch (IllegalArgumentException e) {
143-
log.warn("회원탈퇴 실패: {}", e.getMessage());
144-
return ResponseEntity.notFound().build();
145-
}
114+
Long memberId = (Long) authentication.getPrincipal();
115+
String loginId = (String) authentication.getDetails();
116+
log.info("회원탈퇴 요청: memberId={}, email={}", memberId, loginId);
117+
118+
memberService.withdraw(memberId);
119+
memberService.logout(loginId, response); // 회원 탈퇴 후 세션 및 토큰 정리
120+
log.info("회원탈퇴 성공: memberId={}, email={}", memberId, loginId);
121+
return ResponseEntity.ok().build();
146122
}
147123

148124
@GetMapping("/me")
@@ -152,26 +128,23 @@ public ResponseEntity<Void> withdraw(Authentication authentication, HttpServletR
152128
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자")
153129
})
154130
public ResponseEntity<MemberResponse> getMyInfo(Authentication authentication) {
155-
if (authentication == null || authentication.getName() == null) {
156-
log.warn("인증되지 않은 정보 조회 요청");
157-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
131+
if (authentication == null || authentication.getPrincipal() == null) {
132+
throw new com.ai.lawyer.domain.member.exception.MemberAuthenticationException("인증이 필요합니다.");
158133
}
159134

160-
String loginId = authentication.getName();
161-
log.info("내 정보 조회 요청: email={}", loginId);
162-
163-
try {
164-
// loginId로 Member를 조회하여 실제 memberId 사용
165-
Member member = memberService.findByLoginId(loginId);
166-
MemberResponse response = memberService.getMemberById(member.getMemberId());
167-
log.info("내 정보 조회 성공: memberId={}", response.getMemberId());
168-
return ResponseEntity.ok(response);
169-
} catch (IllegalArgumentException e) {
170-
log.warn("내 정보 조회 실패: {}", e.getMessage());
171-
return ResponseEntity.notFound().build();
172-
}
135+
Long memberId = (Long) authentication.getPrincipal();
136+
log.info("내 정보 조회 요청: memberId={}", memberId);
137+
138+
MemberResponse response = memberService.getMemberById(memberId);
139+
log.info("내 정보 조회 성공: memberId={}", response.getMemberId());
140+
return ResponseEntity.ok(response);
173141
}
174142

143+
/**
144+
* HTTP 쿠키에서 리프레시 토큰을 추출합니다.
145+
* @param request HTTP 요청 객체
146+
* @return 리프레시 토큰 값 또는 null
147+
*/
175148
private String extractRefreshTokenFromCookies(HttpServletRequest request) {
176149
if (request.getCookies() != null) {
177150
for (jakarta.servlet.http.Cookie cookie : request.getCookies()) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.ai.lawyer.domain.member.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
import java.time.LocalDateTime;
7+
8+
@Getter
9+
@AllArgsConstructor
10+
public class MemberErrorResponse {
11+
private final String message;
12+
private final int status;
13+
private final String error;
14+
private final LocalDateTime timestamp;
15+
16+
public static MemberErrorResponse of(String message, int status, String error) {
17+
return new MemberErrorResponse(message, status, error, LocalDateTime.now());
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.ai.lawyer.domain.member.exception;
2+
3+
public class MemberAuthenticationException extends RuntimeException {
4+
public MemberAuthenticationException(String message) {
5+
super(message);
6+
}
7+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.ai.lawyer.domain.member.exception;
2+
3+
import com.ai.lawyer.domain.member.dto.MemberErrorResponse;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.MethodArgumentNotValidException;
8+
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import org.springframework.web.bind.annotation.RestControllerAdvice;
10+
11+
@RestControllerAdvice(basePackages = "com.ai.lawyer.domain.member")
12+
@Slf4j
13+
public class MemberExceptionHandler {
14+
15+
/**
16+
* IllegalArgumentException 고도화 처리
17+
* 메시지에 따라 HTTP 상태코드와 에러 메시지 다르게 반환
18+
*/
19+
@ExceptionHandler(IllegalArgumentException.class)
20+
public ResponseEntity<MemberErrorResponse> handleMemberIllegalArgumentException(IllegalArgumentException e) {
21+
log.warn("Member 도메인 IllegalArgumentException: {}", e.getMessage());
22+
23+
String msg = e.getMessage();
24+
HttpStatus status;
25+
String error = switch (msg) {
26+
case "이미 존재하는 이메일입니다.", "잘못된 입력입니다." -> {
27+
status = HttpStatus.BAD_REQUEST;
28+
yield "잘못된 요청";
29+
}
30+
case "존재하지 않는 회원입니다.", "비밀번호가 일치하지 않습니다." -> {
31+
status = HttpStatus.UNAUTHORIZED;
32+
yield "인증 실패";
33+
}
34+
default -> {
35+
status = HttpStatus.BAD_REQUEST;
36+
yield "오류 발생";
37+
}
38+
};
39+
40+
// 메시지 기반으로 상태코드 결정
41+
42+
MemberErrorResponse errorResponse = MemberErrorResponse.of(msg, status.value(), error);
43+
return ResponseEntity.status(status).body(errorResponse);
44+
}
45+
46+
/**
47+
* 인증 관련 예외 처리
48+
*/
49+
@ExceptionHandler(MemberAuthenticationException.class)
50+
public ResponseEntity<MemberErrorResponse> handleMemberAuthenticationException(MemberAuthenticationException e) {
51+
log.warn("Member 도메인 AuthenticationException: {}", e.getMessage());
52+
MemberErrorResponse errorResponse = MemberErrorResponse.of(
53+
e.getMessage(),
54+
HttpStatus.UNAUTHORIZED.value(),
55+
"인증 실패"
56+
);
57+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
58+
}
59+
60+
/**
61+
* 유효성 검증 실패 처리
62+
*/
63+
@ExceptionHandler(MethodArgumentNotValidException.class)
64+
public ResponseEntity<MemberErrorResponse> handleMemberValidationException(MethodArgumentNotValidException e) {
65+
String message = e.getBindingResult().getAllErrors().getFirst().getDefaultMessage();
66+
log.warn("Member 도메인 유효성 검증 실패: {}", message);
67+
MemberErrorResponse errorResponse = MemberErrorResponse.of(
68+
message,
69+
HttpStatus.BAD_REQUEST.value(),
70+
"유효성 검증 실패"
71+
);
72+
return ResponseEntity.badRequest().body(errorResponse);
73+
}
74+
}

0 commit comments

Comments
 (0)