From 8e405ca8ccd2cd36ecd41e69ad0e31f7f7bd5582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:40:46 +0900 Subject: [PATCH 1/3] =?UTF-8?q?test:=20controller=EC=A0=84=EB=B6=80=20rest?= =?UTF-8?q?docs=EB=A1=9C=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/alarm/alarm.adoc | 4 + src/docs/asciidoc/api/feed/feed.adoc | 9 + .../api/subscription/subscription.adoc | 24 ++- src/docs/asciidoc/api/summary/summary.adoc | 4 + src/docs/asciidoc/api/url/url.adoc | 4 + src/docs/asciidoc/index.adoc | 22 +- .../controller/AlarmQueryController.java | 1 - .../controller/SubscriptionController.java | 4 +- ...dService.java => SubscriptionService.java} | 4 +- .../presentation/SummaryControllerTest.java | 40 ++++ .../AlarmQueryControllerTest.java | 63 ++++++ .../FeedQueryControllerTest.java} | 23 +-- .../SubscriptionControllerTest.java | 188 +++++++++++++----- .../presentation/UrlQueryControllerTest.java | 38 ++++ .../support/DocumentationTestSupport.java | 29 ++- .../support/RestDocsSupport.java | 45 ----- .../templates/response-fields.snippet | 5 +- 17 files changed, 380 insertions(+), 127 deletions(-) create mode 100644 src/docs/asciidoc/api/alarm/alarm.adoc create mode 100644 src/docs/asciidoc/api/feed/feed.adoc create mode 100644 src/docs/asciidoc/api/summary/summary.adoc create mode 100644 src/docs/asciidoc/api/url/url.adoc rename src/main/java/com/todaysound/todaysound_server/domain/subscription/service/{SubscriptionCommandService.java => SubscriptionService.java} (95%) create mode 100644 src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java create mode 100644 src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java rename src/test/java/com/todaysound/todaysound_server/{domain/feed/controller/FeedControllerTest.java => query/feed/presentation/FeedQueryControllerTest.java} (79%) create mode 100644 src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java delete mode 100644 src/test/java/com/todaysound/todaysound_server/support/RestDocsSupport.java diff --git a/src/docs/asciidoc/api/alarm/alarm.adoc b/src/docs/asciidoc/api/alarm/alarm.adoc new file mode 100644 index 0000000..c097624 --- /dev/null +++ b/src/docs/asciidoc/api/alarm/alarm.adoc @@ -0,0 +1,4 @@ +[[alarm-list]] +=== 알림 목록 조회 + +operation::alarm-query-controller-test/알림_목록을_조회합니다[snippets='http-request,query-parameters,http-response,response-fields'] diff --git a/src/docs/asciidoc/api/feed/feed.adoc b/src/docs/asciidoc/api/feed/feed.adoc new file mode 100644 index 0000000..1898993 --- /dev/null +++ b/src/docs/asciidoc/api/feed/feed.adoc @@ -0,0 +1,9 @@ +[[feed-list]] +=== 피드 목록 조회 + +operation::feed-query-controller-test/피드_목록을_조회합니다[snippets='http-request,query-parameters,http-response,response-fields'] + +[[home-feed-list]] +=== 홈 피드 목록 조회 + +operation::feed-query-controller-test/홈_피드_목록을_조회합니다[snippets='http-request,http-response,response-fields'] diff --git a/src/docs/asciidoc/api/subscription/subscription.adoc b/src/docs/asciidoc/api/subscription/subscription.adoc index 5cdd5ac..87547b4 100644 --- a/src/docs/asciidoc/api/subscription/subscription.adoc +++ b/src/docs/asciidoc/api/subscription/subscription.adoc @@ -1,4 +1,24 @@ [[subscription-create]] -=== 구독 목록 등록 +=== 구독 등록 -operation::subscription-controller-test/신규_구독을_등록한다[snippets='http-request,request-fields,http-response,response-fields'] \ No newline at end of file +operation::subscription-controller-test/신규_구독을_등록한다[snippets='http-request,request-fields,http-response,response-fields'] + +[[subscription-list]] +=== 구독 목록 조회 + +operation::subscription-controller-test/구독_목록_조회[snippets='http-request,query-parameters,http-response,response-fields'] + +[[subscription-update]] +=== 구독 수정 + +operation::subscription-controller-test/구독을_수정한다[snippets='http-request,path-parameters,request-fields,http-response'] + +[[subscription-delete]] +=== 구독 삭제 + +operation::subscription-controller-test/구독을_삭제한다[snippets='http-request,path-parameters,http-response,response-fields'] + +[[keyword-list]] +=== 키워드 목록 조회 + +operation::subscription-controller-test/키워드_목록을_조회한다[snippets='http-request,http-response,response-fields'] diff --git a/src/docs/asciidoc/api/summary/summary.adoc b/src/docs/asciidoc/api/summary/summary.adoc new file mode 100644 index 0000000..0277d54 --- /dev/null +++ b/src/docs/asciidoc/api/summary/summary.adoc @@ -0,0 +1,4 @@ +[[summary-delete]] +=== 요약 삭제 + +operation::summary-controller-test/요약을_삭제한다[snippets='http-request,path-parameters,http-response,response-fields'] diff --git a/src/docs/asciidoc/api/url/url.adoc b/src/docs/asciidoc/api/url/url.adoc new file mode 100644 index 0000000..9c85ead --- /dev/null +++ b/src/docs/asciidoc/api/url/url.adoc @@ -0,0 +1,4 @@ +[[url-list]] +=== URL 목록 조회 + +operation::url-query-controller-test/url목록을_조회한다[snippets='http-request,http-response,response-fields'] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 6d850fd..1148806 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -12,4 +12,24 @@ endif::[] [[Subscription-API]] == Subscription API -include::api/subscription/subscription.adoc[] \ No newline at end of file +include::api/subscription/subscription.adoc[] + +[[Feed-API]] +== Feed API + +include::api/feed/feed.adoc[] + +[[Alarm-API]] +== Alarm API + +include::api/alarm/alarm.adoc[] + +[[Url-API]] +== URL API + +include::api/url/url.adoc[] + +[[Summary-API]] +== Summary API + +include::api/summary/summary.adoc[] diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java index d58fe98..f87f55a 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java @@ -15,7 +15,6 @@ public class AlarmQueryController implements AlarmApi { private final AlarmQueryService alarmQueryService; - private final SummaryCommandService summaryCommandService; /** * 최근 알림 목록 조회 diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java index 26631d3..d5f1952 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java @@ -5,7 +5,7 @@ import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; -import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionCommandService; +import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionQueryService; import com.todaysound.todaysound_server.global.dto.PageRequest; @@ -23,7 +23,7 @@ public class SubscriptionController implements SubscriptionApi { private final SubscriptionQueryService subscriptionQueryService; - private final SubscriptionCommandService subscriptionCommandService; + private final SubscriptionService subscriptionCommandService; // 사용자의 구독을 페이지네이션(한 페이지 size 만큼)해서 가져옴. @GetMapping() diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionCommandService.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java similarity index 95% rename from src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionCommandService.java rename to src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java index 8b04104..f2137ca 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionCommandService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java @@ -4,8 +4,6 @@ import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; import com.todaysound.todaysound_server.domain.subscription.exception.KeywordException; import com.todaysound.todaysound_server.domain.subscription.repository.KeywordRepository; -import com.todaysound.todaysound_server.domain.subscription.repository.SubscriptionKeywordRepository; -import com.todaysound.todaysound_server.global.exception.CommonErrorCode; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,7 +21,7 @@ @Service @Transactional @RequiredArgsConstructor -public class SubscriptionCommandService { +public class SubscriptionService { private final SubscriptionRepository subscriptionRepository; private final KeywordRepository keywordRepository; diff --git a/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java new file mode 100644 index 0000000..728529d --- /dev/null +++ b/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java @@ -0,0 +1,40 @@ +package com.todaysound.todaysound_server.command.summary.presentation; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.todaysound.todaysound_server.support.DocumentationTestSupport; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; + +class SummaryControllerTest extends DocumentationTestSupport { + + @Test + void 요약을_삭제한다() throws Exception { + // given + Long summaryId = 1L; + + doNothing().when(summaryCommandService).deleteSummary(anyString(), anyString(), anyLong()); + + // when then + mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/summaries/{summaryId}", summaryId) + .header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret")) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(restDocsHandler.document( + pathParameters(parameterWithName("summaryId").description("삭제할 요약의 ID")), + responseFields( + fieldWithPath("errorCode").type(JsonFieldType.NULL).description("에러 코드, 성공 시 null"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.NULL).description("응답 데이터, 삭제 시 null")))); + } +} diff --git a/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java new file mode 100644 index 0000000..af4449e --- /dev/null +++ b/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java @@ -0,0 +1,63 @@ +package com.todaysound.todaysound_server.query.alarm.presentation; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willReturn; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; +import com.todaysound.todaysound_server.support.DocumentationTestSupport; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class AlarmQueryControllerTest extends DocumentationTestSupport { + + @Test + void 알림_목록을_조회합니다() throws Exception { + // given + PageRequest pageRequest = PageRequest.of(0, 10); + + RecentAlarmResponse response1 = new RecentAlarmResponse(1L, 2L, "제목1", "내용1", "http://naver.com", "1시간 전", + true); + RecentAlarmResponse response2 = new RecentAlarmResponse(2L, 3L, "제목2", "내용2", "http://google.com", "2시간 전", + false); + RecentAlarmResponse response3 = new RecentAlarmResponse(3L, 4L, "제목3", "내용3", "http://daum.net", "3시간 전", true); + + List responseList = List.of(response1, response2, response3); + + given(alarmQueryService.getRecentAlarms(any(), anyString(), anyString())).willReturn(responseList); + + // when + ResultActions result = mockMvc.perform( + get("/api/alarms").param("page", String.valueOf(pageRequest.getPageNumber())) + .param("size", String.valueOf(pageRequest.getPageSize())).header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret")); + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + queryParameters(parameterWithName("page").description("페이지 번호 (0부터 시작)"), + parameterWithName("size").description("페이지 크기")), + responseFields(fieldWithPath("errorCode").description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.ARRAY).description("피드 목록"), + fieldWithPath("result[].subscriptionId").type(JsonFieldType.NUMBER).description("구독 ID"), + fieldWithPath("result[].summaryId").type(JsonFieldType.NUMBER).description("요약 ID"), + fieldWithPath("result[].alias").type(JsonFieldType.STRING).description("구독 별칭"), + fieldWithPath("result[].summaryContent").type(JsonFieldType.STRING).description("요약 내용"), + fieldWithPath("result[].postUrl").type(JsonFieldType.STRING).description("연결 url"), + fieldWithPath("result[].timeAgo").type(JsonFieldType.STRING).description("피드 작성 후 경과 시간"), + fieldWithPath("result[].isKeywordMatched").type(JsonFieldType.BOOLEAN) + .description("키워드 매칭 여부")))); + + } +} \ No newline at end of file diff --git a/src/test/java/com/todaysound/todaysound_server/domain/feed/controller/FeedControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java similarity index 79% rename from src/test/java/com/todaysound/todaysound_server/domain/feed/controller/FeedControllerTest.java rename to src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java index ddad25a..d861a05 100644 --- a/src/test/java/com/todaysound/todaysound_server/domain/feed/controller/FeedControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java @@ -1,14 +1,11 @@ -package com.todaysound.todaysound_server.domain.feed.controller; +package com.todaysound.todaysound_server.query.feed.presentation; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willReturn; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -21,7 +18,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.test.web.servlet.ResultActions; -class FeedControllerTest extends DocumentationTestSupport { +class FeedQueryControllerTest extends DocumentationTestSupport { @Test @@ -48,14 +45,14 @@ class FeedControllerTest extends DocumentationTestSupport { result.andExpect(status().isOk()).andDo(restDocsHandler.document( queryParameters(parameterWithName("page").description("페이지 번호 (0부터 시작)"), parameterWithName("size").description("페이지 크기")), - responseFields(fieldWithPath("errorCode").description("응답 코드"), - fieldWithPath("message").description("응답 메시지"), fieldWithPath("result").description("피드 목록"), - fieldWithPath("result[].subscriptionId").description("구독 ID"), - fieldWithPath("result[].alias").description("구독 별칭"), - fieldWithPath("result[].summaryTitle").description("요약 제목"), - fieldWithPath("result[].summaryContent").description("요약 내용"), - fieldWithPath("result[].postUrl").description("연결 url"), - fieldWithPath("result[].timeAgo").description("피드 작성 후 경과 시간")))); + responseFields(fieldWithPath("errorCode").description("응답 코드"), + fieldWithPath("message").description("응답 메시지"), fieldWithPath("result").description("피드 목록"), + fieldWithPath("result[].subscriptionId").description("구독 ID"), + fieldWithPath("result[].alias").description("구독 별칭"), + fieldWithPath("result[].summaryTitle").description("요약 제목"), + fieldWithPath("result[].summaryContent").description("요약 내용"), + fieldWithPath("result[].postUrl").description("연결 url"), + fieldWithPath("result[].timeAgo").description("피드 작성 후 경과 시간")))); } diff --git a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java index 37f0190..4f97558 100644 --- a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java @@ -1,74 +1,154 @@ package com.todaysound.todaysound_server.query.feed.presentation; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequestDto; +import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionUpdateRequest; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto.KeywordItem; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; -import com.todaysound.todaysound_server.support.RestDocsSupport; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse.KeywordResponse; +import com.todaysound.todaysound_server.support.DocumentationTestSupport; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; -public class SubscriptionControllerTest extends RestDocsSupport { - - -// @Test -// void 신규_구독을_등록한다() throws Exception { -// // given -// SubscriptionCreateRequestDto request = new SubscriptionCreateRequestDto( -// 1L, -// List.of(1L, 2L), -// "넓은마을", -// true -// ); -// -// given(subscriptionCommandService.createSubscription(anyString(), anyString(), any(SubscriptionCreateRequestDto.class))) -// .willReturn(SubscriptionCreationResponseDto.builder() -// .subscriptionId(1L) -// .build() -// ); -// -// // when then -// mockMvc.perform( -// post("/api/subscriptions") -// .content(objectMapper.writeValueAsString(request)) -// .header("X-User-ID", "test-user-uuid") -// .header("X-Device-Secret", "test-device-secret") -// .contentType(MediaType.APPLICATION_JSON) -// ) -// .andDo(print()) -// .andExpect(status().isOk()) -// .andDo(restDocsHandler.document( -// requestFields( -// fieldWithPath("urlId").type(JsonFieldType.NUMBER) -// .description("구독할 URL의 ID"), -// fieldWithPath("keywordIds").type(JsonFieldType.ARRAY) -// .optional() -// .description("구독에 연관된 키워드 ID 리스트"), -// fieldWithPath("alias").type(JsonFieldType.STRING) -// .description("구독 별칭"), -// fieldWithPath("isUrgent").type(JsonFieldType.BOOLEAN) -// .description("긴급 알림 여부") -// ), -// responseFields( -// fieldWithPath("errorCode").type(JsonFieldType.NULL) -// .description("에러 코드, 성공 시 null"), -// fieldWithPath("message").type(JsonFieldType.STRING) -// .description("응답 메시지"), -// fieldWithPath("result").type(JsonFieldType.OBJECT) -// .description("응답 데이터"), -// fieldWithPath("result.subscriptionId").type(JsonFieldType.NUMBER) -// .description("생성된 구독의 ID") -// ) -// )); -// } +public class SubscriptionControllerTest extends DocumentationTestSupport { + + + @Test + void 신규_구독을_등록한다() throws Exception { + // given + SubscriptionCreateRequestDto request = new SubscriptionCreateRequestDto(1L, List.of(1L, 2L), "넓은마을", true); + + given(subscriptionCommandService.createSubscription(anyString(), anyString(), + any(SubscriptionCreateRequestDto.class))).willReturn( + SubscriptionCreationResponseDto.builder().subscriptionId(1L).build()); + + // when then + ResultActions result = mockMvc.perform( + post("/api/subscriptions").content(objectMapper.writeValueAsString(request)) + .header("X-User-ID", "test-user-uuid").header("X-Device-Secret", "test-device-secret") + .contentType(MediaType.APPLICATION_JSON)).andDo(print()); + + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("urlId").type(JsonFieldType.NUMBER).description("구독할 URL의 ID"), + fieldWithPath("keywordIds").type(JsonFieldType.ARRAY).optional() + .description("구독에 연관된 키워드 ID 리스트"), + fieldWithPath("alias").type(JsonFieldType.STRING).description("구독 별칭"), + fieldWithPath("isAlarmEnabled").type(JsonFieldType.BOOLEAN).description("알림 여부")), + responseFields(fieldWithPath("errorCode").type(JsonFieldType.NULL).description("에러 코드, 성공 시 null"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("result.subscriptionId").type(JsonFieldType.NUMBER).description("생성된 구독의 ID")))); + } + + @Test + void 구독_목록_조회() throws Exception { + //given + SubscriptionResponse response1 = new SubscriptionResponse(1L, "https://example.com/feed1", "기술 블로그", true, + List.of(new KeywordResponse(1L, "AI"), new KeywordResponse(2L, "개발"))); + SubscriptionResponse response2 = new SubscriptionResponse(2L, "https://example.com/feed2", "뉴스 피드", false, + List.of(new KeywordResponse(3L, "뉴스"))); + + List responseList = List.of(response1, response2); + + given(subscriptionQueryService.getMySubscriptions(any(), anyString(), anyString())).willReturn(responseList); + + //when then + mockMvc.perform(get("/api/subscriptions").header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret").param("page", "0").param("size", "10")).andDo(print()) + .andExpect(status().isOk()).andDo(restDocsHandler.document( + queryParameters(parameterWithName("page").description("페이지 번호").optional(), + parameterWithName("size").description("페이지 크기").optional()), + responseFields(fieldWithPath("errorCode").type(JsonFieldType.NULL).description("에러 코드, 성공 시 null"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.ARRAY).description("구독 목록"), + fieldWithPath("result[].id").type(JsonFieldType.NUMBER).description("구독 ID"), + fieldWithPath("result[].url").type(JsonFieldType.STRING).description("구독 URL"), + fieldWithPath("result[].alias").type(JsonFieldType.STRING).description("구독 별칭"), + fieldWithPath("result[].isAlarmEnabled").type(JsonFieldType.BOOLEAN).description("알림 활성화 여부"), + fieldWithPath("result[].keywords").type(JsonFieldType.ARRAY).description("키워드 목록"), + fieldWithPath("result[].keywords[].id").type(JsonFieldType.NUMBER).description("키워드 ID"), + fieldWithPath("result[].keywords[].name").type(JsonFieldType.STRING).description("키워드 이름")))); + } + + @Test + void 구독을_삭제한다() throws Exception { + //given + Long subscriptionId = 1L; + + doNothing().when(subscriptionCommandService).deleteSubscription(anyLong(), anyString(), anyString()); + + //when then + mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/subscriptions/{subscriptionId}", subscriptionId) + .header("X-User-ID", "test-user-uuid").header("X-Device-Secret", "test-device-secret")).andDo(print()) + .andExpect(status().isOk()).andDo(restDocsHandler.document( + pathParameters(parameterWithName("subscriptionId").description("삭제할 구독의 ID")), + responseFields(fieldWithPath("errorCode").type(JsonFieldType.NULL).description("에러 코드, 성공 시 null"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.NULL).description("응답 데이터, 삭제 시 null")))); + } + + @Test + void 키워드_목록을_조회한다() throws Exception { + //given + KeywordListResponseDto response = new KeywordListResponseDto( + List.of(new KeywordItem(1L, "AI"), new KeywordItem(2L, "개발"), new KeywordItem(3L, "뉴스"))); + + given(subscriptionQueryService.getAllKeywords()).willReturn(response); + + //when then + mockMvc.perform(get("/api/subscriptions/keywords").header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret")).andDo(print()).andExpect(status().isOk()) + .andDo(restDocsHandler.document(responseFields( + fieldWithPath("errorCode").type(JsonFieldType.NULL).description("에러 코드, 성공 시 null"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("result.keywords").type(JsonFieldType.ARRAY).description("키워드 목록"), + fieldWithPath("result.keywords[].id").type(JsonFieldType.NUMBER).description("키워드 ID"), + fieldWithPath("result.keywords[].name").type(JsonFieldType.STRING).description("키워드 이름")))); + } + + @Test + void 구독을_수정한다() throws Exception { + //given + Long subscriptionId = 1L; + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(List.of(1L, 2L), "수정된 별칭", true); + + doNothing().when(subscriptionCommandService) + .updateSubscription(anyLong(), anyString(), anyString(), any(SubscriptionUpdateRequest.class)); + + //when then + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.patch("/api/subscriptions/{subscriptionId}", subscriptionId) + .content(objectMapper.writeValueAsString(request)).header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret").contentType(MediaType.APPLICATION_JSON)) + .andDo(print()); + + result.andExpect(status().isNoContent()).andDo(restDocsHandler.document( + pathParameters(parameterWithName("subscriptionId").description("수정할 구독의 ID")), requestFields( + fieldWithPath("keywordIds").type(JsonFieldType.ARRAY).optional().description("변경할 키워드 ID 리스트"), + fieldWithPath("alias").type(JsonFieldType.STRING).optional().description("변경할 구독 별칭"), + fieldWithPath("isAlarmEnabled").type(JsonFieldType.BOOLEAN).optional() + .description("알림 활성화 여부")))); + } } diff --git a/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java new file mode 100644 index 0000000..09b87b6 --- /dev/null +++ b/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java @@ -0,0 +1,38 @@ +package com.todaysound.todaysound_server.query.url.presentation; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponseDto; +import com.todaysound.todaysound_server.support.DocumentationTestSupport; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class UrlQueryControllerTest extends DocumentationTestSupport { + + @Test + void URL목록을_조회한다() throws Exception { + // given + UrlResponseDto response = new UrlResponseDto(1L, "https://todaysound.com", "Today's Sound"); + + List responseList = List.of(response); + + given(urlQueryService.getUrls()).willReturn(responseList); + + // when + ResultActions result = mockMvc.perform(get("/api/urls")); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + responseFields(fieldWithPath("errorCode").description("응답 코드"), + fieldWithPath("message").description("응답 메시지"), fieldWithPath("result").description("URL 목록"), + fieldWithPath("result[].id").type(JsonFieldType.NUMBER).description("URL ID"), + fieldWithPath("result[].link").type(JsonFieldType.STRING).description("URL 링크"), + fieldWithPath("result[].title").type(JsonFieldType.STRING).description("URL 제목")))); + } +} \ No newline at end of file diff --git a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java index b07b22f..0c19d7a 100644 --- a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java +++ b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java @@ -1,8 +1,17 @@ package com.todaysound.todaysound_server.support; import com.fasterxml.jackson.databind.ObjectMapper; +import com.todaysound.todaysound_server.domain.alarm.controller.AlarmQueryController; +import com.todaysound.todaysound_server.domain.alarm.service.AlarmQueryService; import com.todaysound.todaysound_server.domain.feed.controller.FeedController; import com.todaysound.todaysound_server.domain.feed.service.FeedQueryService; +import com.todaysound.todaysound_server.domain.subscription.controller.SubscriptionController; +import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; +import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionQueryService; +import com.todaysound.todaysound_server.domain.summary.controller.SummaryController; +import com.todaysound.todaysound_server.domain.summary.service.SummaryCommandService; +import com.todaysound.todaysound_server.domain.url.controller.UrlController; +import com.todaysound.todaysound_server.domain.url.service.UrlQueryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -12,9 +21,8 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest(controllers = { - FeedController.class, -}) +@WebMvcTest(controllers = {FeedController.class, AlarmQueryController.class, SubscriptionController.class, + UrlController.class, SummaryController.class}) @Import({RestDocsConfig.class,}) @AutoConfigureRestDocs @AutoConfigureMockMvc(addFilters = false) @@ -32,4 +40,19 @@ public class DocumentationTestSupport { @MockitoBean protected FeedQueryService feedQueryService; + @MockitoBean + protected AlarmQueryService alarmQueryService; + + @MockitoBean + protected SubscriptionQueryService subscriptionQueryService; + + @MockitoBean + protected SubscriptionService subscriptionCommandService; + + @MockitoBean + protected UrlQueryService urlQueryService; + + @MockitoBean + protected SummaryCommandService summaryCommandService; + } diff --git a/src/test/java/com/todaysound/todaysound_server/support/RestDocsSupport.java b/src/test/java/com/todaysound/todaysound_server/support/RestDocsSupport.java deleted file mode 100644 index 2687d12..0000000 --- a/src/test/java/com/todaysound/todaysound_server/support/RestDocsSupport.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.todaysound.todaysound_server.support; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.todaysound.todaysound_server.domain.subscription.controller.SubscriptionController; -import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionCommandService; -import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionQueryService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.context.annotation.Import; -import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; - - -@WebMvcTest( - controllers = { - SubscriptionController.class - } -) -@Import({ - RestDocsConfig.class, -}) -@AutoConfigureMockMvc(addFilters = false) -@AutoConfigureRestDocs -public abstract class RestDocsSupport { - - @Autowired - protected MockMvc mockMvc; - - @Autowired - protected RestDocumentationResultHandler restDocsHandler; - - @Autowired - protected ObjectMapper objectMapper; - - @MockitoBean - protected SubscriptionCommandService subscriptionCommandService; - - @MockitoBean - protected SubscriptionQueryService subscriptionQueryService; - -} diff --git a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet index 457e34d..fa9bbcd 100644 --- a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet @@ -1,14 +1,13 @@ //==== Response Fields |=== -|Path|Type|Optional|Description +|Path|Type|Description {{#fields}} |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} |{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} -|=== \ No newline at end of file +|=== From d65b2bc43d9cf6e8ff0565fdcab1318e530b9fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:12:38 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/chore.yml | 2 +- .github/ISSUE_TEMPLATE/docs.yml | 2 +- .github/ISSUE_TEMPLATE/feature.yml | 2 +- .github/ISSUE_TEMPLATE/fix.yml | 2 +- .github/ISSUE_TEMPLATE/refactor.yml | 2 +- .github/ISSUE_TEMPLATE/setting.yml | 2 +- .github/pull_request_template.md | 6 + build.gradle | 58 ++++----- .../TodaysoundServerApplication.java | 6 +- .../domain/alarm/controller/AlarmApi.java | 3 +- .../alarm/controller/AlarmController.java | 1 - .../controller/AlarmQueryController.java | 10 +- .../controller/InternalAlertController.java | 23 ++-- ...equestDto.java => SummaryReadRequest.java} | 2 +- .../dto/response/UnreadAlarmResponse.java | 7 +- .../alarm/exception/AlarmException.java | 3 +- .../repository/AlarmDynamicRepository.java | 2 +- .../AlarmDynamicRepositoryImpl.java | 11 +- .../alarm/repository/AlarmRepository.java | 3 +- .../alarm/service/AlarmQueryService.java | 9 +- .../domain/feed/controller/FeedApi.java | 31 +++-- .../feed/controller/FeedController.java | 18 +-- ...FeedResponseDTO.java => FeedResponse.java} | 6 +- .../repository/FeedDynamicRepository.java | 4 +- .../repository/FeedDynamicRepositoryImpl.java | 10 +- .../domain/feed/service/FeedQueryService.java | 20 ++- .../controller/InternalSubscriptionApi.java | 9 +- .../InternalSubscriptionController.java | 9 +- .../controller/SubscriptionApi.java | 17 ++- .../controller/SubscriptionController.java | 39 +++--- ...to.java => SubscriptionCreateRequest.java} | 3 +- ...java => InternalSubscriptionResponse.java} | 21 +--- ...ponseDto.java => KeywordListResponse.java} | 7 +- ...java => SubscriptionCreationResponse.java} | 6 +- .../dto/response/SubscriptionResponse.java | 1 - .../domain/subscription/entity/Keyword.java | 11 +- .../subscription/entity/Subscription.java | 18 ++- .../entity/SubscriptionKeyword.java | 1 - .../exception/KeywordException.java | 7 +- .../exception/SubscriptionException.java | 10 +- .../factory/SubscriptionFactory.java | 9 +- .../repository/KeywordRepository.java | 4 +- .../SubscriptionDynamicRepository.java | 5 +- .../SubscriptionDynamicRepositoryImpl.java | 23 ++-- .../SubscriptionKeywordRepository.java | 1 - .../repository/SubscriptionRepository.java | 5 +- .../service/SubscriptionQueryService.java | 56 ++++----- .../service/SubscriptionService.java | 27 ++-- .../summary/controller/SummaryController.java | 18 +-- .../domain/summary/entity/Summary.java | 7 +- .../summary/exception/SummaryException.java | 3 +- .../summary/repository/SummaryRepository.java | 5 +- ...ommandService.java => SummaryService.java} | 11 +- .../domain/url/controller/UrlApi.java | 7 +- .../domain/url/controller/UrlController.java | 7 +- .../{UrlResponseDto.java => UrlResponse.java} | 4 +- .../domain/url/service/UrlQueryService.java | 9 +- .../domain/user/controller/UserApi.java | 118 +++++++++--------- .../user/controller/UserController.java | 30 +++-- ...stDto.java => FCMNotificationRequest.java} | 2 +- ...RequestDto.java => UserSecretRequest.java} | 5 +- .../dto/response/FCMNotificationResponse.java | 8 ++ .../response/FCMNotificationResponseDto.java | 8 -- ...IdResponseDto.java => UserIdResponse.java} | 8 +- .../domain/user/entity/FCM_Token.java | 12 +- .../domain/user/entity/User.java | 36 +++--- .../domain/user/exception/AuthErrorCode.java | 2 +- .../domain/user/factory/UserFactory.java | 20 ++- .../domain/user/repository/FCMRepository.java | 5 +- .../user/repository/UserRepository.java | 5 +- .../domain/user/service/SecretService.java | 14 +-- .../domain/user/service/UserQueryService.java | 2 +- ...erCommandService.java => UserService.java} | 24 ++-- .../user/validator/HeaderAuthValidator.java | 3 +- .../domain/user/validator/UserValidator.java | 2 +- .../global/config/FCMConfig.java | 7 +- .../global/config/MdcLoggingFilter.java | 7 +- .../global/config/SecurityConfig.java | 3 +- .../global/config/SwaggerConfig.java | 53 ++++---- .../global/entity/BaseEntity.java | 9 +- .../global/exception/CommonErrorCode.java | 2 +- .../global/exception/CustomErrorResponse.java | 2 +- .../global/exception/ErrorCode.java | 2 + .../exception/GlobalExceptionHandler.java | 10 +- .../global/presentation/FCMApi.java | 8 +- .../global/presentation/FCMController.java | 10 +- .../global/utils/CryptoUtils.java | 4 +- .../global/utils/LogMarkers.java | 53 ++++---- src/main/resources/logback-spring.xml | 10 +- .../TodaysoundServerApplicationTests.java | 6 +- .../presentation/SummaryControllerTest.java | 2 +- .../query/alarm/AlarmControllerTest.java | 106 ---------------- .../AlarmQueryControllerTest.java | 5 +- .../presentation/FeedQueryControllerTest.java | 26 ++-- .../SubscriptionControllerTest.java | 22 ++-- .../presentation/UrlQueryControllerTest.java | 6 +- .../support/DocumentationTestSupport.java | 8 +- .../support/RestDocsConfig.java | 6 +- .../restdocs/templates/request-fields.snippet | 14 +-- .../templates/response-fields.snippet | 12 +- 100 files changed, 596 insertions(+), 706 deletions(-) rename src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/{SummaryReadRequestDto.java => SummaryReadRequest.java} (60%) rename src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/{FeedResponseDTO.java => FeedResponse.java} (84%) rename src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/{SubscriptionCreateRequestDto.java => SubscriptionCreateRequest.java} (91%) rename src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/{InternalSubscriptionResponseDto.java => InternalSubscriptionResponse.java} (76%) rename src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/{KeywordListResponseDto.java => KeywordListResponse.java} (84%) rename src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/{SubscriptionCreationResponseDto.java => SubscriptionCreationResponse.java} (63%) rename src/main/java/com/todaysound/todaysound_server/domain/summary/service/{SummaryCommandService.java => SummaryService.java} (83%) rename src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/{UrlResponseDto.java => UrlResponse.java} (79%) rename src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/{FCMNotificationRequestDto.java => FCMNotificationRequest.java} (92%) rename src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/{UserSecretRequestDto.java => UserSecretRequest.java} (94%) create mode 100644 src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponse.java delete mode 100644 src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponseDto.java rename src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/{UserIdResponseDto.java => UserIdResponse.java} (64%) rename src/main/java/com/todaysound/todaysound_server/domain/user/service/{UserCommandService.java => UserService.java} (80%) delete mode 100644 src/test/java/com/todaysound/todaysound_server/query/alarm/AlarmControllerTest.java diff --git a/.github/ISSUE_TEMPLATE/chore.yml b/.github/ISSUE_TEMPLATE/chore.yml index 8a0b106..8fac28e 100644 --- a/.github/ISSUE_TEMPLATE/chore.yml +++ b/.github/ISSUE_TEMPLATE/chore.yml @@ -1,7 +1,7 @@ name: Chore Template description: 기타 사항 추가 시 쓰는 템플릿 title: "🚀 [Chore] " -labels: ["🚀chore"] +labels: [ "🚀chore" ] body: - type: input diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml index b9851b9..1d4247e 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -1,7 +1,7 @@ name: Docs Template description: 문서 추가 또는 수정을 위한 템플릿 title: "📖 [Docs] " -labels: ["📖docs"] +labels: [ "📖docs" ] body: - type: input diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 6ab971a..9da4040 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,7 +1,7 @@ name: Feature Template description: 기능 추가할 때 쓰는 템플릿 title: "✨ [Feat] " -labels: ["✨feature"] +labels: [ "✨feature" ] body: - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/fix.yml b/.github/ISSUE_TEMPLATE/fix.yml index 2fb4ca7..3152de6 100644 --- a/.github/ISSUE_TEMPLATE/fix.yml +++ b/.github/ISSUE_TEMPLATE/fix.yml @@ -1,7 +1,7 @@ name: Fix template description: 무언가를 수정할 때 쓰는 템플릿 title: "🐛 [Fix] " -labels: ["🐛bug"] +labels: [ "🐛bug" ] body: - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/refactor.yml b/.github/ISSUE_TEMPLATE/refactor.yml index bd52fe0..47bb533 100644 --- a/.github/ISSUE_TEMPLATE/refactor.yml +++ b/.github/ISSUE_TEMPLATE/refactor.yml @@ -1,7 +1,7 @@ name: Refactor Template description: 리팩토링할 때 쓰는 템플릿 title: "♻️ [Refactor] " -labels: ["♻️refactor"] +labels: [ "♻️refactor" ] body: - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/setting.yml b/.github/ISSUE_TEMPLATE/setting.yml index 1e4a013..b8c7214 100644 --- a/.github/ISSUE_TEMPLATE/setting.yml +++ b/.github/ISSUE_TEMPLATE/setting.yml @@ -1,7 +1,7 @@ name: Setting Template description: 환경 설정, 의존성 관리 등 세팅 관련 작업을 위한 템플릿 title: "⚙️ [Setting] " -labels: ["⚙️setting"] +labels: [ "⚙️setting" ] body: - type: input diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 374abc4..b0693e1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,9 @@ ## Related issue 🛠 + [//]: # (해당하는 이슈 번호 달아주기) 어떤 변경사항이 있었나요? + - [ ] 🐛 BugFix Something isn't working - [ ] 💻 CrossBrowsing Browser compatibility - [ ] 🌏 Deploy Deploy @@ -13,15 +15,19 @@ - [ ] ✅ Test Test related ## Work Description ✏️ + [//]: # (작업 내용 간단 소개) 작업 내용을 작성해주세요. + - 작업 내용 ## Uncompleted Tasks 😅 + [//]: # (없다면 N/A) - [ ] Task1 ## To Reviewers 📢 + [//]: # (reviewer가 알면 좋은 내용들) 리뷰어가 알면 좋은 내용을 작성해주세요. diff --git a/build.gradle b/build.gradle index 3bf1376..d2e94e8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,35 +1,35 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.5.6' - id 'io.spring.dependency-management' version '1.1.7' - id 'org.asciidoctor.jvm.convert' version '4.0.5' + id 'java' + id 'org.springframework.boot' version '3.5.6' + id 'io.spring.dependency-management' version '1.1.7' + id 'org.asciidoctor.jvm.convert' version '4.0.5' } group = 'com.todaysound' -version = '0.0.1-SNAPSHOT' +version = '0.0.1-SNAPSHOT' description = 'Demo project for Spring Boot' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } - asciidoctorExt + compileOnly { + extendsFrom annotationProcessor + } + asciidoctorExt } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' // TestContainer @@ -43,24 +43,24 @@ dependencies { // 구조화된 JSON 로깅 implementation 'net.logstash.logback:logstash-logback-encoder:8.0' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' // RestDocs asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' - // QueryDsl - implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // QueryDsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" // Firebase Admin SDK implementation 'com.google.firebase:firebase-admin:9.2.0' @@ -77,7 +77,7 @@ dependencies { } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } ext { // 전역 변수 diff --git a/src/main/java/com/todaysound/todaysound_server/TodaysoundServerApplication.java b/src/main/java/com/todaysound/todaysound_server/TodaysoundServerApplication.java index 68ea241..4dceb27 100644 --- a/src/main/java/com/todaysound/todaysound_server/TodaysoundServerApplication.java +++ b/src/main/java/com/todaysound/todaysound_server/TodaysoundServerApplication.java @@ -6,8 +6,8 @@ @SpringBootApplication public class TodaysoundServerApplication { - public static void main(String[] args) { - SpringApplication.run(TodaysoundServerApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(TodaysoundServerApplication.class, args); + } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmApi.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmApi.java index 2b7c416..f3cf76f 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmApi.java @@ -10,11 +10,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestHeader; -import java.util.List; - @Tag(name = "Alarm", description = "알람 조회 및 읽음 처리 API") public interface AlarmApi { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmController.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmController.java index 8a8d209..3079671 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmController.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java index f87f55a..a79337a 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/AlarmQueryController.java @@ -1,13 +1,15 @@ package com.todaysound.todaysound_server.domain.alarm.controller; -import java.util.List; - -import org.springframework.web.bind.annotation.*; import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; import com.todaysound.todaysound_server.domain.alarm.service.AlarmQueryService; -import com.todaysound.todaysound_server.domain.summary.service.SummaryCommandService; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/alarms") diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/InternalAlertController.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/InternalAlertController.java index 45fdadc..c84207e 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/InternalAlertController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/controller/InternalAlertController.java @@ -1,14 +1,14 @@ package com.todaysound.todaysound_server.domain.alarm.controller; -import com.todaysound.todaysound_server.domain.summary.entity.Summary; -import com.todaysound.todaysound_server.domain.summary.repository.SummaryRepository; +import com.fasterxml.jackson.annotation.JsonProperty; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.subscription.repository.SubscriptionRepository; +import com.todaysound.todaysound_server.domain.summary.entity.Summary; +import com.todaysound.todaysound_server.domain.summary.repository.SummaryRepository; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.global.application.FCMService; import com.todaysound.todaysound_server.global.exception.BaseException; import com.todaysound.todaysound_server.global.exception.CommonErrorCode; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,18 +17,9 @@ /** * 크롤러용 알림 생성 엔드포인트 - * - * POST /internal/alerts - * { - * "user_id": 10, - * "subscription_id": 1, - * "site_post_id": "12345", - * "title": "게시글 제목", - * "url": "https://...", - * "content_raw": "...원문...", - * "content_summary": "...요약...", - * "keyword_matched": true - * } + *

+ * POST /internal/alerts { "user_id": 10, "subscription_id": 1, "site_post_id": "12345", "title": "게시글 제목", "url": + * "https://...", "content_raw": "...원문...", "content_summary": "...요약...", "keyword_matched": true } */ @RestController @RequestMapping("/internal") @@ -54,7 +45,7 @@ public void createAlert(@RequestBody InternalAlertRequest request) { User user = subscription.getUser(); String prefix; - if(request.keywordMatched == true) { + if (request.keywordMatched == true) { prefix = "[" + request.siteAlias + "]"; } else { prefix = "[긴급/" + request.siteAlias + "]"; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequestDto.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequest.java similarity index 60% rename from src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequestDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequest.java index b53ea2e..db4c421 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequestDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/request/SummaryReadRequest.java @@ -2,6 +2,6 @@ import java.util.List; -public record SummaryReadRequestDto(List summaryIds) { +public record SummaryReadRequest(List summaryIds) { } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/response/UnreadAlarmResponse.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/response/UnreadAlarmResponse.java index d20c80a..a171a12 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/response/UnreadAlarmResponse.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/dto/response/UnreadAlarmResponse.java @@ -1,15 +1,14 @@ package com.todaysound.todaysound_server.domain.alarm.dto.response; -import java.time.LocalDateTime; -import java.util.List; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.utils.TimeUtil; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.List; /** - * 메인화면용 읽지 않은 알람 응답 DTO - * - 읽지 않은 Summary만 포함 + * 메인화면용 읽지 않은 알람 응답 DTO - 읽지 않은 Summary만 포함 */ public record UnreadAlarmResponse( @Schema(description = "구독 ID", example = "1") diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/exception/AlarmException.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/exception/AlarmException.java index 44ddd17..5db5c5d 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/exception/AlarmException.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/exception/AlarmException.java @@ -9,7 +9,8 @@ @AllArgsConstructor public enum AlarmException implements ErrorCode { - ALARM_NOT_FOUND(HttpStatus.NOT_FOUND, "ALARM401_1", "유효하지 않은 ALARM ID 입니다."),; + ALARM_NOT_FOUND(HttpStatus.NOT_FOUND, "ALARM401_1", "유효하지 않은 ALARM ID 입니다."), + ; private final HttpStatus status; private final String errorCode; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepository.java index 1e9849b..6cd0d61 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepository.java @@ -1,9 +1,9 @@ package com.todaysound.todaysound_server.domain.alarm.repository; -import java.util.List; import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; public interface AlarmDynamicRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepositoryImpl.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepositoryImpl.java index a2edfa7..c095b80 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepositoryImpl.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmDynamicRepositoryImpl.java @@ -1,15 +1,14 @@ package com.todaysound.todaysound_server.domain.alarm.repository; -import java.util.List; +import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; +import static com.todaysound.todaysound_server.domain.summary.entity.QSummary.summary; -import com.todaysound.todaysound_server.domain.summary.entity.Summary; -import org.springframework.stereotype.Repository; import com.querydsl.jpa.impl.JPAQueryFactory; +import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; import lombok.RequiredArgsConstructor; - -import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; -import static com.todaysound.todaysound_server.domain.summary.entity.QSummary.summary; +import org.springframework.stereotype.Repository; @Repository diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmRepository.java index 8cf92cd..87ab2e7 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/repository/AlarmRepository.java @@ -1,8 +1,7 @@ package com.todaysound.todaysound_server.domain.alarm.repository; -import org.springframework.data.jpa.repository.JpaRepository; - import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; +import org.springframework.data.jpa.repository.JpaRepository; public interface AlarmRepository extends JpaRepository, AlarmDynamicRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/alarm/service/AlarmQueryService.java b/src/main/java/com/todaysound/todaysound_server/domain/alarm/service/AlarmQueryService.java index e25f458..21949ef 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/alarm/service/AlarmQueryService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/alarm/service/AlarmQueryService.java @@ -1,15 +1,14 @@ package com.todaysound.todaysound_server.domain.alarm.service; -import java.util.List; - +import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; +import com.todaysound.todaysound_server.domain.alarm.repository.AlarmRepository; import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; -import org.springframework.stereotype.Service; -import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; -import com.todaysound.todaysound_server.domain.alarm.repository.AlarmRepository; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedApi.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedApi.java index 23d2c80..e3f51c0 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedApi.java @@ -1,31 +1,30 @@ package com.todaysound.todaysound_server.domain.feed.controller; -import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponseDTO; +import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponse; import com.todaysound.todaysound_server.domain.feed.dto.response.HomeFeedResponse; import com.todaysound.todaysound_server.global.dto.PageRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestHeader; -import java.util.List; - @Tag(name = "Feed", description = "피드 조회 API") public interface FeedApi { - @Operation(summary = "피드 조회", description = """ - 사용자의 읽지 않은 요약(Summary) 중 알람이 활성화된 구독의 요약들인 피드를 - 페이지네이션하여 조회합니다. - """, tags = {"Feed"}, operationId = "getFeeds") - List findFeeds(@ModelAttribute PageRequest pageRequest, - @RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret); + @Operation(summary = "피드 조회", description = """ + 사용자의 읽지 않은 요약(Summary) 중 알람이 활성화된 구독의 요약들인 피드를 + 페이지네이션하여 조회합니다. + """, tags = {"Feed"}, operationId = "getFeeds") + List findFeeds(@ModelAttribute PageRequest pageRequest, + @RequestHeader("X-User-ID") String userUuid, + @RequestHeader("X-Device-Secret") String deviceSecret); - @Operation(summary = "홈화면 피드 조회", description = """ - 사용자의 읽지 않은 요약(Summary) 중 알람이 활성화된 구독의 요약들인 피드를 - 페이징 없이 전체 조회합니다. - """, tags = {"Feed"}, operationId = "getFeedsForHome") - List findFeedsForHome(@RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret); + @Operation(summary = "홈화면 피드 조회", description = """ + 사용자의 읽지 않은 요약(Summary) 중 알람이 활성화된 구독의 요약들인 피드를 + 페이징 없이 전체 조회합니다. + """, tags = {"Feed"}, operationId = "getFeedsForHome") + List findFeedsForHome(@RequestHeader("X-User-ID") String userUuid, + @RequestHeader("X-Device-Secret") String deviceSecret); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedController.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedController.java index 8169e9f..506e428 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/controller/FeedController.java @@ -1,16 +1,16 @@ package com.todaysound.todaysound_server.domain.feed.controller; +import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponse; +import com.todaysound.todaysound_server.domain.feed.dto.response.HomeFeedResponse; +import com.todaysound.todaysound_server.domain.feed.service.FeedQueryService; +import com.todaysound.todaysound_server.global.dto.PageRequest; import java.util.List; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponseDTO; -import com.todaysound.todaysound_server.domain.feed.dto.response.HomeFeedResponse; -import com.todaysound.todaysound_server.domain.feed.service.FeedQueryService; -import com.todaysound.todaysound_server.global.dto.PageRequest; -import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/api/feeds") @@ -21,9 +21,9 @@ public class FeedController implements FeedApi { @Override @GetMapping() - public List findFeeds(@ModelAttribute final PageRequest pageRequest, - @RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret) { + public List findFeeds(@ModelAttribute final PageRequest pageRequest, + @RequestHeader("X-User-ID") String userUuid, + @RequestHeader("X-Device-Secret") String deviceSecret) { return feedQueryService.findFeeds(userUuid, deviceSecret, pageRequest); } @@ -31,7 +31,7 @@ public List findFeeds(@ModelAttribute final PageRequest pageReq @Override @GetMapping("/home") public List findFeedsForHome(@RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret) { + @RequestHeader("X-Device-Secret") String deviceSecret) { return feedQueryService.findFeedsForHome(userUuid, deviceSecret); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponseDTO.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponse.java similarity index 84% rename from src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponseDTO.java rename to src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponse.java index d6f7f84..207d88d 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponseDTO.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/dto/response/FeedResponse.java @@ -3,7 +3,7 @@ import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.utils.TimeUtil; -public record FeedResponseDTO( +public record FeedResponse( Long subscriptionId, String alias, String summaryTitle, @@ -11,8 +11,8 @@ public record FeedResponseDTO( String postUrl, String timeAgo ) { - public static FeedResponseDTO of(Summary summary) { - return new FeedResponseDTO( + public static FeedResponse of(Summary summary) { + return new FeedResponse( summary.getSubscription().getId(), summary.getSubscription().getAlias(), summary.getTitle(), diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepository.java index cedee90..189b85e 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepository.java @@ -1,10 +1,8 @@ package com.todaysound.todaysound_server.domain.feed.repository; -import java.util.List; - import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.dto.PageRequest; - +import java.util.List; public interface FeedDynamicRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepositoryImpl.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepositoryImpl.java index b863829..dad0bf5 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepositoryImpl.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/repository/FeedDynamicRepositoryImpl.java @@ -1,14 +1,14 @@ package com.todaysound.todaysound_server.domain.feed.repository; -import java.util.List; -import org.springframework.stereotype.Repository; +import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; +import static com.todaysound.todaysound_server.domain.summary.entity.QSummary.summary; + import com.querydsl.jpa.impl.JPAQueryFactory; import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; import lombok.RequiredArgsConstructor; - -import static com.todaysound.todaysound_server.domain.summary.entity.QSummary.summary; -import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; +import org.springframework.stereotype.Repository; @Repository diff --git a/src/main/java/com/todaysound/todaysound_server/domain/feed/service/FeedQueryService.java b/src/main/java/com/todaysound/todaysound_server/domain/feed/service/FeedQueryService.java index 24a9241..c6fc5bd 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/feed/service/FeedQueryService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/feed/service/FeedQueryService.java @@ -1,18 +1,16 @@ package com.todaysound.todaysound_server.domain.feed.service; -import java.util.List; -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; - -import org.springframework.transaction.annotation.Transactional; -import com.todaysound.todaysound_server.global.dto.PageRequest; -import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponseDTO; +import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponse; import com.todaysound.todaysound_server.domain.feed.dto.response.HomeFeedResponse; import com.todaysound.todaysound_server.domain.feed.repository.FeedDynamicRepository; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; +import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -22,13 +20,13 @@ public class FeedQueryService { private final FeedDynamicRepository feedDynamicRepository; private final HeaderAuthValidator headerAuthValidator; - public List findFeeds(final String userUuid, final String deviceSecret, - final PageRequest pageRequest) { + public List findFeeds(final String userUuid, final String deviceSecret, + final PageRequest pageRequest) { User user = headerAuthValidator.validateAndGetUser(userUuid, deviceSecret); return feedDynamicRepository.findFeeds(user.getId(), pageRequest).stream() - .map(FeedResponseDTO::of).toList(); + .map(FeedResponse::of).toList(); } public List findFeedsForHome(final String userUuid, final String deviceSecret) { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionApi.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionApi.java index 2bf6969..0d88627 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionApi.java @@ -1,6 +1,6 @@ package com.todaysound.todaysound_server.domain.subscription.controller; -import com.todaysound.todaysound_server.domain.subscription.dto.response.InternalSubscriptionResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.InternalSubscriptionResponse; import com.todaysound.todaysound_server.global.exception.CustomErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -9,11 +9,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; -import java.util.List; - @Tag(name = "InternalSubscription", description = "크롤러용 구독 조회 및 상태 업데이트 내부 API") public interface InternalSubscriptionApi { @@ -32,7 +31,7 @@ public interface InternalSubscriptionApi { description = "구독 정보 조회 성공", content = @Content( mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = InternalSubscriptionResponseDto.class)), + array = @ArraySchema(schema = @Schema(implementation = InternalSubscriptionResponse.class)), examples = @ExampleObject( name = "구독 목록 예시", value = """ @@ -69,7 +68,7 @@ public interface InternalSubscriptionApi { ) ) }) - List getSubscriptions(); + List getSubscriptions(); @Operation( summary = "마지막으로 본 게시글 ID 업데이트 (크롤러용)", diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionController.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionController.java index 07fc334..3e3a9f5 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/InternalSubscriptionController.java @@ -1,10 +1,11 @@ package com.todaysound.todaysound_server.domain.subscription.controller; -import com.todaysound.todaysound_server.domain.subscription.dto.response.InternalSubscriptionResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.InternalSubscriptionResponse; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.subscription.repository.SubscriptionRepository; import com.todaysound.todaysound_server.global.exception.BaseException; import com.todaysound.todaysound_server.global.exception.CommonErrorCode; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; @@ -14,8 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/internal") @RequiredArgsConstructor @@ -27,10 +26,10 @@ public class InternalSubscriptionController implements InternalSubscriptionApi { * 크롤러용: 모든 구독 정보를 단순 JSON 형태로 반환 */ @GetMapping("/subscriptions") - public List getSubscriptions() { + public List getSubscriptions() { List subscriptions = subscriptionRepository.findAll(); return subscriptions.stream() - .map(InternalSubscriptionResponseDto::from) + .map(InternalSubscriptionResponse::from) .toList(); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionApi.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionApi.java index b7de8fa..11003b5 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionApi.java @@ -1,8 +1,8 @@ package com.todaysound.todaysound_server.domain.subscription.controller; -import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequestDto; -import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; -import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequest; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponse; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponse; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; import com.todaysound.todaysound_server.global.dto.PageRequest; import com.todaysound.todaysound_server.global.exception.CustomErrorResponse; @@ -15,13 +15,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.List; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; -import java.util.List; - @Tag(name = "Subscription", description = "사용자 구독 관리 API") public interface SubscriptionApi { @@ -192,8 +191,8 @@ void deleteSubscription( ) ) }) - SubscriptionCreationResponseDto createSubscription( - @RequestBody @Valid SubscriptionCreateRequestDto subscriptionCreateRequestDto, + SubscriptionCreationResponse createSubscription( + @RequestBody @Valid SubscriptionCreateRequest subscriptionCreateRequest, @RequestHeader("X-User-ID") String userUuid, @RequestHeader("X-Device-Secret") String deviceSecret ); @@ -212,11 +211,11 @@ SubscriptionCreationResponseDto createSubscription( description = "키워드 목록 조회 성공", content = @Content( mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = KeywordListResponseDto.class)) + array = @ArraySchema(schema = @Schema(implementation = KeywordListResponse.class)) ) ) }) - KeywordListResponseDto getAllKeywords(); + KeywordListResponse getAllKeywords(); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java index d5f1952..f67ec94 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/controller/SubscriptionController.java @@ -1,21 +1,28 @@ package com.todaysound.todaysound_server.domain.subscription.controller; -import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequestDto; +import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequest; import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionUpdateRequest; -import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; -import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponse; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponse; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; -import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionQueryService; +import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; import com.todaysound.todaysound_server.global.dto.PageRequest; - import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - - -import java.util.List; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/subscriptions") @@ -23,7 +30,7 @@ public class SubscriptionController implements SubscriptionApi { private final SubscriptionQueryService subscriptionQueryService; - private final SubscriptionService subscriptionCommandService; + private final SubscriptionService subscriptionService; // 사용자의 구독을 페이지네이션(한 페이지 size 만큼)해서 가져옴. @GetMapping() @@ -38,21 +45,21 @@ public List getMySubscriptions(@ModelAttribute final PageR public void deleteSubscription(@PathVariable Long subscriptionId, @RequestHeader("X-User-ID") String userUuid, @RequestHeader("X-Device-Secret") String deviceSecret) { - subscriptionCommandService.deleteSubscription(subscriptionId, userUuid, deviceSecret); + subscriptionService.deleteSubscription(subscriptionId, userUuid, deviceSecret); } @PostMapping() - public SubscriptionCreationResponseDto createSubscription( - @RequestBody @Valid SubscriptionCreateRequestDto subscriptionCreateRequestDto, + public SubscriptionCreationResponse createSubscription( + @RequestBody @Valid SubscriptionCreateRequest subscriptionCreateRequest, @RequestHeader("X-User-ID") String userUuid, @RequestHeader("X-Device-Secret") String deviceSecret) { - return subscriptionCommandService.createSubscription(userUuid, deviceSecret, subscriptionCreateRequestDto); + return subscriptionService.createSubscription(userUuid, deviceSecret, subscriptionCreateRequest); } /** * 저장된 모든 키워드 목록 조회 */ @GetMapping("/keywords") - public KeywordListResponseDto getAllKeywords() { + public KeywordListResponse getAllKeywords() { return subscriptionQueryService.getAllKeywords(); } @@ -62,6 +69,6 @@ public void updateSubscription(@PathVariable Long subscriptionId, @RequestBody @Valid SubscriptionUpdateRequest request, @RequestHeader("X-User-ID") String userUuid, @RequestHeader("X-Device-Secret") String deviceSecret) { - subscriptionCommandService.updateSubscription(subscriptionId, userUuid, deviceSecret, request); + subscriptionService.updateSubscription(subscriptionId, userUuid, deviceSecret, request); } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequestDto.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequest.java similarity index 91% rename from src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequestDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequest.java index 8bdf9dc..08b3f63 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequestDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/request/SubscriptionCreateRequest.java @@ -1,13 +1,12 @@ package com.todaysound.todaysound_server.domain.subscription.dto.request; import jakarta.validation.constraints.NotNull; - import java.util.List; /** * 구독 생성 요청 DTO - 헤더(X-User-ID, X-Device-Secret)는 컨트롤러의 @RequestHeader로 받고, 본 DTO는 바디의 필드만 검증/바인딩합니다. */ -public record SubscriptionCreateRequestDto( +public record SubscriptionCreateRequest( @NotNull(message = "URL ID는 필수입니다.") Long urlId, diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponse.java similarity index 76% rename from src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponseDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponse.java index 431c858..636b89d 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponseDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/InternalSubscriptionResponse.java @@ -2,24 +2,15 @@ import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.subscription.entity.SubscriptionKeyword; - import java.util.List; /** * 크롤러 전용 구독 응답 DTO - * - * [ - * { - * "id": 1, - * "user_id": 10, - * "site_url": "https://sw.dongguk.edu/board/list.do?id=S181", - * "site_alias": "동국대 SW공지", - * "keyword": "장학", - * "last_seen_post_id": "12345" - * } - * ] + *

+ * [ { "id": 1, "user_id": 10, "site_url": "https://sw.dongguk.edu/board/list.do?id=S181", "site_alias": "동국대 SW공지", + * "keyword": "장학", "last_seen_post_id": "12345" } ] */ -public record InternalSubscriptionResponseDto( +public record InternalSubscriptionResponse( Long id, Long user_id, String site_url, @@ -28,7 +19,7 @@ public record InternalSubscriptionResponseDto( String last_seen_post_id ) { - public static InternalSubscriptionResponseDto from(Subscription subscription) { + public static InternalSubscriptionResponse from(Subscription subscription) { String keyword = extractFirstKeyword(subscription.getSubscriptionKeywords()); // 빈 문자열은 "아직 본 적 없음" 이므로 null 로 내려서 크롤러에서 None 으로 처리되게 함 @@ -37,7 +28,7 @@ public static InternalSubscriptionResponseDto from(Subscription subscription) { lastSeenPostId = null; } - return new InternalSubscriptionResponseDto( + return new InternalSubscriptionResponse( subscription.getId(), subscription.getUser().getId(), subscription.getUrl().getLink(), diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponse.java similarity index 84% rename from src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponseDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponse.java index b60eb06..6fc3cf7 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponseDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/KeywordListResponse.java @@ -2,21 +2,20 @@ import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; import io.swagger.v3.oas.annotations.media.Schema; - import java.util.List; /** * 키워드 목록 조회 응답 DTO */ -public record KeywordListResponseDto( +public record KeywordListResponse( @Schema(description = "키워드 목록") List keywords ) { - public static KeywordListResponseDto from(List keywords) { + public static KeywordListResponse from(List keywords) { List items = keywords.stream() .map(KeywordItem::from) .toList(); - return new KeywordListResponseDto(items); + return new KeywordListResponse(items); } public record KeywordItem( diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponse.java similarity index 63% rename from src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponseDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponse.java index cc845e1..f67b331 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponseDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionCreationResponse.java @@ -5,12 +5,12 @@ import lombok.Builder; @Builder -public record SubscriptionCreationResponseDto( +public record SubscriptionCreationResponse( @Schema(description = "생성된 구독 ID", example = "123") Long subscriptionId ) { - public static SubscriptionCreationResponseDto from(Subscription subscription) { - return new SubscriptionCreationResponseDto(subscription.getId()); + public static SubscriptionCreationResponse from(Subscription subscription) { + return new SubscriptionCreationResponse(subscription.getId()); } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionResponse.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionResponse.java index 6028d9a..59af626 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionResponse.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/dto/response/SubscriptionResponse.java @@ -2,7 +2,6 @@ import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; - import java.util.List; public record SubscriptionResponse( diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Keyword.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Keyword.java index 2cb12b5..026ea1c 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Keyword.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Keyword.java @@ -1,16 +1,19 @@ package com.todaysound.todaysound_server.domain.subscription.entity; import com.todaysound.todaysound_server.global.entity.BaseEntity; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @Builder diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Subscription.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Subscription.java index 8f01d81..52a9dda 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Subscription.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/Subscription.java @@ -1,10 +1,20 @@ package com.todaysound.todaysound_server.domain.subscription.entity; +import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.domain.url.entity.Url; import com.todaysound.todaysound_server.domain.user.entity.User; -import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.global.entity.BaseEntity; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -15,10 +25,6 @@ import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.UpdateTimestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @Builder diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/SubscriptionKeyword.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/SubscriptionKeyword.java index ba781dd..242786a 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/SubscriptionKeyword.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/entity/SubscriptionKeyword.java @@ -8,7 +8,6 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/KeywordException.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/KeywordException.java index fef5eaf..6d356d7 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/KeywordException.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/KeywordException.java @@ -7,14 +7,13 @@ @Getter @AllArgsConstructor -public enum KeywordException implements ErrorCode{ +public enum KeywordException implements ErrorCode { - KEYWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "KEYWORD404_1", "유효하지 않은 KEYWORD ID 입니다."),; + KEYWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "KEYWORD404_1", "유효하지 않은 KEYWORD ID 입니다."), + ; private final HttpStatus status; private final String errorCode; private final String message; } - -//그리고 지금 종버튼에 펜 버튼을 만들어서 수정 페이지로 넘어갈 수 있게 해줄래 UI는 Create페이지랑 같지만 url은 재설정할 수 없고 별칭이랑 keyword랑 알람 받을지만 설정할 수 있어 아 일단 이 전에 현재 긴급 알람인지 \ No newline at end of file diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/SubscriptionException.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/SubscriptionException.java index 1ebceb3..e5f8318 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/SubscriptionException.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/exception/SubscriptionException.java @@ -1,18 +1,18 @@ package com.todaysound.todaysound_server.domain.subscription.exception; -import org.springframework.http.HttpStatus; import com.todaysound.todaysound_server.global.exception.ErrorCode; import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.http.HttpStatus; @Getter @AllArgsConstructor public enum SubscriptionException implements ErrorCode { - SUBSCRIPTION_NOT_PERMISSION(HttpStatus.UNAUTHORIZED, "SUBSCRIPTION401_1","해당 구독을 삭제할 권한이 없습니다."), - SUBSCRIPTION_NOT_FOUND(HttpStatus.NOT_FOUND,"SUBSCRIPTION404_1", "해당 구독을 찾을 수 없습니다."), - SUBSCRIPTION_INVALID_URL(HttpStatus.BAD_REQUEST,"SUBSCRIPTION400_1", "유효하지 않은 URL입니다."), - SUBSCRIPTION_ALREADY_EXISTS(HttpStatus.CONFLICT,"SUBSCRIPTION409_1", "이미 존재하는 구독 URL입니다."); + SUBSCRIPTION_NOT_PERMISSION(HttpStatus.UNAUTHORIZED, "SUBSCRIPTION401_1", "해당 구독을 삭제할 권한이 없습니다."), + SUBSCRIPTION_NOT_FOUND(HttpStatus.NOT_FOUND, "SUBSCRIPTION404_1", "해당 구독을 찾을 수 없습니다."), + SUBSCRIPTION_INVALID_URL(HttpStatus.BAD_REQUEST, "SUBSCRIPTION400_1", "유효하지 않은 URL입니다."), + SUBSCRIPTION_ALREADY_EXISTS(HttpStatus.CONFLICT, "SUBSCRIPTION409_1", "이미 존재하는 구독 URL입니다."); private final HttpStatus status; private final String errorCode; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/factory/SubscriptionFactory.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/factory/SubscriptionFactory.java index 38e3958..e102a80 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/factory/SubscriptionFactory.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/factory/SubscriptionFactory.java @@ -9,13 +9,12 @@ import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.global.exception.BaseException; import com.todaysound.todaysound_server.global.exception.CommonErrorCode; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.List; - @Component @Slf4j @RequiredArgsConstructor @@ -28,7 +27,7 @@ public class SubscriptionFactory { * 구독 생성 */ public Subscription create(User user, Long urlId, List keywordIds, String alias, - boolean isAlarmEnabled) { + boolean isAlarmEnabled) { log.debug("구독 생성 시작: user={}, urlId={}, keywordIds={}", user.getUserId(), urlId, keywordIds); @@ -60,7 +59,7 @@ public Subscription create(User user, Long urlId, List keywordIds, String * 키워드 ID 리스트로 SubscriptionKeyword 생성 */ private List createSubscriptionKeywordsFromIds(Subscription subscription, - List keywordIds) { + List keywordIds) { // 키워드 ID로 키워드 조회 List keywords = keywordRepository.findAllById(keywordIds); diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/KeywordRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/KeywordRepository.java index dc843cf..1f36a63 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/KeywordRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/KeywordRepository.java @@ -1,12 +1,10 @@ package com.todaysound.todaysound_server.domain.subscription.repository; import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; -import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface KeywordRepository extends JpaRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepository.java index 9abb34d..adf208d 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepository.java @@ -1,9 +1,8 @@ package com.todaysound.todaysound_server.domain.subscription.repository; -import java.util.List; - import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; +import java.util.List; public interface SubscriptionDynamicRepository { - List findByUserId(Long userId, Long cursor, Integer size); + List findByUserId(Long userId, Long cursor, Integer size); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepositoryImpl.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepositoryImpl.java index 3376179..2cc4a8c 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepositoryImpl.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionDynamicRepositoryImpl.java @@ -1,28 +1,23 @@ package com.todaysound.todaysound_server.domain.subscription.repository; -import java.time.LocalDateTime; -import java.util.List; - -import org.springframework.stereotype.Repository; +import static com.todaysound.todaysound_server.domain.subscription.entity.QKeyword.keyword; +import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; +import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscriptionKeyword.subscriptionKeyword; -import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; - +import java.util.List; import lombok.RequiredArgsConstructor; - -import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscription.subscription; -import static com.todaysound.todaysound_server.domain.subscription.entity.QKeyword.keyword; -import static com.todaysound.todaysound_server.domain.subscription.entity.QSubscriptionKeyword.subscriptionKeyword; +import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor public class SubscriptionDynamicRepositoryImpl implements SubscriptionDynamicRepository { - private final JPAQueryFactory queryFactory; + private final JPAQueryFactory queryFactory; - @Override - public List findByUserId(Long userId, Long page, Integer size) { + @Override + public List findByUserId(Long userId, Long page, Integer size) { return queryFactory .selectFrom(subscription) @@ -38,6 +33,6 @@ public List findByUserId(Long userId, Long page, Integer size) { .limit(size) .fetch(); - } + } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionKeywordRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionKeywordRepository.java index 23f94c5..ee7ff4c 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionKeywordRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionKeywordRepository.java @@ -1,6 +1,5 @@ package com.todaysound.todaysound_server.domain.subscription.repository; -import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.subscription.entity.SubscriptionKeyword; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionRepository.java index de7c8f6..3286cb3 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/repository/SubscriptionRepository.java @@ -1,15 +1,12 @@ package com.todaysound.todaysound_server.domain.subscription.repository; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; -import com.todaysound.todaysound_server.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface SubscriptionRepository extends JpaRepository, SubscriptionDynamicRepository { // boolean existsByUserAndUrl(User user, String url); - + // Optional findByUserAndUrl(User user, String url); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionQueryService.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionQueryService.java index 19a5a2e..5a6acff 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionQueryService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionQueryService.java @@ -1,9 +1,6 @@ package com.todaysound.todaysound_server.domain.subscription.service; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponse; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; @@ -12,41 +9,44 @@ import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; import com.todaysound.todaysound_server.global.dto.PageRequest; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class SubscriptionQueryService { - private final SubscriptionRepository subscriptionRepository; - private final KeywordRepository keywordRepository; - private final HeaderAuthValidator headerAuthValidator; + private final SubscriptionRepository subscriptionRepository; + private final KeywordRepository keywordRepository; + private final HeaderAuthValidator headerAuthValidator; - public List getMySubscriptions(final PageRequest pageRequest, - final String userUuid, final String deviceSecret) { + public List getMySubscriptions(final PageRequest pageRequest, + final String userUuid, final String deviceSecret) { - // 헤더 인증 검증 및 사용자 획득 - User user = headerAuthValidator.validateAndGetUser(userUuid, deviceSecret); + // 헤더 인증 검증 및 사용자 획득 + User user = headerAuthValidator.validateAndGetUser(userUuid, deviceSecret); - List mySubscriptions = subscriptionRepository.findByUserId(user.getId(), - pageRequest.page(), pageRequest.size()); + List mySubscriptions = subscriptionRepository.findByUserId(user.getId(), + pageRequest.page(), pageRequest.size()); - return mySubscriptions.stream().map(subscription -> SubscriptionResponse.of( - subscription, - subscription.getSubscriptionKeywords().stream() - .map(subscriptionKeyword -> subscriptionKeyword - .getKeyword()) - .toList())) - .toList(); + return mySubscriptions.stream().map(subscription -> SubscriptionResponse.of( + subscription, + subscription.getSubscriptionKeywords().stream() + .map(subscriptionKeyword -> subscriptionKeyword + .getKeyword()) + .toList())) + .toList(); - } + } - /** - * 저장된 모든 키워드 목록 조회 - */ - public KeywordListResponseDto getAllKeywords() { - List keywords = keywordRepository.findAll(); - return KeywordListResponseDto.from(keywords); - } + /** + * 저장된 모든 키워드 목록 조회 + */ + public KeywordListResponse getAllKeywords() { + List keywords = keywordRepository.findAll(); + return KeywordListResponse.from(keywords); + } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java index f2137ca..46bc2af 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/subscription/service/SubscriptionService.java @@ -1,22 +1,22 @@ package com.todaysound.todaysound_server.domain.subscription.service; +import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequest; import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionUpdateRequest; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponse; import com.todaysound.todaysound_server.domain.subscription.entity.Keyword; -import com.todaysound.todaysound_server.domain.subscription.exception.KeywordException; -import com.todaysound.todaysound_server.domain.subscription.repository.KeywordRepository; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; +import com.todaysound.todaysound_server.domain.subscription.exception.KeywordException; import com.todaysound.todaysound_server.domain.subscription.exception.SubscriptionException; -import com.todaysound.todaysound_server.domain.subscription.repository.SubscriptionRepository; -import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequestDto; -import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; import com.todaysound.todaysound_server.domain.subscription.factory.SubscriptionFactory; +import com.todaysound.todaysound_server.domain.subscription.repository.KeywordRepository; +import com.todaysound.todaysound_server.domain.subscription.repository.SubscriptionRepository; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; import com.todaysound.todaysound_server.global.exception.BaseException; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @Transactional @@ -43,9 +43,9 @@ public void deleteSubscription(final Long subscriptionId, final String userUuid, subscriptionRepository.deleteById(subscriptionId); } - public SubscriptionCreationResponseDto createSubscription(final String headerUserUuid, - final String headerDeviceSecret, - final SubscriptionCreateRequestDto requestDto) { + public SubscriptionCreationResponse createSubscription(final String headerUserUuid, + final String headerDeviceSecret, + final SubscriptionCreateRequest requestDto) { // 헤더 인증 검증 및 사용자 획득 User user = headerAuthValidator.validateAndGetUser(headerUserUuid, headerDeviceSecret); @@ -58,10 +58,11 @@ public SubscriptionCreationResponseDto createSubscription(final String headerUse requestDto.isAlarmEnabled() ); Subscription savedSubscription = subscriptionRepository.save(subscription); - return SubscriptionCreationResponseDto.from(savedSubscription); + return SubscriptionCreationResponse.from(savedSubscription); } - public void updateSubscription(Long subscriptionId, String userUuid, String deviceSecret, SubscriptionUpdateRequest request) { + public void updateSubscription(Long subscriptionId, String userUuid, String deviceSecret, + SubscriptionUpdateRequest request) { // 헤더 인증 검증 및 사용자 획득 User user = headerAuthValidator.validateAndGetUser(userUuid, deviceSecret); diff --git a/src/main/java/com/todaysound/todaysound_server/domain/summary/controller/SummaryController.java b/src/main/java/com/todaysound/todaysound_server/domain/summary/controller/SummaryController.java index c46a109..313a0f3 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/summary/controller/SummaryController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/summary/controller/SummaryController.java @@ -1,6 +1,6 @@ package com.todaysound.todaysound_server.domain.summary.controller; -import com.todaysound.todaysound_server.domain.summary.service.SummaryCommandService; +import com.todaysound.todaysound_server.domain.summary.service.SummaryService; import com.todaysound.todaysound_server.global.exception.CustomErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -8,7 +8,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @Tag(name = "Summary", description = "요약 관리 API") @RestController @@ -16,7 +20,7 @@ @RequiredArgsConstructor public class SummaryController { - private final SummaryCommandService summaryCommandService; + private final SummaryService summaryService; @Operation( summary = "요약 삭제", @@ -51,11 +55,11 @@ public class SummaryController { }) @DeleteMapping("/{summaryId}") public void deleteSummary( - @RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret, - @PathVariable Long summaryId + @RequestHeader("X-User-ID") String userUuid, + @RequestHeader("X-Device-Secret") String deviceSecret, + @PathVariable Long summaryId ) { - summaryCommandService.deleteSummary(userUuid, deviceSecret, summaryId); + summaryService.deleteSummary(userUuid, deviceSecret, summaryId); } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/summary/entity/Summary.java b/src/main/java/com/todaysound/todaysound_server/domain/summary/entity/Summary.java index 4372640..0d81086 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/summary/entity/Summary.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/summary/entity/Summary.java @@ -8,12 +8,11 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Getter @Table(name = "summaries") @@ -50,8 +49,8 @@ public class Summary extends BaseEntity { // Summary 생성 팩토리 메서드 public static Summary create(String hash, String title, String content, - String postUrl, String postDate, boolean isKeywordMatched, - Subscription subscription) { + String postUrl, String postDate, boolean isKeywordMatched, + Subscription subscription) { Summary summary = new Summary(); summary.hash = hash; summary.title = title; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/summary/exception/SummaryException.java b/src/main/java/com/todaysound/todaysound_server/domain/summary/exception/SummaryException.java index 8f0491d..73f4d79 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/summary/exception/SummaryException.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/summary/exception/SummaryException.java @@ -10,7 +10,8 @@ public enum SummaryException implements ErrorCode { - SUMMARY_NOT_FOUND(HttpStatus.NOT_FOUND, "SUMMARY401_1", "유효하지 않은 SUMMARY ID 입니다."),; + SUMMARY_NOT_FOUND(HttpStatus.NOT_FOUND, "SUMMARY401_1", "유효하지 않은 SUMMARY ID 입니다."), + ; private final HttpStatus status; private final String errorCode; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/summary/repository/SummaryRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/summary/repository/SummaryRepository.java index 6844be2..df768cb 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/summary/repository/SummaryRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/summary/repository/SummaryRepository.java @@ -1,13 +1,10 @@ package com.todaysound.todaysound_server.domain.summary.repository; import com.todaysound.todaysound_server.domain.summary.entity.Summary; -import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; - @Repository public interface SummaryRepository extends JpaRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryCommandService.java b/src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryService.java similarity index 83% rename from src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryCommandService.java rename to src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryService.java index 72d86bd..c026da4 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryCommandService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/summary/service/SummaryService.java @@ -1,28 +1,23 @@ package com.todaysound.todaysound_server.domain.summary.service; -import com.todaysound.todaysound_server.domain.alarm.dto.request.SummaryReadRequestDto; +import static com.todaysound.todaysound_server.global.utils.LogMarkers.BUSINESS; + import com.todaysound.todaysound_server.domain.summary.entity.Summary; import com.todaysound.todaysound_server.domain.summary.exception.SummaryException; import com.todaysound.todaysound_server.domain.summary.repository.SummaryRepository; -import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; import com.todaysound.todaysound_server.global.exception.BaseException; -import com.todaysound.todaysound_server.global.exception.CommonErrorCode; -import com.todaysound.todaysound_server.global.utils.LogMarkers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.todaysound.todaysound_server.global.utils.LogMarkers.*; - @Slf4j @Service @RequiredArgsConstructor @Transactional -public class SummaryCommandService { +public class SummaryService { private final SummaryRepository summaryRepository; private final HeaderAuthValidator headerAuthValidator; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlApi.java b/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlApi.java index 2ed571e..c1cd989 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlApi.java @@ -1,6 +1,6 @@ package com.todaysound.todaysound_server.domain.url.controller; -import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponseDto; +import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponse; import com.todaysound.todaysound_server.global.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; - import java.util.List; @Tag(name = "Url", description = "URL 관리 API") @@ -30,7 +29,7 @@ public interface UrlApi { content = @Content( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), - array = @ArraySchema(schema = @Schema(implementation = UrlResponseDto.class)), + array = @ArraySchema(schema = @Schema(implementation = UrlResponse.class)), examples = @ExampleObject( name = "URL 목록 조회 성공 예시", value = """ @@ -50,6 +49,6 @@ public interface UrlApi { ) ) }) - List getUrls(); + List getUrls(); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlController.java b/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlController.java index 0df440f..8a81fcc 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/url/controller/UrlController.java @@ -1,14 +1,13 @@ package com.todaysound.todaysound_server.domain.url.controller; -import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponseDto; +import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponse; import com.todaysound.todaysound_server.domain.url.service.UrlQueryService; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/api/urls") @RequiredArgsConstructor @@ -18,7 +17,7 @@ public class UrlController implements UrlApi { @Override @GetMapping - public List getUrls() { + public List getUrls() { return urlQueryService.getUrls(); } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponse.java similarity index 79% rename from src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponseDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponse.java index a639d59..05727d5 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponseDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/url/dto/response/UrlResponse.java @@ -1,9 +1,9 @@ package com.todaysound.todaysound_server.domain.url.dto.response; -public record UrlResponseDto ( +public record UrlResponse( Long id, String link, String title -){ +) { } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/url/service/UrlQueryService.java b/src/main/java/com/todaysound/todaysound_server/domain/url/service/UrlQueryService.java index 8638bce..763a161 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/url/service/UrlQueryService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/url/service/UrlQueryService.java @@ -1,25 +1,24 @@ package com.todaysound.todaysound_server.domain.url.service; -import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponseDto; +import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponse; import com.todaysound.todaysound_server.domain.url.entity.Url; import com.todaysound.todaysound_server.domain.url.repository.UrlRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class UrlQueryService { private final UrlRepository urlRepository; - public List getUrls() { + public List getUrls() { List urls = urlRepository.findAll(); return urls.stream() - .map(url -> new UrlResponseDto( + .map(url -> new UrlResponse( url.getId(), url.getLink(), url.getTitle() diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserApi.java b/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserApi.java index e60fa13..e49c7f5 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserApi.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserApi.java @@ -1,7 +1,7 @@ package com.todaysound.todaysound_server.domain.user.controller; -import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequestDto; -import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponseDto; +import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponse; import com.todaysound.todaysound_server.global.exception.CustomErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -17,63 +17,63 @@ @Tag(name = "User", description = "사용자 관리 API") public interface UserApi { - @Operation(summary = "익명 사용자 생성", description = """ - IOS가 생성한 랜덤 시크릿을 받아서 새 user_id를 - 생성하고 시크릿을 해시화 하고 DB에 저장한 이후에 응답을 반환합니다. - """, tags = {"User"}, operationId = "generateAnonymousUser") - @ApiResponses({@ApiResponse(responseCode = "200", description = "익명 사용자 생성 성공", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = ApiResponse.class), - examples = @ExampleObject( - name = "GlobalResponseAdvice로 감싸진 생성 성공 응답", - value = """ - { - "errorCode": null, - "message": "OK", - "result": { - "user_id": "user-uuid-1234" - } - } - """ + @Operation(summary = "익명 사용자 생성", description = """ + IOS가 생성한 랜덤 시크릿을 받아서 새 user_id를 + 생성하고 시크릿을 해시화 하고 DB에 저장한 이후에 응답을 반환합니다. + """, tags = {"User"}, operationId = "generateAnonymousUser") + @ApiResponses({@ApiResponse(responseCode = "200", description = "익명 사용자 생성 성공", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ApiResponse.class), + examples = @ExampleObject( + name = "GlobalResponseAdvice로 감싸진 생성 성공 응답", + value = """ + { + "errorCode": null, + "message": "OK", + "result": { + "user_id": "user-uuid-1234" + } + } + """ - ))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", - content = @Content(mediaType = "application/json", - schema = @Schema( - implementation = CustomErrorResponse.class), - examples = @ExampleObject(name = "잘못된 요청", - value = """ - { - "status": 400, - "code": "INVALID_REQUEST", - "message": "deviceSecret이 비어있습니다" - } - """))), - @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", - content = @Content(mediaType = "application/json", - schema = @Schema( - implementation = CustomErrorResponse.class), - examples = @ExampleObject(name = "인증 실패", - value = """ - { - "status": 401, - "code": "UNAUTHORIZED", - "message": "인증이 필요합니다" - } - """)))}) - UserIdResponseDto anonymous(@Valid @RequestBody UserSecretRequestDto userSecretRequestDto); + ))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", + content = @Content(mediaType = "application/json", + schema = @Schema( + implementation = CustomErrorResponse.class), + examples = @ExampleObject(name = "잘못된 요청", + value = """ + { + "status": 400, + "code": "INVALID_REQUEST", + "message": "deviceSecret이 비어있습니다" + } + """))), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", + content = @Content(mediaType = "application/json", + schema = @Schema( + implementation = CustomErrorResponse.class), + examples = @ExampleObject(name = "인증 실패", + value = """ + { + "status": 401, + "code": "UNAUTHORIZED", + "message": "인증이 필요합니다" + } + """)))}) + UserIdResponse anonymous(@Valid @RequestBody UserSecretRequest userSecretRequest); - @Operation(summary = "사용자 탈퇴", description = """ - X-User-ID, X-Device-Secret 헤더를 이용해 사용자를 탈퇴 처리합니다. - """, tags = {"User"}, operationId = "withdrawUser") - @ApiResponses({@ApiResponse(responseCode = "200", description = "사용자 탈퇴 성공"), @ApiResponse( - responseCode = "401", description = "인증되지 않은 사용자", - content = @Content(mediaType = "application/json", schema = @Schema( - implementation = CustomErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", - content = @Content(mediaType = "application/json", - schema = @Schema( - implementation = CustomErrorResponse.class)))}) - void withdraw(@RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret); + @Operation(summary = "사용자 탈퇴", description = """ + X-User-ID, X-Device-Secret 헤더를 이용해 사용자를 탈퇴 처리합니다. + """, tags = {"User"}, operationId = "withdrawUser") + @ApiResponses({@ApiResponse(responseCode = "200", description = "사용자 탈퇴 성공"), @ApiResponse( + responseCode = "401", description = "인증되지 않은 사용자", + content = @Content(mediaType = "application/json", schema = @Schema( + implementation = CustomErrorResponse.class))), + @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", + content = @Content(mediaType = "application/json", + schema = @Schema( + implementation = CustomErrorResponse.class)))}) + void withdraw(@RequestHeader("X-User-ID") String userUuid, + @RequestHeader("X-Device-Secret") String deviceSecret); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserController.java b/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserController.java index 5512c43..f267101 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserController.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/controller/UserController.java @@ -1,35 +1,39 @@ package com.todaysound.todaysound_server.domain.user.controller; -import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequestDto; -import com.todaysound.todaysound_server.domain.user.service.UserCommandService; +import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponse; +import com.todaysound.todaysound_server.domain.user.service.UserService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponseDto; - -import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController implements UserApi { - private final UserCommandService userCommandService; + private final UserService userService; @Override @PostMapping("/anonymous") - public UserIdResponseDto anonymous( - @Valid @RequestBody UserSecretRequestDto userSecretRequestDto) { - return userCommandService.anonymous(userSecretRequestDto); + public UserIdResponse anonymous( + @Valid @RequestBody UserSecretRequest userSecretRequest) { + return userService.anonymous(userSecretRequest); } @Override @DeleteMapping("/withdraw") @ResponseStatus(HttpStatus.OK) public void withdraw(@RequestHeader("X-User-ID") String userUuid, - @RequestHeader("X-Device-Secret") String deviceSecret) { - userCommandService.withdraw(userUuid, deviceSecret); + @RequestHeader("X-Device-Secret") String deviceSecret) { + userService.withdraw(userUuid, deviceSecret); } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequestDto.java b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequest.java similarity index 92% rename from src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequestDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequest.java index 324f944..5ddebef 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequestDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/FCMNotificationRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record FCMNotificationRequestDto( +public record FCMNotificationRequest( @NotBlank(message = "알림 제목은 필수입니다.") @Size(min = 1, max = 100, message = "제목 길이는 1~100자여야 합니다.") String title, diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequestDto.java b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequest.java similarity index 94% rename from src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequestDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequest.java index 1299d8c..4fd07c9 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequestDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/request/UserSecretRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record UserSecretRequestDto( +public record UserSecretRequest( @NotBlank(message = "디바이스 스크릿은 필수입니다.") @Size(min = 8, max = 256, message = "시크릿 길이는 8~256자여야 합니다.") @@ -16,4 +16,5 @@ public record UserSecretRequestDto( @NotBlank(message = "fcmToken은 필수입니다.") @Size(min = 4, max = 256, message = "fcmToken은 4~256자여야 합니다.") String fcmToken -) { } \ No newline at end of file +) { +} \ No newline at end of file diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponse.java b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponse.java new file mode 100644 index 0000000..580fd10 --- /dev/null +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponse.java @@ -0,0 +1,8 @@ +package com.todaysound.todaysound_server.domain.user.dto.response; + +public record FCMNotificationResponse(String message) { + public static FCMNotificationResponse success() { + return new FCMNotificationResponse("FCM 알림이 성공적으로 전송되었습니다."); + } +} + diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponseDto.java deleted file mode 100644 index 8300944..0000000 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/FCMNotificationResponseDto.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.todaysound.todaysound_server.domain.user.dto.response; - -public record FCMNotificationResponseDto(String message) { - public static FCMNotificationResponseDto success() { - return new FCMNotificationResponseDto("FCM 알림이 성공적으로 전송되었습니다."); - } -} - diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponseDto.java b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponse.java similarity index 64% rename from src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponseDto.java rename to src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponse.java index 395507d..48e19ef 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponseDto.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/dto/response/UserIdResponse.java @@ -3,12 +3,12 @@ import com.todaysound.todaysound_server.domain.user.entity.User; import io.swagger.v3.oas.annotations.media.Schema; -public record UserIdResponseDto( +public record UserIdResponse( @Schema(description = "외부 노출용 사용자 ID", example = "user-uuid-1234") String userId ) { - public static UserIdResponseDto from(User user) { - return new UserIdResponseDto(user.getUserId()); - } + public static UserIdResponse from(User user) { + return new UserIdResponse(user.getUserId()); + } } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/entity/FCM_Token.java b/src/main/java/com/todaysound/todaysound_server/domain/user/entity/FCM_Token.java index 278e329..a9b993c 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/entity/FCM_Token.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/entity/FCM_Token.java @@ -1,8 +1,16 @@ package com.todaysound.todaysound_server.domain.user.entity; import com.todaysound.todaysound_server.global.entity.BaseEntity; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/entity/User.java b/src/main/java/com/todaysound/todaysound_server/domain/user/entity/User.java index e056d88..6dc958d 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/entity/User.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/entity/User.java @@ -2,11 +2,23 @@ import com.todaysound.todaysound_server.domain.subscription.entity.Subscription; import com.todaysound.todaysound_server.global.entity.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import java.util.ArrayList; import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @Getter @@ -23,7 +35,7 @@ public class User extends BaseEntity { /** * 기본 키는 BaseEntity에서 상속받음 - * + * * @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; */ @@ -42,8 +54,7 @@ public class User extends BaseEntity { private String hashedSecret; /** - * 중복 검사용 지문 (고정 출력 해시: 예, SHA-256) - BCrypt는 솔트로 인해 매번 값이 달라 중복 비교에 부적합 - fingerprint는 유니크 인덱스로 - * 중복을 방지하는 용도로 사용 + * 중복 검사용 지문 (고정 출력 해시: 예, SHA-256) - BCrypt는 솔트로 인해 매번 값이 달라 중복 비교에 부적합 - fingerprint는 유니크 인덱스로 중복을 방지하는 용도로 사용 */ @Column(name = "secret_fingerprint", nullable = false, unique = true, length = 64) private String secretFingerprint; @@ -66,22 +77,20 @@ public class User extends BaseEntity { /** * 평문 시크릿 (비영속 필드) - * + * * @Transient로 JPA가 DB에 저장하지 않도록 설정 생성 시에만 사용하고 저장 후에는 null 처리 */ @Transient private String plainSecret; - /********************************* 연관관계 매핑 *********************************/ /** * 사용자의 구독 목록 - * - * @OneToMany: 1:N 관계, User 1개가 여러 Subscription을 가질 수 있음 mappedBy = "user": Subscription 엔티티의 - * user 필드가 연관관계의 주인 cascade = CascadeType.ALL: User 삭제 시 관련 Subscription도 함께 삭제 - * orphanRemoval = true: 고아 객체(연관관계가 끊어진 객체) 자동 삭제 fetch = FetchType.LAZY: 지연 로딩으로 - * 성능 최적화 + * + * @OneToMany: 1:N 관계, User 1개가 여러 Subscription을 가질 수 있음 mappedBy = "user": Subscription 엔티티의 user 필드가 연관관계의 주인 + * cascade = CascadeType.ALL: User 삭제 시 관련 Subscription도 함께 삭제 orphanRemoval = true: 고아 객체(연관관계가 끊어진 객체) 자동 삭제 fetch + * = FetchType.LAZY: 지연 로딩으로 성능 최적화 */ @Builder.Default @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, @@ -92,7 +101,6 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List fcmTokenList = new ArrayList<>(); - /********************************* 생성 메서드 *********************************/ /********************************* 비니지스 로직 *********************************/ diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/exception/AuthErrorCode.java b/src/main/java/com/todaysound/todaysound_server/domain/user/exception/AuthErrorCode.java index 66bdac7..f8405e2 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/exception/AuthErrorCode.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/exception/AuthErrorCode.java @@ -11,7 +11,7 @@ public enum AuthErrorCode implements ErrorCode { DEVICE_SECRET_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "AUTH409_1", "이미 존재하는 디바이스 시크릿입니다."), FCM_TOKEN_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "AUTH409_2", - "이미 존재하는 FCM 토큰입니다."); + "이미 존재하는 FCM 토큰입니다."); private final HttpStatus status; private final String errorCode; diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/factory/UserFactory.java b/src/main/java/com/todaysound/todaysound_server/domain/user/factory/UserFactory.java index 0a01dff..b3bafc2 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/factory/UserFactory.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/factory/UserFactory.java @@ -1,18 +1,16 @@ package com.todaysound.todaysound_server.domain.user.factory; -import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequestDto; -import com.todaysound.todaysound_server.domain.user.entity.FCM_Token; +import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequest; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.entity.UserType; import com.todaysound.todaysound_server.domain.user.service.SecretService; +import com.todaysound.todaysound_server.global.utils.CryptoUtils; +import java.util.ArrayList; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.UUID; -import com.todaysound.todaysound_server.global.utils.CryptoUtils; - @Component @RequiredArgsConstructor @Slf4j @@ -22,25 +20,25 @@ public class UserFactory { /** * 익명 사용자 생성 */ - public User createAnonymousUser(UserSecretRequestDto userSecretRequestDto) { + public User createAnonymousUser(UserSecretRequest userSecretRequest) { // 배포시 로깅은 제거 log.debug("익명 사용자 생성 시작: deviceSecret={}", - userSecretRequestDto.deviceSecret().substring(0, 8) + "..."); + userSecretRequest.deviceSecret().substring(0, 8) + "..."); // UUID 생성 String userId = UUID.randomUUID().toString(); // BCrypt 해쉬화 - String hashedSecret = secretService.encode(userSecretRequestDto.deviceSecret()); + String hashedSecret = secretService.encode(userSecretRequest.deviceSecret()); // 중복 검사용 fingerprint 생성 (SHA-256) - String secretFingerprint = CryptoUtils.sha256(userSecretRequestDto.deviceSecret()); + String secretFingerprint = CryptoUtils.sha256(userSecretRequest.deviceSecret()); // User 엔티티 생성 User user = User.builder().userId(userId).hashedSecret(hashedSecret) .secretFingerprint(secretFingerprint).userType(UserType.ANONYMOUS).isActive(true) - .plainSecret(userSecretRequestDto.deviceSecret()) // 생성 시에만 설정 + .plainSecret(userSecretRequest.deviceSecret()) // 생성 시에만 설정 .fcmTokenList(new ArrayList<>()) // 빌더 사용 시 명시적으로 초기화 .build(); diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/repository/FCMRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/user/repository/FCMRepository.java index 0515a3b..dc84958 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/repository/FCMRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/repository/FCMRepository.java @@ -2,12 +2,13 @@ import com.todaysound.todaysound_server.domain.user.entity.FCM_Token; import com.todaysound.todaysound_server.domain.user.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface FCMRepository extends JpaRepository { List findByUser(User user); + FCM_Token findByUserId(Long userId); + void deleteAllByFcmTokenIn(List fcmTokens); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/repository/UserRepository.java b/src/main/java/com/todaysound/todaysound_server/domain/user/repository/UserRepository.java index d8976f9..acd67ab 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/repository/UserRepository.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/repository/UserRepository.java @@ -2,11 +2,10 @@ import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.entity.UserType; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository { diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/service/SecretService.java b/src/main/java/com/todaysound/todaysound_server/domain/user/service/SecretService.java index ee9d8c6..4bb2630 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/service/SecretService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/service/SecretService.java @@ -5,28 +5,28 @@ import org.springframework.stereotype.Service; /** - * 시크릿 관련 서비스 - * - 시크릿 해시화 - * - 시크릿 검증 + * 시크릿 관련 서비스 - 시크릿 해시화 - 시크릿 검증 */ @Service @RequiredArgsConstructor public class SecretService { - + private final BCryptPasswordEncoder bCryptPasswordEncoder; - + /** * 시크릿 해시화 + * * @param rawPassword 평문 시크릿 * @return 해시화된 시크릿 */ public String encode(String rawPassword) { return bCryptPasswordEncoder.encode(rawPassword); } - + /** * 시크릿 검증 - * @param rawPassword 평문 시크릿 + * + * @param rawPassword 평문 시크릿 * @param encodedPassword 해시화된 시크릿 * @return 검증 결과 */ diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserQueryService.java b/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserQueryService.java index b91ad0a..5d9d913 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserQueryService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserQueryService.java @@ -4,10 +4,10 @@ import com.todaysound.todaysound_server.domain.user.exception.UserErrorCode; import com.todaysound.todaysound_server.domain.user.repository.UserRepository; import com.todaysound.todaysound_server.global.exception.BaseException; +import com.todaysound.todaysound_server.global.utils.CryptoUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.todaysound.todaysound_server.global.utils.CryptoUtils; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserCommandService.java b/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserService.java similarity index 80% rename from src/main/java/com/todaysound/todaysound_server/domain/user/service/UserCommandService.java rename to src/main/java/com/todaysound/todaysound_server/domain/user/service/UserService.java index dd166c1..4b8f7fa 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserCommandService.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/service/UserService.java @@ -1,47 +1,45 @@ package com.todaysound.todaysound_server.domain.user.service; -import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequestDto; -import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponseDto; +import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponse; import com.todaysound.todaysound_server.domain.user.entity.FCM_Token; import com.todaysound.todaysound_server.domain.user.entity.User; import com.todaysound.todaysound_server.domain.user.factory.UserFactory; import com.todaysound.todaysound_server.domain.user.repository.UserRepository; import com.todaysound.todaysound_server.domain.user.validator.HeaderAuthValidator; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - @Slf4j @Service @Transactional @RequiredArgsConstructor -public class UserCommandService { +public class UserService { private final UserRepository userRepository; private final UserFactory userFactory; private final UserQueryService userQueryService; private final HeaderAuthValidator headerAuthValidator; - public UserIdResponseDto anonymous(UserSecretRequestDto userSecretRequestDto) { + public UserIdResponse anonymous(UserSecretRequest userSecretRequest) { log.info("Anonymous user command received"); boolean secretExists = - userQueryService.existsBySecretFingerprint(userSecretRequestDto.deviceSecret()); + userQueryService.existsBySecretFingerprint(userSecretRequest.deviceSecret()); User user; if (!secretExists) { log.info("User secret does not exist, creating new user"); - log.info("fcmToken: {}", userSecretRequestDto.fcmToken()); + log.info("fcmToken: {}", userSecretRequest.fcmToken()); - User newUser = userFactory.createAnonymousUser(userSecretRequestDto); + User newUser = userFactory.createAnonymousUser(userSecretRequest); - FCM_Token fcmToken = FCM_Token.builder().fcmToken(userSecretRequestDto.fcmToken()) - .model(userSecretRequestDto.model()).user(newUser).build(); + FCM_Token fcmToken = FCM_Token.builder().fcmToken(userSecretRequest.fcmToken()) + .model(userSecretRequest.model()).user(newUser).build(); newUser.addFcmToken(fcmToken); @@ -50,10 +48,10 @@ public UserIdResponseDto anonymous(UserSecretRequestDto userSecretRequestDto) { user.clearPlainSecret(); } else { log.info("User secret exists, returning existing user"); - user = userQueryService.findBySecretFingerprint(userSecretRequestDto.deviceSecret()); + user = userQueryService.findBySecretFingerprint(userSecretRequest.deviceSecret()); } - return UserIdResponseDto.from(user); + return UserIdResponse.from(user); } diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/validator/HeaderAuthValidator.java b/src/main/java/com/todaysound/todaysound_server/domain/user/validator/HeaderAuthValidator.java index b0068d6..c2e1eaa 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/validator/HeaderAuthValidator.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/validator/HeaderAuthValidator.java @@ -10,8 +10,7 @@ import org.springframework.stereotype.Component; /** - * 헤더 기반 인증 값 검증기 - * - X-User-ID, X-Device-Secret을 검증하여 유효한 User를 반환 + * 헤더 기반 인증 값 검증기 - X-User-ID, X-Device-Secret을 검증하여 유효한 User를 반환 */ @Component @RequiredArgsConstructor diff --git a/src/main/java/com/todaysound/todaysound_server/domain/user/validator/UserValidator.java b/src/main/java/com/todaysound/todaysound_server/domain/user/validator/UserValidator.java index e26681f..78b60ea 100644 --- a/src/main/java/com/todaysound/todaysound_server/domain/user/validator/UserValidator.java +++ b/src/main/java/com/todaysound/todaysound_server/domain/user/validator/UserValidator.java @@ -13,7 +13,7 @@ public class UserValidator { private final UserQueryService userQueryService; public void validateUniqueSecret(String deviceSecret) { - if(userQueryService.existsBySecretFingerprint(deviceSecret)){ + if (userQueryService.existsBySecretFingerprint(deviceSecret)) { throw BaseException.type(AuthErrorCode.DEVICE_SECRET_ALREADY_EXISTED); } } diff --git a/src/main/java/com/todaysound/todaysound_server/global/config/FCMConfig.java b/src/main/java/com/todaysound/todaysound_server/global/config/FCMConfig.java index 3cb25b3..7800068 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/config/FCMConfig.java +++ b/src/main/java/com/todaysound/todaysound_server/global/config/FCMConfig.java @@ -4,6 +4,9 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import jakarta.annotation.PostConstruct; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; import lombok.extern.slf4j.Slf4j; @@ -11,10 +14,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - @Slf4j @Configuration public class FCMConfig { diff --git a/src/main/java/com/todaysound/todaysound_server/global/config/MdcLoggingFilter.java b/src/main/java/com/todaysound/todaysound_server/global/config/MdcLoggingFilter.java index c4da18a..5141f8d 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/config/MdcLoggingFilter.java +++ b/src/main/java/com/todaysound/todaysound_server/global/config/MdcLoggingFilter.java @@ -4,15 +4,14 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.UUID; - @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class MdcLoggingFilter extends OncePerRequestFilter { @@ -24,7 +23,7 @@ public class MdcLoggingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + FilterChain filterChain) throws ServletException, IOException { try { // 고유한 trace ID 생성 String traceId = generateTraceId(); diff --git a/src/main/java/com/todaysound/todaysound_server/global/config/SecurityConfig.java b/src/main/java/com/todaysound/todaysound_server/global/config/SecurityConfig.java index fead8b8..ce102d1 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/config/SecurityConfig.java +++ b/src/main/java/com/todaysound/todaysound_server/global/config/SecurityConfig.java @@ -42,7 +42,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/subscriptions/**").permitAll() // 구독 API 허용 .requestMatchers("/api/fcm/**").permitAll() // FCM API 허용 (테스트용) .requestMatchers("/api/feeds/**").permitAll() // Feed API 허용 - .requestMatchers("/api/urls/**").permitAll() // Url API 허용 + .requestMatchers("/api/urls/**") + .permitAll() // Url API 허용 // 크롤러 전용 내부 API (로컬/내부 네트워크에서만 접근한다고 가정) .requestMatchers("/internal/**").permitAll() diff --git a/src/main/java/com/todaysound/todaysound_server/global/config/SwaggerConfig.java b/src/main/java/com/todaysound/todaysound_server/global/config/SwaggerConfig.java index 8fd49f2..8c524f4 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/config/SwaggerConfig.java +++ b/src/main/java/com/todaysound/todaysound_server/global/config/SwaggerConfig.java @@ -1,39 +1,38 @@ package com.todaysound.todaysound_server.global.config; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; -import io.swagger.v3.oas.models.Components; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; @Configuration public class SwaggerConfig { - @Bean - public OpenAPI openAPI() { - - // API 정보 설정 - Info info = new Info().title("Today Sound API") // 제목 - .description("오늘의 소리 API 명세서") // 설명 - .version("1.0.0"); // 버전 - - String jwtSchemeName = "JWT"; - SecurityRequirement securityRequirement = - new SecurityRequirement().addList(jwtSchemeName); - - Components components = new Components().addSecuritySchemes(jwtSchemeName, - new SecurityScheme().name(jwtSchemeName) // 스키마 이름 - .type(SecurityScheme.Type.HTTP) // HTTP 타입 - .scheme("bearer") // Bearer 토큰 - .bearerFormat("JWT")); // JWT 형식 - - return new OpenAPI().addServersItem(new Server().url("/")) // 서버 URL - .info(info) // API 정보 - .addSecurityItem(securityRequirement) // 보안 요구사항 - .components(components); // 컴포넌트 - } + @Bean + public OpenAPI openAPI() { + + // API 정보 설정 + Info info = new Info().title("Today Sound API") // 제목 + .description("오늘의 소리 API 명세서") // 설명 + .version("1.0.0"); // 버전 + + String jwtSchemeName = "JWT"; + SecurityRequirement securityRequirement = + new SecurityRequirement().addList(jwtSchemeName); + + Components components = new Components().addSecuritySchemes(jwtSchemeName, + new SecurityScheme().name(jwtSchemeName) // 스키마 이름 + .type(SecurityScheme.Type.HTTP) // HTTP 타입 + .scheme("bearer") // Bearer 토큰 + .bearerFormat("JWT")); // JWT 형식 + + return new OpenAPI().addServersItem(new Server().url("/")) // 서버 URL + .info(info) // API 정보 + .addSecurityItem(securityRequirement) // 보안 요구사항 + .components(components); // 컴포넌트 + } } diff --git a/src/main/java/com/todaysound/todaysound_server/global/entity/BaseEntity.java b/src/main/java/com/todaysound/todaysound_server/global/entity/BaseEntity.java index 385c707..12012b4 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/entity/BaseEntity.java +++ b/src/main/java/com/todaysound/todaysound_server/global/entity/BaseEntity.java @@ -4,9 +4,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; -import lombok.Getter; - import java.util.Objects; +import lombok.Getter; @MappedSuperclass @Getter @@ -18,10 +17,12 @@ public class BaseEntity { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } BaseEntity that = (BaseEntity) o; return id.equals(that.id); } diff --git a/src/main/java/com/todaysound/todaysound_server/global/exception/CommonErrorCode.java b/src/main/java/com/todaysound/todaysound_server/global/exception/CommonErrorCode.java index 1ed8551..b9cdf53 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/exception/CommonErrorCode.java +++ b/src/main/java/com/todaysound/todaysound_server/global/exception/CommonErrorCode.java @@ -1,8 +1,8 @@ package com.todaysound.todaysound_server.global.exception; -import org.springframework.http.HttpStatus; import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.http.HttpStatus; @Getter @AllArgsConstructor diff --git a/src/main/java/com/todaysound/todaysound_server/global/exception/CustomErrorResponse.java b/src/main/java/com/todaysound/todaysound_server/global/exception/CustomErrorResponse.java index 69d8def..3cbe155 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/exception/CustomErrorResponse.java +++ b/src/main/java/com/todaysound/todaysound_server/global/exception/CustomErrorResponse.java @@ -6,7 +6,7 @@ @Getter @AllArgsConstructor public class CustomErrorResponse { - + private int status; private String errorCode; private String message; diff --git a/src/main/java/com/todaysound/todaysound_server/global/exception/ErrorCode.java b/src/main/java/com/todaysound/todaysound_server/global/exception/ErrorCode.java index f05f793..b16cf3d 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/exception/ErrorCode.java +++ b/src/main/java/com/todaysound/todaysound_server/global/exception/ErrorCode.java @@ -4,6 +4,8 @@ public interface ErrorCode { HttpStatus getStatus(); + String getErrorCode(); + String getMessage(); } diff --git a/src/main/java/com/todaysound/todaysound_server/global/exception/GlobalExceptionHandler.java b/src/main/java/com/todaysound/todaysound_server/global/exception/GlobalExceptionHandler.java index 8d32b43..ea89364 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/todaysound/todaysound_server/global/exception/GlobalExceptionHandler.java @@ -1,5 +1,8 @@ package com.todaysound.todaysound_server.global.exception; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -8,10 +11,6 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.NoHandlerFoundException; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - @Slf4j @RestControllerAdvice @RequiredArgsConstructor @@ -70,7 +69,8 @@ public ResponseEntity handleMediaTypeNotSupported(HttpMedia 위의 핸들러들로 처리되지 않은 모든 RuntimeException */ @ExceptionHandler(RuntimeException.class) - public ResponseEntity handleUnexpectedException(RuntimeException e, HttpServletRequest request) { + public ResponseEntity handleUnexpectedException(RuntimeException e, + HttpServletRequest request) { log.error("Unexpected error occurred", e); log.error("Request info: {} {}", request.getMethod(), request.getRequestURI()); diff --git a/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMApi.java b/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMApi.java index 4eee2d3..fea79b6 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMApi.java +++ b/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMApi.java @@ -1,7 +1,7 @@ package com.todaysound.todaysound_server.global.presentation; -import com.todaysound.todaysound_server.domain.user.dto.request.FCMNotificationRequestDto; -import com.todaysound.todaysound_server.domain.user.dto.response.FCMNotificationResponseDto; +import com.todaysound.todaysound_server.domain.user.dto.request.FCMNotificationRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.FCMNotificationResponse; import com.todaysound.todaysound_server.global.exception.CustomErrorResponse; import com.todaysound.todaysound_server.global.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -46,8 +46,8 @@ public interface FCMApi { "message": "인증이 필요합니다." } """)))}) - FCMNotificationResponseDto sendNotification(@RequestHeader("X-User-ID") String userId, - @Valid @RequestBody FCMNotificationRequestDto requestDto); + FCMNotificationResponse sendNotification(@RequestHeader("X-User-ID") String userId, + @Valid @RequestBody FCMNotificationRequest requestDto); } diff --git a/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMController.java b/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMController.java index cbccdf1..20b23e3 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMController.java +++ b/src/main/java/com/todaysound/todaysound_server/global/presentation/FCMController.java @@ -1,7 +1,7 @@ package com.todaysound.todaysound_server.global.presentation; -import com.todaysound.todaysound_server.domain.user.dto.request.FCMNotificationRequestDto; -import com.todaysound.todaysound_server.domain.user.dto.response.FCMNotificationResponseDto; +import com.todaysound.todaysound_server.domain.user.dto.request.FCMNotificationRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.FCMNotificationResponse; import com.todaysound.todaysound_server.domain.user.service.UserQueryService; import com.todaysound.todaysound_server.global.application.FCMService; import com.todaysound.todaysound_server.global.dto.FCMUpdateRequest; @@ -23,15 +23,15 @@ public class FCMController implements FCMApi { private final UserQueryService userQueryService; @PostMapping("/send") - public FCMNotificationResponseDto sendNotification(@RequestHeader("X-User-ID") String userId, - @Valid @RequestBody FCMNotificationRequestDto requestDto) { + public FCMNotificationResponse sendNotification(@RequestHeader("X-User-ID") String userId, + @Valid @RequestBody FCMNotificationRequest requestDto) { // X-User-ID로 사용자 조회 var user = userQueryService.findByUserId(userId); // FCM 알림 전송 fcmService.sendNotificationToUser(user, requestDto.title(), requestDto.body()); - return FCMNotificationResponseDto.success(); + return FCMNotificationResponse.success(); } @PutMapping("") diff --git a/src/main/java/com/todaysound/todaysound_server/global/utils/CryptoUtils.java b/src/main/java/com/todaysound/todaysound_server/global/utils/CryptoUtils.java index 2b44cb0..b0340b7 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/utils/CryptoUtils.java +++ b/src/main/java/com/todaysound/todaysound_server/global/utils/CryptoUtils.java @@ -11,6 +11,7 @@ public class CryptoUtils { /** * SHA-256 해시 생성 + * * @param input 입력 문자열 * @return SHA-256 해시값 (64자리 hex 문자열) */ @@ -27,9 +28,10 @@ public static String sha256(String input) { throw new IllegalStateException("SHA-256 not available", e); } } - + /** * MD5 해시 생성 (필요시 사용) + * * @param input 입력 문자열 * @return MD5 해시값 (32자리 hex 문자열) */ diff --git a/src/main/java/com/todaysound/todaysound_server/global/utils/LogMarkers.java b/src/main/java/com/todaysound/todaysound_server/global/utils/LogMarkers.java index 06b01f2..7faafff 100644 --- a/src/main/java/com/todaysound/todaysound_server/global/utils/LogMarkers.java +++ b/src/main/java/com/todaysound/todaysound_server/global/utils/LogMarkers.java @@ -5,44 +5,53 @@ public final class LogMarkers { - private LogMarkers() { - // 유틸리티 클래스 - } + /** + * 핵심 비즈니스 로직 (구독 생성, 삭제 등) + */ + public static final Marker BUSINESS = MarkerFactory.getMarker("BUSINESS"); // ============================================ // 핵심 비즈니스 로그 (반드시 모니터링 필요) // ============================================ - - /** 핵심 비즈니스 로직 (구독 생성, 삭제 등) */ - public static final Marker BUSINESS = MarkerFactory.getMarker("BUSINESS"); - - /** 사용자 인증/인가 관련 */ + /** + * 사용자 인증/인가 관련 + */ public static final Marker AUTH = MarkerFactory.getMarker("AUTH"); - - /** 결제/과금 관련 (최우선 모니터링) */ + /** + * 결제/과금 관련 (최우선 모니터링) + */ public static final Marker PAYMENT = MarkerFactory.getMarker("PAYMENT"); - - /** 외부 API 호출 (FCM, 외부 서비스 등) */ + /** + * 외부 API 호출 (FCM, 외부 서비스 등) + */ public static final Marker EXTERNAL_API = MarkerFactory.getMarker("EXTERNAL_API"); + /** + * 즉시 대응 필요한 심각한 문제 + */ + public static final Marker CRITICAL = MarkerFactory.getMarker("CRITICAL"); // ============================================ // 심각도 높은 로그 // ============================================ - - /** 즉시 대응 필요한 심각한 문제 */ - public static final Marker CRITICAL = MarkerFactory.getMarker("CRITICAL"); - - /** 알람이 필요한 중요 이벤트 */ + /** + * 알람이 필요한 중요 이벤트 + */ public static final Marker ALERT = MarkerFactory.getMarker("ALERT"); + /** + * API 성능 측정 + */ + public static final Marker PERFORMANCE = MarkerFactory.getMarker("PERFORMANCE"); // ============================================ // 성능/모니터링 로그 // ============================================ - - /** API 성능 측정 */ - public static final Marker PERFORMANCE = MarkerFactory.getMarker("PERFORMANCE"); - - /** 스케줄러/배치 작업 */ + /** + * 스케줄러/배치 작업 + */ public static final Marker SCHEDULER = MarkerFactory.getMarker("SCHEDULER"); + + private LogMarkers() { + // 유틸리티 클래스 + } } diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 3ce9296..5ffc33f 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,15 +1,15 @@ - + - + - + @@ -25,7 +25,7 @@ - + @@ -63,7 +63,7 @@ - + diff --git a/src/test/java/com/todaysound/todaysound_server/TodaysoundServerApplicationTests.java b/src/test/java/com/todaysound/todaysound_server/TodaysoundServerApplicationTests.java index 08118c4..a3101ac 100644 --- a/src/test/java/com/todaysound/todaysound_server/TodaysoundServerApplicationTests.java +++ b/src/test/java/com/todaysound/todaysound_server/TodaysoundServerApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class TodaysoundServerApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java index 728529d..f319133 100644 --- a/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/command/summary/presentation/SummaryControllerTest.java @@ -22,7 +22,7 @@ class SummaryControllerTest extends DocumentationTestSupport { // given Long summaryId = 1L; - doNothing().when(summaryCommandService).deleteSummary(anyString(), anyString(), anyLong()); + doNothing().when(summaryService).deleteSummary(anyString(), anyString(), anyLong()); // when then mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/summaries/{summaryId}", summaryId) diff --git a/src/test/java/com/todaysound/todaysound_server/query/alarm/AlarmControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/alarm/AlarmControllerTest.java deleted file mode 100644 index 9883314..0000000 --- a/src/test/java/com/todaysound/todaysound_server/query/alarm/AlarmControllerTest.java +++ /dev/null @@ -1,106 +0,0 @@ -//package com.todaysound.todaysound_server.query.alarm; -// -//import com.todaysound.todaysound_server.domain.alarm.controller.AlarmController; -//import java.time.LocalDate; -//import java.util.Date; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -//import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -//import org.springframework.test.context.bean.override.mockito.MockitoBean; -//import org.springframework.test.web.servlet.MockMvc; -// -//import static org.mockito.BDDMockito.*; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -// -//import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; -//import com.todaysound.todaysound_server.domain.alarm.service.AlarmQueryService; -//import com.todaysound.todaysound_server.domain.summary.service.SummaryCommandService; -//import com.todaysound.todaysound_server.global.dto.PageRequestDTO; -// -//import java.util.List; -// -//@WebMvcTest(controllers = AlarmController.class) -//@AutoConfigureMockMvc(addFilters = false) // 보안 필터 끄기 -//public class AlarmControllerTest { -// -// @Autowired -// private MockMvc mockMvc; // 가짜로 HTTP 요청을 보내줄 친구 -// -// @MockitoBean -// private AlarmQueryService alarmQueryService; // 가짜 서비스 객체 1 -// -// // (CommandService는 이 테스트 메소드에선 안 쓰이지만, 컨트롤러가 의존하고 있어서 Mock으로 채워줘야 에러가 안 남) -// @MockitoBean -// private SummaryCommandService summaryCommandService; -// -// -// // BDD 패턴 적용: Given - When - Then -// @Test -// public void getRecentAlarms_success() throws Exception { -// // given -// String userUuid = "test-user-uuid"; -// String deviceSecret = "test-device-secret"; -// -// RecentAlarmResponse response = new RecentAlarmResponse( -// 1L, -// 1L, -// "동국대 SW 융합교육원", -// "요약된 알림 내용...", -// "https://example.com/post/1", -// "5분 전", -// true, -// false -// ); -// -// List responses = List.of(response); -// -// // PageRequestDTO(page, size) 에 맞춰서 mock 설정 -// given(alarmQueryService.getRecentAlarms( -// any(PageRequestDTO.class), -// eq(userUuid), -// eq(deviceSecret) -// )).willReturn(responses); -// -// // when -// // then -// mockMvc.perform(get("/api/alarms") -// .param("page", "0") -// .param("size", "10") -// .header("X-User-ID", userUuid) -// .header("X-Device-Secret", deviceSecret)) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.result[0].subscriptionId").value(1L)) -// .andExpect(jsonPath("$.result[0].alias").value("동국대 SW 융합교육원")) -// .andExpect(jsonPath("$.result[0].isUrgent").value(true)); -// -// // 서비스가 정확한 값으로 한 번 호출됐는지도 검증 (선택) -// then(alarmQueryService).should(times(1)) -// .getRecentAlarms(any(PageRequestDTO.class), eq(userUuid), eq(deviceSecret)); -// } -// -// -// @Test -// // @DisplayName("헤더 누락 시 400 에러가 터지는지 확인") // Junit5에서는 이렇게 이름을 붙여줌 -// public void getRecentAlarms_fail_missing_header() throws Exception { -// // given -// // 이번엔 준비물이 필요 없어. -// // 왜냐? 컨트롤러 입구컷 당해서 서비스까지 가지도 못할 거니까! -// -// // when -// mockMvc.perform(get("/api/alarms") -// .param("page", "0") -// .param("size", "10") -// // .header("X-User-ID", "...") <--- 이걸 일부러 뺌 -// .header("X-Device-Secret", "test-device-secret")) -// -// // then -// .andExpect(status().isBadRequest()) // 1. 상태 코드가 400인지 확인 -// .andDo(print()); // 2. 로그에 요청/응답 내용을 자세히 찍음 (디버깅용) -// -// // 진짜로 서비스가 호출 안 됐는지 확인! -// then(alarmQueryService).shouldHaveNoInteractions(); -// } -//} diff --git a/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java index af4449e..c80b0b5 100644 --- a/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/alarm/presentation/AlarmQueryControllerTest.java @@ -1,10 +1,8 @@ package com.todaysound.todaysound_server.query.alarm.presentation; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willReturn; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -15,7 +13,6 @@ import com.todaysound.todaysound_server.domain.alarm.dto.response.RecentAlarmResponse; import com.todaysound.todaysound_server.support.DocumentationTestSupport; import java.util.List; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; import org.springframework.restdocs.payload.JsonFieldType; @@ -49,7 +46,7 @@ class AlarmQueryControllerTest extends DocumentationTestSupport { parameterWithName("size").description("페이지 크기")), responseFields(fieldWithPath("errorCode").description("응답 코드"), fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), - fieldWithPath("result").type(JsonFieldType.ARRAY).description("피드 목록"), + fieldWithPath("result").type(JsonFieldType.ARRAY).description("알림 목록"), fieldWithPath("result[].subscriptionId").type(JsonFieldType.NUMBER).description("구독 ID"), fieldWithPath("result[].summaryId").type(JsonFieldType.NUMBER).description("요약 ID"), fieldWithPath("result[].alias").type(JsonFieldType.STRING).description("구독 별칭"), diff --git a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java index d861a05..234fa0b 100644 --- a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/FeedQueryControllerTest.java @@ -10,7 +10,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponseDTO; +import com.todaysound.todaysound_server.domain.feed.dto.response.FeedResponse; import com.todaysound.todaysound_server.domain.feed.dto.response.HomeFeedResponse; import com.todaysound.todaysound_server.support.DocumentationTestSupport; import java.util.List; @@ -26,13 +26,13 @@ class FeedQueryControllerTest extends DocumentationTestSupport { // given PageRequest request = PageRequest.of(0, 10); - FeedResponseDTO response1 = new FeedResponseDTO(1L, "피드 내용", "작성자 이름", "2024-06-01T12:00:00", "fe", "1시간 전"); - FeedResponseDTO response2 = new FeedResponseDTO(2L, "또 다른 피드 내용", "다른 작성자", "2024-06-01T11:30:00", "fe2", + FeedResponse response1 = new FeedResponse(1L, "피드 내용", "작성자 이름", "2024-06-01T12:00:00", "fe", "1시간 전"); + FeedResponse response2 = new FeedResponse(2L, "또 다른 피드 내용", "다른 작성자", "2024-06-01T11:30:00", "fe2", "2시간 전"); - FeedResponseDTO response3 = new FeedResponseDTO(3L, "세 번째 피드 내용", "세 번째 작성자", "2024-06-01T10:00:00", "fe3", + FeedResponse response3 = new FeedResponse(3L, "세 번째 피드 내용", "세 번째 작성자", "2024-06-01T10:00:00", "fe3", "3시간 전"); - List responseList = List.of(response1, response2, response3); + List responseList = List.of(response1, response2, response3); given(feedQueryService.findFeeds(anyString(), anyString(), any())).willReturn(responseList); @@ -45,14 +45,14 @@ class FeedQueryControllerTest extends DocumentationTestSupport { result.andExpect(status().isOk()).andDo(restDocsHandler.document( queryParameters(parameterWithName("page").description("페이지 번호 (0부터 시작)"), parameterWithName("size").description("페이지 크기")), - responseFields(fieldWithPath("errorCode").description("응답 코드"), - fieldWithPath("message").description("응답 메시지"), fieldWithPath("result").description("피드 목록"), - fieldWithPath("result[].subscriptionId").description("구독 ID"), - fieldWithPath("result[].alias").description("구독 별칭"), - fieldWithPath("result[].summaryTitle").description("요약 제목"), - fieldWithPath("result[].summaryContent").description("요약 내용"), - fieldWithPath("result[].postUrl").description("연결 url"), - fieldWithPath("result[].timeAgo").description("피드 작성 후 경과 시간")))); + responseFields(fieldWithPath("errorCode").description("응답 코드"), + fieldWithPath("message").description("응답 메시지"), fieldWithPath("result").description("피드 목록"), + fieldWithPath("result[].subscriptionId").description("구독 ID"), + fieldWithPath("result[].alias").description("구독 별칭"), + fieldWithPath("result[].summaryTitle").description("요약 제목"), + fieldWithPath("result[].summaryContent").description("요약 내용"), + fieldWithPath("result[].postUrl").description("연결 url"), + fieldWithPath("result[].timeAgo").description("피드 작성 후 경과 시간")))); } diff --git a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java index 4f97558..765d3df 100644 --- a/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/feed/presentation/SubscriptionControllerTest.java @@ -16,11 +16,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequestDto; +import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionCreateRequest; import com.todaysound.todaysound_server.domain.subscription.dto.request.SubscriptionUpdateRequest; -import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto; -import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponseDto.KeywordItem; -import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponseDto; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponse; +import com.todaysound.todaysound_server.domain.subscription.dto.response.KeywordListResponse.KeywordItem; +import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionCreationResponse; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse; import com.todaysound.todaysound_server.domain.subscription.dto.response.SubscriptionResponse.KeywordResponse; import com.todaysound.todaysound_server.support.DocumentationTestSupport; @@ -37,11 +37,11 @@ public class SubscriptionControllerTest extends DocumentationTestSupport { @Test void 신규_구독을_등록한다() throws Exception { // given - SubscriptionCreateRequestDto request = new SubscriptionCreateRequestDto(1L, List.of(1L, 2L), "넓은마을", true); + SubscriptionCreateRequest request = new SubscriptionCreateRequest(1L, List.of(1L, 2L), "넓은마을", true); - given(subscriptionCommandService.createSubscription(anyString(), anyString(), - any(SubscriptionCreateRequestDto.class))).willReturn( - SubscriptionCreationResponseDto.builder().subscriptionId(1L).build()); + given(subscriptionService.createSubscription(anyString(), anyString(), + any(SubscriptionCreateRequest.class))).willReturn( + SubscriptionCreationResponse.builder().subscriptionId(1L).build()); // when then ResultActions result = mockMvc.perform( @@ -96,7 +96,7 @@ public class SubscriptionControllerTest extends DocumentationTestSupport { //given Long subscriptionId = 1L; - doNothing().when(subscriptionCommandService).deleteSubscription(anyLong(), anyString(), anyString()); + doNothing().when(subscriptionService).deleteSubscription(anyLong(), anyString(), anyString()); //when then mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/subscriptions/{subscriptionId}", subscriptionId) @@ -111,7 +111,7 @@ public class SubscriptionControllerTest extends DocumentationTestSupport { @Test void 키워드_목록을_조회한다() throws Exception { //given - KeywordListResponseDto response = new KeywordListResponseDto( + KeywordListResponse response = new KeywordListResponse( List.of(new KeywordItem(1L, "AI"), new KeywordItem(2L, "개발"), new KeywordItem(3L, "뉴스"))); given(subscriptionQueryService.getAllKeywords()).willReturn(response); @@ -134,7 +134,7 @@ public class SubscriptionControllerTest extends DocumentationTestSupport { Long subscriptionId = 1L; SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(List.of(1L, 2L), "수정된 별칭", true); - doNothing().when(subscriptionCommandService) + doNothing().when(subscriptionService) .updateSubscription(anyLong(), anyString(), anyString(), any(SubscriptionUpdateRequest.class)); //when then diff --git a/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java b/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java index 09b87b6..8ff7a10 100644 --- a/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java +++ b/src/test/java/com/todaysound/todaysound_server/query/url/presentation/UrlQueryControllerTest.java @@ -6,7 +6,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponseDto; +import com.todaysound.todaysound_server.domain.url.dto.response.UrlResponse; import com.todaysound.todaysound_server.support.DocumentationTestSupport; import java.util.List; import org.junit.jupiter.api.Test; @@ -18,9 +18,9 @@ class UrlQueryControllerTest extends DocumentationTestSupport { @Test void URL목록을_조회한다() throws Exception { // given - UrlResponseDto response = new UrlResponseDto(1L, "https://todaysound.com", "Today's Sound"); + UrlResponse response = new UrlResponse(1L, "https://todaysound.com", "Today's Sound"); - List responseList = List.of(response); + List responseList = List.of(response); given(urlQueryService.getUrls()).willReturn(responseList); diff --git a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java index 0c19d7a..db8fe1a 100644 --- a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java +++ b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java @@ -6,10 +6,10 @@ import com.todaysound.todaysound_server.domain.feed.controller.FeedController; import com.todaysound.todaysound_server.domain.feed.service.FeedQueryService; import com.todaysound.todaysound_server.domain.subscription.controller.SubscriptionController; -import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionQueryService; +import com.todaysound.todaysound_server.domain.subscription.service.SubscriptionService; import com.todaysound.todaysound_server.domain.summary.controller.SummaryController; -import com.todaysound.todaysound_server.domain.summary.service.SummaryCommandService; +import com.todaysound.todaysound_server.domain.summary.service.SummaryService; import com.todaysound.todaysound_server.domain.url.controller.UrlController; import com.todaysound.todaysound_server.domain.url.service.UrlQueryService; import org.springframework.beans.factory.annotation.Autowired; @@ -47,12 +47,12 @@ public class DocumentationTestSupport { protected SubscriptionQueryService subscriptionQueryService; @MockitoBean - protected SubscriptionService subscriptionCommandService; + protected SubscriptionService subscriptionService; @MockitoBean protected UrlQueryService urlQueryService; @MockitoBean - protected SummaryCommandService summaryCommandService; + protected SummaryService summaryService; } diff --git a/src/test/java/com/todaysound/todaysound_server/support/RestDocsConfig.java b/src/test/java/com/todaysound/todaysound_server/support/RestDocsConfig.java index fd6d971..8ef14d4 100644 --- a/src/test/java/com/todaysound/todaysound_server/support/RestDocsConfig.java +++ b/src/test/java/com/todaysound/todaysound_server/support/RestDocsConfig.java @@ -1,12 +1,14 @@ package com.todaysound.todaysound_server.support; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; + import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; - @TestConfiguration public class RestDocsConfig { diff --git a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet index 91911b2..2b207e4 100644 --- a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet @@ -1,14 +1,14 @@ //==== Request Fields -|=== -|Path|Type|Optional|Description + |=== + |Path|Type|Optional|Description {{#fields}} -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} + |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} + |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} + |{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} + |{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} -|=== \ No newline at end of file + |=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet index fa9bbcd..babb4a90 100644 --- a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet @@ -1,13 +1,13 @@ //==== Response Fields -|=== -|Path|Type|Description + |=== + |Path|Type|Description {{#fields}} -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} + |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} + |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} + |{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} -|=== + |=== From b6540e41086b3c062f24e1e2f59c473fc687457b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:14:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20user=20Test=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EB=81=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 5 ++ .../user/presentation/UserControllerTest.java | 67 +++++++++++++++++++ .../support/DocumentationTestSupport.java | 6 +- .../templates/response-fields.snippet | 14 ++-- 4 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/todaysound/todaysound_server/command/user/presentation/UserControllerTest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 1148806..e894766 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -33,3 +33,8 @@ include::api/url/url.adoc[] == Summary API include::api/summary/summary.adoc[] + +[[User-API]] +== User API + +include::api/user/user.adoc[] diff --git a/src/test/java/com/todaysound/todaysound_server/command/user/presentation/UserControllerTest.java b/src/test/java/com/todaysound/todaysound_server/command/user/presentation/UserControllerTest.java new file mode 100644 index 0000000..3ea2e78 --- /dev/null +++ b/src/test/java/com/todaysound/todaysound_server/command/user/presentation/UserControllerTest.java @@ -0,0 +1,67 @@ +package com.todaysound.todaysound_server.command.user.presentation; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.todaysound.todaysound_server.domain.user.dto.request.UserSecretRequest; +import com.todaysound.todaysound_server.domain.user.dto.response.UserIdResponse; +import com.todaysound.todaysound_server.support.DocumentationTestSupport; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class UserControllerTest extends DocumentationTestSupport { + + + @Test + void 익명_사용자를_등록한다() throws Exception { + // given + UserIdResponse response = new UserIdResponse("test-user-uuid-1234"); + + UserSecretRequest request = new UserSecretRequest("device-secret-5678", "Pixel 5", "fcm-token-91011"); + + given(userService.anonymous(request)).willReturn(response); + + // when then + ResultActions result = mockMvc.perform( + post("/api/users/anonymous").content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("deviceSecret").type(JsonFieldType.STRING).description("디바이스 시크릿"), + fieldWithPath("model").type(JsonFieldType.STRING).description("디바이스 모델명"), + fieldWithPath("fcmToken").type(JsonFieldType.STRING).description("FCM 토큰")), + responseFields(fieldWithPath("errorCode").type(JsonFieldType.NULL).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result").type(JsonFieldType.OBJECT).description("피드 목록"), + fieldWithPath("result.userId").type(JsonFieldType.STRING).description("등록된 사용자 UUID")))); + } + + @Test + void 회원을_탈퇴한다() throws Exception { + // given + willDoNothing().given(userService).withdraw(anyString(), anyString()); + + // when then + ResultActions result = mockMvc.perform( + RestDocumentationRequestBuilders.delete("/api/users/withdraw") + .header("X-User-ID", "test-user-uuid") + .header("X-Device-Secret", "test-device-secret")); + + result.andDo(print()).andExpect(status().isOk()).andDo(restDocsHandler.document( + requestHeaders( + headerWithName("X-User-ID").description("사용자 UUID"), + headerWithName("X-Device-Secret").description("디바이스 시크릿")))); + } +} \ No newline at end of file diff --git a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java index db8fe1a..f8a5f0b 100644 --- a/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java +++ b/src/test/java/com/todaysound/todaysound_server/support/DocumentationTestSupport.java @@ -12,6 +12,8 @@ import com.todaysound.todaysound_server.domain.summary.service.SummaryService; import com.todaysound.todaysound_server.domain.url.controller.UrlController; import com.todaysound.todaysound_server.domain.url.service.UrlQueryService; +import com.todaysound.todaysound_server.domain.user.controller.UserController; +import com.todaysound.todaysound_server.domain.user.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -22,7 +24,7 @@ import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(controllers = {FeedController.class, AlarmQueryController.class, SubscriptionController.class, - UrlController.class, SummaryController.class}) + UrlController.class, SummaryController.class, UserController.class}) @Import({RestDocsConfig.class,}) @AutoConfigureRestDocs @AutoConfigureMockMvc(addFilters = false) @@ -55,4 +57,6 @@ public class DocumentationTestSupport { @MockitoBean protected SummaryService summaryService; + @MockitoBean + protected UserService userService; } diff --git a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet index babb4a90..19e8b6a 100644 --- a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet @@ -1,13 +1,13 @@ -//==== Response Fields - |=== - |Path|Type|Description +==== Response Fields +|=== +|Path|Type|Description {{#fields}} - |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} - |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} - |{{#tableCellContent}}{{description}}{{/tableCellContent}} +|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} +|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} - |=== +|=== \ No newline at end of file