diff --git a/Api-Module/src/docs/asciidoc/Tag.adoc b/Api-Module/src/docs/asciidoc/Tag.adoc index b4a3549e..56317819 100644 --- a/Api-Module/src/docs/asciidoc/Tag.adoc +++ b/Api-Module/src/docs/asciidoc/Tag.adoc @@ -26,12 +26,16 @@ operation::TagControllerTest/getTopRankParentTagTest/[snippets='http-request,req operation::TagControllerTest/getParentTagsByFilter/[snippets='http-request,http-response,response-fields'] - [[GetChildTagTest]] -=== 하위 태그 조회 API +=== 하위 태그 전체 조회 API operation::TagControllerTest/getAllChildTagTest/[snippets='http-request,path-parameters,http-response,response-fields'] +[[GetChildTagsByFilter]] +=== 상위 태그 내 하위 태그 조회 API + +operation::TagControllerTest/getChildTagsByYear/[snippets='http-request,path-parameters,http-response,response-fields'] + [[duplicatedParentTagTest]] ==== 상위 태그 이름 중복 예외 diff --git a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetChildTag.kt b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetChildTag.kt new file mode 100644 index 00000000..87390050 --- /dev/null +++ b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetChildTag.kt @@ -0,0 +1,21 @@ +package com.bamyanggang.apimodule.domain.tag.application.dto + +import com.bamyanggang.apimodule.domain.tag.application.dto.GetParentTag.TagDetail +import java.util.* + +class GetChildTag { + data class Response( + val tags: List + ) + + data class TotalTagInfo( + val totalExperienceCount: Int, + val tagInfos : List + ) + + data class ChildTagSummary( + val id: UUID, + val name: String, + val experienceCount: Int + ) +} diff --git a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetTag.kt b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetParentTag.kt similarity index 81% rename from Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetTag.kt rename to Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetParentTag.kt index f709ac04..8e15289c 100644 --- a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetTag.kt +++ b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/dto/GetParentTag.kt @@ -2,7 +2,7 @@ package com.bamyanggang.apimodule.domain.tag.application.dto import java.util.* -class GetTag { +class GetParentTag { data class Response( val tags: List ) @@ -14,10 +14,10 @@ class GetTag { data class TotalTagInfo( val totalExperienceCount: Int, - val tagInfos : List + val tagInfos : List ) - data class TagSummary( + data class ParentTagSummary( val id: UUID, val name: String, val strongPointCount: Int, diff --git a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/service/TagGetService.kt b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/service/TagGetService.kt index 17983516..7914f695 100644 --- a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/service/TagGetService.kt +++ b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/application/service/TagGetService.kt @@ -1,7 +1,8 @@ package com.bamyanggang.apimodule.domain.tag.application.service import com.bamyanggang.apimodule.common.getAuthenticationPrincipal -import com.bamyanggang.apimodule.domain.tag.application.dto.GetTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetChildTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetParentTag import com.bamyanggang.domainmodule.domain.experience.service.ExperienceReader import com.bamyanggang.domainmodule.domain.tag.service.TagReader import org.springframework.stereotype.Service @@ -14,29 +15,29 @@ class TagGetService( private val experienceReader: ExperienceReader ) { @Transactional(readOnly = true) - fun getAllParentTagByUserId(): GetTag.Response { + fun getAllParentTagByUserId(): GetParentTag.Response { val tagDetails = getAuthenticationPrincipal().let { tagReader.readAllParentTagsByUserId(it).map { tag -> - GetTag.TagDetail(tag.id, tag.name) + GetParentTag.TagDetail(tag.id, tag.name) } } - return GetTag.Response(tagDetails) + return GetParentTag.Response(tagDetails) } @Transactional(readOnly = true) - fun getAllChildTagsByParentTagId(parentTagId: UUID): GetTag.Response { + fun getAllChildTagsByParentTagId(parentTagId: UUID): GetChildTag.Response { val tagDetails = getAuthenticationPrincipal().let { tagReader.readAllChildTagsByUserId(it, parentTagId).map { tag -> - GetTag.TagDetail(tag.id, tag.name) + GetParentTag.TagDetail(tag.id, tag.name) } } - return GetTag.Response(tagDetails) + return GetChildTag.Response(tagDetails) } @Transactional(readOnly = true) - fun getParentTagsByYearAndLimit(year: Int, limit: Int): GetTag.Response { + fun getParentTagsByYearAndLimit(year: Int, limit: Int): GetParentTag.Response { val currentUserId = getAuthenticationPrincipal() val topParentTagIds = experienceReader.readByYearDesc(year, currentUserId) .distinctBy { it.parentTagId } @@ -44,14 +45,14 @@ class TagGetService( .map { it.parentTagId } return tagReader.readByIds(topParentTagIds).map { - GetTag.TagDetail(it.id, it.name) + GetParentTag.TagDetail(it.id, it.name) }.let { - GetTag.Response(it) + GetParentTag.Response(it) } } @Transactional(readOnly = true) - fun getAllParentTagsByYear(year: Int): GetTag.TotalTagInfo { + fun getAllParentTagsByYear(year: Int): GetParentTag.TotalTagInfo { val currentUserId = getAuthenticationPrincipal() val experiences = experienceReader.readByYearDesc(year, currentUserId) @@ -67,7 +68,7 @@ class TagGetService( } } - GetTag.TagSummary( + GetParentTag.ParentTagSummary( parentTag.id, parentTag.name, strongPoints.size, @@ -75,7 +76,29 @@ class TagGetService( ) } - return GetTag.TotalTagInfo( + return GetParentTag.TotalTagInfo( + experiences.size, + tagSummaries + ) + } + + @Transactional(readOnly = true) + fun getAllChildTagsByYearAndParentTagId(year: Int, parentTagId: UUID): GetChildTag.TotalTagInfo { + val currentUserId = getAuthenticationPrincipal() + val experiences = experienceReader.readByYearDesc(year, currentUserId) + val experienceGroup = experiences.groupBy { it.childTagId } + + val tagSummaries = experienceGroup.map { + val childTag = tagReader.readById(it.key) + + GetChildTag.ChildTagSummary( + childTag.id, + childTag.name, + it.value.size + ) + } + + return GetChildTag.TotalTagInfo( experiences.size, tagSummaries ) diff --git a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagApi.kt b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagApi.kt index ebe9ada4..a76f6af7 100644 --- a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagApi.kt +++ b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagApi.kt @@ -2,7 +2,8 @@ package com.bamyanggang.apimodule.domain.tag.presentation object TagApi { const val BASE_URL = "/api/tags" - const val MY_TAG_URL = "$BASE_URL/my" + const val MY_PARENT_TAG_URL = "$BASE_URL/my" const val TOP_RANK_TAG_URL = "$BASE_URL/top-rank" const val TAG_PATH_VARIABLE_URL = "$BASE_URL/{tagId}" + const val MY_CHILD_TAG_URL = "$BASE_URL/{tagId}/my" } diff --git a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagController.kt b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagController.kt index 390b8dbb..27f29ae4 100644 --- a/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagController.kt +++ b/Api-Module/src/main/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagController.kt @@ -1,7 +1,8 @@ package com.bamyanggang.apimodule.domain.tag.presentation import com.bamyanggang.apimodule.domain.tag.application.dto.CreateTag -import com.bamyanggang.apimodule.domain.tag.application.dto.GetTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetChildTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetParentTag import com.bamyanggang.apimodule.domain.tag.application.service.TagCreateService import com.bamyanggang.apimodule.domain.tag.application.service.TagDeleteService import com.bamyanggang.apimodule.domain.tag.application.service.TagGetService @@ -14,29 +15,35 @@ class TagController( private val tagDeleteService: TagDeleteService, private val tagGetService: TagGetService ) { - @GetMapping(TagApi.BASE_URL) - fun getParentTagsByYear(@RequestParam("year") year: Int): GetTag.TotalTagInfo { - return tagGetService.getAllParentTagsByYear(year) - } - @GetMapping(TagApi.TOP_RANK_TAG_URL) fun getTopRankTagsByLimit( @RequestParam("year") year: Int, @RequestParam("limit") limit: Int - ): GetTag.Response { + ): GetParentTag.Response { return tagGetService.getParentTagsByYearAndLimit(year, limit) } - @GetMapping(TagApi.MY_TAG_URL) - fun getUserParentTags(): GetTag.Response { + @GetMapping(TagApi.MY_PARENT_TAG_URL) + fun getUserParentTags(): GetParentTag.Response { return tagGetService.getAllParentTagByUserId() } - @GetMapping(TagApi.TAG_PATH_VARIABLE_URL) - fun getAllChildTags(@PathVariable("tagId") parentTagId: UUID): GetTag.Response { + @GetMapping(TagApi.MY_CHILD_TAG_URL) + fun getUserChildTags(@PathVariable("tagId") parentTagId: UUID): GetChildTag.Response { return tagGetService.getAllChildTagsByParentTagId(parentTagId) } + @GetMapping(TagApi.BASE_URL) + fun getParentTagsByYear(@RequestParam("year") year: Int): GetParentTag.TotalTagInfo { + return tagGetService.getAllParentTagsByYear(year) + } + + @GetMapping(TagApi.TAG_PATH_VARIABLE_URL) + fun getChildTagsByYear(@PathVariable("tagId") parentTagId: UUID, + @RequestParam("year") year: Int): GetChildTag.TotalTagInfo { + return tagGetService.getAllChildTagsByYearAndParentTagId(year, parentTagId) + } + @PostMapping(TagApi.BASE_URL) fun createParentTag(@RequestBody request: CreateTag.Request): CreateTag.Response { return tagCreateService.createParentTag(request) diff --git a/Api-Module/src/test/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagControllerTest.kt b/Api-Module/src/test/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagControllerTest.kt index 674ce59b..15e55e8f 100644 --- a/Api-Module/src/test/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagControllerTest.kt +++ b/Api-Module/src/test/kotlin/com/bamyanggang/apimodule/domain/tag/presentation/TagControllerTest.kt @@ -2,7 +2,8 @@ package com.bamyanggang.apimodule.domain.tag.presentation import com.bamyanggang.apimodule.BaseRestDocsTest import com.bamyanggang.apimodule.domain.tag.application.dto.CreateTag -import com.bamyanggang.apimodule.domain.tag.application.dto.GetTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetChildTag +import com.bamyanggang.apimodule.domain.tag.application.dto.GetParentTag import com.bamyanggang.commonmodule.exception.ExceptionHandler import com.bamyanggang.commonmodule.fixture.generateFixture import com.bamyanggang.domainmodule.domain.tag.exception.TagException @@ -70,7 +71,7 @@ class TagControllerTest : BaseRestDocsTest() { val parentTagId = generateFixture() - given(tagController.createChildTag(createChildTagRequest, parentTagId, )).willReturn(createChildTagResponse) + given(tagController.createChildTag(createChildTagRequest, parentTagId)).willReturn(createChildTagResponse) val request = RestDocumentationRequestBuilders.post(TagApi.TAG_PATH_VARIABLE_URL, parentTagId) .header("Authorization", "Bearer Access Token") @@ -212,18 +213,18 @@ class TagControllerTest : BaseRestDocsTest() { @DisplayName("하위 태그를 전체 조회한다.") fun getAllChildTagTest() { //given - val parentTagId = generateFixture() + val parentTagId = UUID.randomUUID() val tagDetails = arrayListOf( - GetTag.TagDetail(generateFixture(), "하위 태그 이름 1"), - GetTag.TagDetail(generateFixture(), "하위 태그 이름 2") + GetParentTag.TagDetail(UUID.randomUUID(), "하위 태그 이름 1"), + GetParentTag.TagDetail(UUID.randomUUID(), "하위 태그 이름 2") ) - val tagResponse = GetTag.Response(tagDetails) + val tagResponse = GetChildTag.Response(tagDetails) - given(tagController.getAllChildTags(parentTagId)).willReturn(tagResponse) + given(tagController.getUserChildTags(parentTagId)).willReturn(tagResponse) - val request = RestDocumentationRequestBuilders.get(TagApi.TAG_PATH_VARIABLE_URL, parentTagId) + val request = RestDocumentationRequestBuilders.get(TagApi.MY_CHILD_TAG_URL, parentTagId) .header("Authorization", "Bearer Access Token") //when @@ -249,18 +250,18 @@ class TagControllerTest : BaseRestDocsTest() { @Test @DisplayName("유저가 등록한 상위 태그를 전체 조회한다.") - fun getAllParentTagByUserRegisterTest() { + fun getAllParentTagByUserTest() { //given val tagDetails = arrayListOf( - GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"), - GetTag.TagDetail(generateFixture(), "상위 태그 이름 2") + GetParentTag.TagDetail(generateFixture(), "상위 태그 이름 1"), + GetParentTag.TagDetail(generateFixture(), "상위 태그 이름 2") ) - val tagResponse = GetTag.Response(tagDetails) + val tagResponse = GetParentTag.Response(tagDetails) given(tagController.getUserParentTags()).willReturn(tagResponse) - val request = RestDocumentationRequestBuilders.get(TagApi.MY_TAG_URL) + val request = RestDocumentationRequestBuilders.get(TagApi.MY_PARENT_TAG_URL) .header("Authorization", "Bearer Access Token") //when @@ -286,13 +287,13 @@ class TagControllerTest : BaseRestDocsTest() { fun getTopRankParentTagTest() { //given val tagDetails = arrayListOf( - GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"), - GetTag.TagDetail(generateFixture(), "상위 태그 이름 2") + GetParentTag.TagDetail(generateFixture(), "상위 태그 이름 1"), + GetParentTag.TagDetail(generateFixture(), "상위 태그 이름 2") ) val year = 2024 val limit = 6 - val tagResponse = GetTag.Response(tagDetails) + val tagResponse = GetParentTag.Response(tagDetails) given(tagController.getTopRankTagsByLimit(year, limit)).willReturn(tagResponse) @@ -320,22 +321,17 @@ class TagControllerTest : BaseRestDocsTest() { } @Test - @DisplayName("필터에 의해 걸러진 태그 정보를 반환한다.") - fun getParentTagsByFilter() { + @DisplayName("연도 내 상위 태그 정보(관련 경험, 역량 키워드 정보 포함)를 반환한다.") + fun getParentTagsByYear() { //given -// val tagDetails = arrayListOf( -// GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"), -// GetTag.TagDetail(generateFixture(), "상위 태그 이름 2") -// ) - val tagSummaries = arrayListOf( - GetTag.TagSummary( + GetParentTag.ParentTagSummary( UUID.randomUUID(), "상위 태그 정보 1", 3, 14 ), - GetTag.TagSummary( + GetParentTag.ParentTagSummary( UUID.randomUUID(), "상위 태그 정보 2", 1, @@ -343,7 +339,7 @@ class TagControllerTest : BaseRestDocsTest() { ) ) - val tagResponse = GetTag.TotalTagInfo( + val tagResponse = GetParentTag.TotalTagInfo( 21, tagSummaries) @@ -378,6 +374,62 @@ class TagControllerTest : BaseRestDocsTest() { ) } + @Test + @DisplayName("연도 내 하위 태그 정보(관련 경험 개수, 태그 정보)를 반환한다.") + fun getChildTagsByYear() { + //given + val tagSummaries = arrayListOf( + GetChildTag.ChildTagSummary( + UUID.randomUUID(), + "하위 태그 이름 1", + 14, + ), + GetChildTag.ChildTagSummary( + UUID.randomUUID(), + "하위 태그 이름 2", + 7, + ), + ) + + val tagResponse = GetChildTag.TotalTagInfo( + 21, + tagSummaries) + + val year = 2024 + + val parentTagId = UUID.randomUUID() + given(tagController.getChildTagsByYear(parentTagId, year)).willReturn(tagResponse) + + val request = RestDocumentationRequestBuilders.get(TagApi.TAG_PATH_VARIABLE_URL, parentTagId) + .header("Authorization", "Bearer Access Token") + .queryParam("year", year.toString()) + + //when + val result = mockMvc.perform(request) + + //then + result.andExpect(status().isOk) + .andDo(resultHandler.document( + requestHeaders( + headerWithName("Authorization").description("엑세스 토큰") + ), + pathParameters( + parameterWithName("tagId").description("상위 태그 id") + ), + queryParameters( + parameterWithName("year").description("검색 연도") + ), + responseFields( + fieldWithPath("totalExperienceCount").description("상위 태그 내 총 경험 개수"), + fieldWithPath("tagInfos").description("하위 태그 정보 배열"), + fieldWithPath("tagInfos[].id").description("하위 태그 id"), + fieldWithPath("tagInfos[].name").description("하위 태그 이름"), + fieldWithPath("tagInfos[].experienceCount").description("하위 태그 내 경험 개수"), + ) + ) + ) + } + @Test @DisplayName("태그를 삭제한다.") fun deleteTagTest() {