diff --git a/src/main/java/com/ureca/unity/domain/auth/controller/LogoutController.java b/src/main/java/com/ureca/unity/domain/auth/controller/LogoutController.java index 05438c2..02eaf1a 100644 --- a/src/main/java/com/ureca/unity/domain/auth/controller/LogoutController.java +++ b/src/main/java/com/ureca/unity/domain/auth/controller/LogoutController.java @@ -30,8 +30,14 @@ public void logout( HttpServletResponse response ) { logoutService.logout(refreshToken); + + // 1. 현재 표준 쿠기 (Path=/) 삭제 response.addCookie( CookieUtils.deleteRefreshTokenCookie(cookieSecure) ); + + // 2. 레거시 쿠키(Path=/api/auth)도 삭제 (과거 잔재 청소) + response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Lax")); + response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Strict")); } } \ No newline at end of file diff --git a/src/main/java/com/ureca/unity/domain/auth/controller/OAuthController.java b/src/main/java/com/ureca/unity/domain/auth/controller/OAuthController.java index a9377c5..bea21d9 100644 --- a/src/main/java/com/ureca/unity/domain/auth/controller/OAuthController.java +++ b/src/main/java/com/ureca/unity/domain/auth/controller/OAuthController.java @@ -39,6 +39,11 @@ public OAuthLoginResponse login( ) { OAuthLoginResult result = oAuthService.login(OAuthProvider.from(provider), code); + // 1. 레거시(/api/auth) 쿠키 제거 (예전 SameSite가 Strict였다면 Strict로 한 번 더) + response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Strict")); + response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Lax")); + + // 2. 정상(/) 쿠키 설정 response.addCookie( CookieUtils.createRefreshTokenCookie( result.refreshToken(), diff --git a/src/main/java/com/ureca/unity/domain/summary/controller/SummaryController.java b/src/main/java/com/ureca/unity/domain/summary/controller/SummaryController.java index 0e11d13..390b023 100644 --- a/src/main/java/com/ureca/unity/domain/summary/controller/SummaryController.java +++ b/src/main/java/com/ureca/unity/domain/summary/controller/SummaryController.java @@ -3,11 +3,16 @@ import com.ureca.unity.domain.summary.dto.response.SummaryDetailResponse; import com.ureca.unity.domain.summary.dto.response.SummaryListResponse; import com.ureca.unity.domain.summary.service.SummaryService; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +@Tag( + name = "4. Summary", + description = "요약 관련 API" +) @RestController @RequestMapping("/api/summaries") @RequiredArgsConstructor @@ -15,13 +20,13 @@ public class SummaryController { private final SummaryService summaryService; - // 전체 요약 리스트 + // 전체 요약 목록 @GetMapping public List getMySummaries(@RequestParam Long userId) { return summaryService.getMySummaries(userId); } - // 북마크 요약 리스트 + // 북마크 요약 목록 @GetMapping("/bookmarks") public List getBookmarkedSummaries(@RequestParam Long userId) { return summaryService.getBookmarkedSummaries(userId); @@ -32,4 +37,10 @@ public List getBookmarkedSummaries(@RequestParam Long userI public SummaryDetailResponse getSummaryDetail(@PathVariable Long summaryId) { return summaryService.getSummaryDetail(summaryId); } + + // 북마크 토글 (프론트가 PATCH로 호출 중) + @PatchMapping("/{summaryId}/bookmark") + public void toggleBookmark(@PathVariable Long summaryId) { + summaryService.toggleBookmark(summaryId); + } } diff --git a/src/main/java/com/ureca/unity/domain/summary/mapper/SummaryMapper.java b/src/main/java/com/ureca/unity/domain/summary/mapper/SummaryMapper.java index d86ba59..8352e5d 100644 --- a/src/main/java/com/ureca/unity/domain/summary/mapper/SummaryMapper.java +++ b/src/main/java/com/ureca/unity/domain/summary/mapper/SummaryMapper.java @@ -39,12 +39,9 @@ void updateBookmark( @Param("isBookmarked") boolean isBookmarked ); - // 전체 목록 List findByUserId(@Param("userId") Long userId); - // 북마크 목록 List findBookmarkedByUserId(@Param("userId") Long userId); - // 상세 SummaryModel findById(@Param("summaryId") Long summaryId); } diff --git a/src/main/java/com/ureca/unity/domain/summary/service/SummaryService.java b/src/main/java/com/ureca/unity/domain/summary/service/SummaryService.java index f88362a..9f752d3 100644 --- a/src/main/java/com/ureca/unity/domain/summary/service/SummaryService.java +++ b/src/main/java/com/ureca/unity/domain/summary/service/SummaryService.java @@ -26,11 +26,7 @@ public class SummaryService { private final ObjectMapper objectMapper; @Transactional - public void createSummary( - Long counselingResultId, - Long userId, - String counselingText - ) { + public void createSummary(Long counselingResultId, Long userId, String counselingText) { if (userId == null) { throw new CustomException(ErrorCode.INVALID_INPUT_VALUE); } @@ -61,13 +57,8 @@ public void createSummary( summaryMapper.updateStatus(summaryId, "SUCCESS"); - // 필요하면 반환형으로 바꾸거나, 로그용으로 사용 - new SummaryResponse( - gemini.getTitle(), - gemini.getSubject(), - keywords, - points - ); + // (현재는 반환값 사용 안 하니 생성만 유지) + new SummaryResponse(gemini.getTitle(), gemini.getSubject(), keywords, points); } catch (Exception e) { summaryMapper.updateStatus(summaryId, "FAIL"); @@ -78,72 +69,31 @@ public void createSummary( @Transactional(readOnly = true) public List getMySummaries(Long userId) { return summaryMapper.findByUserId(userId).stream() - .map(summary -> { - try { - List keywords = - summary.getKeywords() != null - ? objectMapper.readValue(summary.getKeywords(), - new TypeReference>() {}) - : List.of(); - - return new SummaryListResponse( - summary.getSummaryId(), - summary.getTitle(), - summary.getStatus(), - keywords, - summary.getCreatedAt() - ); - } catch (Exception e) { - throw new IllegalStateException(e); - } - }) + .map(this::toListResponse) .toList(); } @Transactional(readOnly = true) public List getBookmarkedSummaries(Long userId) { return summaryMapper.findBookmarkedByUserId(userId).stream() - .map(summary -> { - try { - List keywords = - summary.getKeywords() != null - ? objectMapper.readValue(summary.getKeywords(), - new TypeReference>() {}) - : List.of(); - - return new SummaryListResponse( - summary.getSummaryId(), - summary.getTitle(), - summary.getStatus(), - keywords, - summary.getCreatedAt() - ); - } catch (Exception e) { - throw new IllegalStateException(e); - } - }) + .map(this::toListResponse) .toList(); } @Transactional(readOnly = true) public SummaryDetailResponse getSummaryDetail(Long summaryId) { SummaryModel summary = summaryMapper.findById(summaryId); - - if (summary == null) { - return null; - } + if (summary == null) return null; try { List keywords = summary.getKeywords() != null - ? objectMapper.readValue(summary.getKeywords(), - new TypeReference>() {}) + ? objectMapper.readValue(summary.getKeywords(), new TypeReference>() {}) : List.of(); List points = summary.getPoints() != null - ? objectMapper.readValue(summary.getPoints(), - new TypeReference>() {}) + ? objectMapper.readValue(summary.getPoints(), new TypeReference>() {}) : List.of(); return new SummaryDetailResponse( @@ -156,7 +106,6 @@ public SummaryDetailResponse getSummaryDetail(Long summaryId) { summary.getIsBookmarked(), summary.getCreatedAt() ); - } catch (Exception e) { throw new IllegalStateException(e); } @@ -165,11 +114,28 @@ public SummaryDetailResponse getSummaryDetail(Long summaryId) { @Transactional public void toggleBookmark(Long summaryId) { Boolean isBookmarked = summaryMapper.findBookmarkStatus(summaryId); - if (isBookmarked == null) { throw new IllegalArgumentException("summary not found"); } - summaryMapper.updateBookmark(summaryId, !isBookmarked); } + + private SummaryListResponse toListResponse(SummaryModel summary) { + try { + List keywords = + summary.getKeywords() != null + ? objectMapper.readValue(summary.getKeywords(), new TypeReference>() {}) + : List.of(); + + return new SummaryListResponse( + summary.getSummaryId(), + summary.getTitle(), + summary.getStatus(), + keywords, + summary.getCreatedAt() + ); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } } diff --git a/src/main/java/com/ureca/unity/global/config/WebMvcConfig.java b/src/main/java/com/ureca/unity/global/config/WebMvcConfig.java index fbddb99..f91e736 100644 --- a/src/main/java/com/ureca/unity/global/config/WebMvcConfig.java +++ b/src/main/java/com/ureca/unity/global/config/WebMvcConfig.java @@ -14,7 +14,7 @@ public class WebMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 모든 엔드포인트 .allowedOrigins(allowedOrigins.split("\\s*,\\s*")) - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowCredentials(true); } } \ No newline at end of file diff --git a/src/main/java/com/ureca/unity/global/util/CookieUtils.java b/src/main/java/com/ureca/unity/global/util/CookieUtils.java index 583c87c..11f5028 100644 --- a/src/main/java/com/ureca/unity/global/util/CookieUtils.java +++ b/src/main/java/com/ureca/unity/global/util/CookieUtils.java @@ -21,7 +21,7 @@ public static Cookie createRefreshTokenCookie( cookie.setSecure(secure); cookie.setPath(PATH); cookie.setMaxAge((int) maxAgeSeconds); - cookie.setAttribute("SameSite", "Strict"); + cookie.setAttribute("SameSite", "Lax"); return cookie; } @@ -31,7 +31,23 @@ public static Cookie deleteRefreshTokenCookie(boolean secure) { cookie.setSecure(secure); cookie.setPath(PATH); cookie.setMaxAge(0); - cookie.setAttribute("SameSite", "Strict"); + cookie.setAttribute("SameSite", "Lax"); return cookie; } + + public static Cookie deleteRefreshTokenCookie(boolean secure, String path, String sameSite) { + String safePath = (path == null || path.isBlank()) ? PATH : path; + + String normalizedSameSite = + (sameSite != null && "Strict".equalsIgnoreCase(sameSite)) ? "Strict" : "Lax"; + + Cookie cookie = new Cookie(REFRESH_TOKEN, null); + cookie.setHttpOnly(true); + cookie.setSecure(secure); + cookie.setPath(safePath); + cookie.setMaxAge(0); + cookie.setAttribute("SameSite", normalizedSameSite); + return cookie; + } + } diff --git a/src/main/resources/mapper/summary/SummaryMapper.xml b/src/main/resources/mapper/summary/SummaryMapper.xml index 5588552..fc9ee4b 100644 --- a/src/main/resources/mapper/summary/SummaryMapper.xml +++ b/src/main/resources/mapper/summary/SummaryMapper.xml @@ -6,7 +6,7 @@ - + INSERT INTO summary ( counseling_result_id, user_id, @@ -37,8 +37,14 @@ SET title = #{title}, subject = #{subject}, - keywords = CAST(#{keywords} AS JSON), - points = CAST(#{points} AS JSON) + keywords = CASE + WHEN #{keywords} IS NULL THEN NULL + ELSE CAST(#{keywords} AS JSON) + END, + points = CASE + WHEN #{points} IS NULL THEN NULL + ELSE CAST(#{points} AS JSON) + END WHERE summary_id = #{summaryId} @@ -48,71 +54,73 @@ WHERE summary_id = #{summaryId} + + UPDATE summary SET is_bookmarked = #{isBookmarked} WHERE summary_id = #{summaryId} - + - + - +