diff --git a/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java index bdca8d7..d3da085 100644 --- a/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/example/be/apiPayload/code/status/ErrorStatus.java @@ -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", "해당 댓글을 찾을 수 없습니다."); diff --git a/src/main/java/com/example/be/config/SecurityConfig.java b/src/main/java/com/example/be/config/SecurityConfig.java index d313f7d..ae9b4df 100644 --- a/src/main/java/com/example/be/config/SecurityConfig.java +++ b/src/main/java/com/example/be/config/SecurityConfig.java @@ -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); diff --git a/src/main/java/com/example/be/repository/PostRepository.java b/src/main/java/com/example/be/repository/PostRepository.java index e63bd21..37c050a 100644 --- a/src/main/java/com/example/be/repository/PostRepository.java +++ b/src/main/java/com/example/be/repository/PostRepository.java @@ -15,4 +15,6 @@ public interface PostRepository extends JpaRepository { @EntityGraph(attributePaths = {"comments", "comments.user", "user"}) Optional findById(Long id); + + Optional findByIdAndUserId(Long id, Long userId); } diff --git a/src/main/java/com/example/be/service/PostServiceImpl.java b/src/main/java/com/example/be/service/PostServiceImpl.java index 1ea6bfe..4150efa 100644 --- a/src/main/java/com/example/be/service/PostServiceImpl.java +++ b/src/main/java/com/example/be/service/PostServiceImpl.java @@ -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(); + } } diff --git a/src/main/java/com/example/be/service/UserServiceImpl.java b/src/main/java/com/example/be/service/UserServiceImpl.java index f88de64..7885e94 100644 --- a/src/main/java/com/example/be/service/UserServiceImpl.java +++ b/src/main/java/com/example/be/service/UserServiceImpl.java @@ -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(); } @@ -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(); } diff --git a/src/main/java/com/example/be/web/controller/PostController.java b/src/main/java/com/example/be/web/controller/PostController.java index db2dac9..9d85196 100644 --- a/src/main/java/com/example/be/web/controller/PostController.java +++ b/src/main/java/com/example/be/web/controller/PostController.java @@ -50,7 +50,6 @@ public ApiResponse getPostDetail( return ApiResponse.onSuccess(postService.getPostDetail(postId, request)); } - // 좋아요 토글 API 추가 @PostMapping("/{postId}/like") @Operation(summary = "게시글 좋아요 토글 API", description = "게시글에 좋아요를 누르거나 취소합니다.") public ApiResponse togglePostLike( @@ -59,4 +58,12 @@ public ApiResponse togglePostLike( return ApiResponse.onSuccess(postLikeService.togglePostLike(postId, request)); } + @PostMapping("/{postId}/delete") + @Operation(summary = "게시글 삭제 API", description = "게시글을 삭제합니다.") + public ApiResponse delete( + @Parameter(description = "게시글 ID") @PathVariable Long postId, + HttpServletRequest request) { + return ApiResponse.onSuccess(postService.deletePost(postId, request)); + } + }