Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum ErrorStatus implements BaseErrorCode {

//게시글 관련 에러
_NOT_FOUND_POST(HttpStatus.NOT_FOUND, "POST401", "해당 게시글을 찾을 수 없습니다."),
_NOT_USER_POST(HttpStatus.NOT_FOUND, "POST402", "해당 유저의 게시글이 아닙니다."),

//댓글 관련 에러
_NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, "COMMENT404", "해당 댓글을 찾을 수 없습니다.");
Expand Down
13 changes: 6 additions & 7 deletions src/main/java/com/example/be/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,21 @@ public class SecurityConfig {
private final OAuthLoginSuccessHandler oAuthLoginSuccessHandler;
private final OAuthLoginFailureHandler oAuthLoginFailureHandler;

// CORS 설정
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"https://studylink.store", // 프론트엔드 도메인
"https://api.studylink.store", // 백엔드 API 도메인
"https://swagger.studylink.store", // Swagger UI 도메인 (필요시)
"https://studylink.store",
"https://api.studylink.store",
"https://swagger.studylink.store",
"http://localhost:6080",
"http://localhost:5173"
"http://localhost:5173" // 프론트엔드 개발 서버
));
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("Authorization"));
config.setExposedHeaders(List.of("Authorization", "Set-Cookie")); // Set-Cookie 헤더 노출
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
config.setAllowCredentials(true);
config.setMaxAge(3600L); // 프리플라이트 요청 캐싱 시간 (1시간)
config.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/example/be/repository/PostRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public interface PostRepository extends JpaRepository<Post, Long> {

@EntityGraph(attributePaths = {"comments", "comments.user", "user"})
Optional<Post> findById(Long id);

Optional<Post> findByIdAndUserId(Long id, Long userId);
}
12 changes: 12 additions & 0 deletions src/main/java/com/example/be/service/PostServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,17 @@ public PostDTO.PostDetailResponseDTO getPostDetail(Long postId, HttpServletReque
.build();
}

public CommonDTO.IsSuccessDTO deletePost(Long postId, HttpServletRequest request) {
User user= getUserFromRequest(request);

postRepository.findById(postId).orElseThrow(() ->
new PostHandler(ErrorStatus._NOT_FOUND_POST));

Post post = postRepository.findByIdAndUserId(postId, user.getId()).orElseThrow(()->
new PostHandler(ErrorStatus._NOT_USER_POST));

postRepository.delete(post);

return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
}
}
53 changes: 24 additions & 29 deletions src/main/java/com/example/be/service/UserServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,31 +85,26 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet
String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME);

String origin = httpRequest.getHeader("Origin");
boolean isSecure = origin == null || !origin.contains("localhost");

// 액세스 토큰
if (isSecure) {
// 배포 환경: Secure + SameSite=None
// boolean isLocalhost = origin != null && origin.contains("localhost");
//
// // 액세스 토큰 쿠키 설정
// if (isLocalhost) {
// // 로컬 개발 환경: SameSite=None, Secure=false
// response.addHeader("Set-Cookie",
// String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
// accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000)));
// response.addHeader("Set-Cookie",
// String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
// refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000)));
// } else {
// 배포 환경: SameSite=None, Secure=true
response.addHeader("Set-Cookie",
String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None",
accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000)));
} else {
// 로컬 환경: SameSite=None (Secure 없음)
response.addHeader("Set-Cookie",
String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000)));
}

// 리프레시 토큰
if (isSecure) {
response.addHeader("Set-Cookie",
String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None",
refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000)));
} else {
response.addHeader("Set-Cookie",
String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None",
refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000)));
}
// }

return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
}
Expand Down Expand Up @@ -145,23 +140,23 @@ public CommonDTO.IsSuccessDTO logout(HttpServletResponse response, HttpServletRe
}

// Origin 헤더로 환경 판단
String origin = request.getHeader("Origin");
boolean isSecure = origin == null || !origin.contains("localhost");
// String origin = request.getHeader("Origin");
// boolean isSecure = origin == null || !origin.contains("localhost");

// 쿠키 삭제 - addHeader 방식 사용
if (isSecure) {
// if (isSecure) {
// 배포 환경
response.addHeader("Set-Cookie",
"accessToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None");
response.addHeader("Set-Cookie",
"refreshToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None");
} else {
// 로컬 환경
response.addHeader("Set-Cookie",
"accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
response.addHeader("Set-Cookie",
"refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
}
// } else {
// // 로컬 환경
// response.addHeader("Set-Cookie",
// "accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
// response.addHeader("Set-Cookie",
// "refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None");
// }

return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public ApiResponse<PostDTO.PostDetailResponseDTO> getPostDetail(
return ApiResponse.onSuccess(postService.getPostDetail(postId, request));
}

// 좋아요 토글 API 추가
@PostMapping("/{postId}/like")
@Operation(summary = "게시글 좋아요 토글 API", description = "게시글에 좋아요를 누르거나 취소합니다.")
public ApiResponse<PostDTO.PostLikeResponseDTO> togglePostLike(
Expand All @@ -59,4 +58,12 @@ public ApiResponse<PostDTO.PostLikeResponseDTO> togglePostLike(
return ApiResponse.onSuccess(postLikeService.togglePostLike(postId, request));
}

@PostMapping("/{postId}/delete")
@Operation(summary = "게시글 삭제 API", description = "게시글을 삭제합니다.")
public ApiResponse<CommonDTO.IsSuccessDTO> delete(
@Parameter(description = "게시글 ID") @PathVariable Long postId,
HttpServletRequest request) {
return ApiResponse.onSuccess(postService.deletePost(postId, request));
}

}