Skip to content

Commit

Permalink
Merge pull request #101 from KUSITMS-29th-TEAM-B/feat/flight-45
Browse files Browse the repository at this point in the history
feat: 태그 조회 API 구현
  • Loading branch information
whereami2048 authored May 20, 2024
2 parents 7c34fd8 + e3dd292 commit 8073abf
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 11 deletions.
13 changes: 12 additions & 1 deletion Api-Module/src/docs/asciidoc/Tag.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ operation::TagControllerTest/createChildTagTest/[snippets='http-request,path-par
[[GetParentTagTest]]
=== 상위 태그 조회 API

operation::TagControllerTest/getAllParentTagTest/[snippets='http-request,http-response,response-fields']
operation::TagControllerTest/getAllParentTagByUserRegisterTest/[snippets='http-request,request-headers,http-response,response-fields']

[[GetTopRankTagTest]]
=== 연도 내 경험 최근 추가 순 태그 조회 API

operation::TagControllerTest/getTopRankParentTagTest/[snippets='http-request,request-headers,http-response,response-fields']

[[GetParentTagsByFilter]]
=== 연도 내 상위 태그 조회 API

operation::TagControllerTest/getParentTagsByFilter/[snippets='http-request,http-response,response-fields']


[[GetChildTagTest]]
=== 하위 태그 조회 API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,16 @@ class GetTag {
val id: UUID,
val name: String
)

data class TotalTagInfo(
val totalExperienceCount: Int,
val tagInfos : List<TagSummary>
)

data class TagSummary(
val id: UUID,
val name: String,
val strongPointCount: Int,
val experienceCount: Int
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ 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.domainmodule.domain.experience.service.ExperienceReader
import com.bamyanggang.domainmodule.domain.tag.service.TagReader
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*

@Service
class TagGetService(
private val tagReader: TagReader
private val tagReader: TagReader,
private val experienceReader: ExperienceReader
) {
@Transactional(readOnly = true)
fun getAllParentTagByUserId(): GetTag.Response {
val tagDetails = getAuthenticationPrincipal().let {
tagReader.readAllParentTagsByUserId(it).map { tag ->
Expand All @@ -20,6 +24,7 @@ class TagGetService(
return GetTag.Response(tagDetails)
}

@Transactional(readOnly = true)
fun getAllChildTagsByParentTagId(parentTagId: UUID): GetTag.Response {
val tagDetails = getAuthenticationPrincipal().let {
tagReader.readAllChildTagsByUserId(it, parentTagId).map { tag ->
Expand All @@ -29,4 +34,50 @@ class TagGetService(

return GetTag.Response(tagDetails)
}

@Transactional(readOnly = true)
fun getParentTagsByYearAndLimit(year: Int, limit: Int): GetTag.Response {
val currentUserId = getAuthenticationPrincipal()
val topParentTagIds = experienceReader.readByYearDesc(year, currentUserId)
.distinctBy { it.parentTagId }
.take(limit)
.map { it.parentTagId }

return tagReader.readByIds(topParentTagIds).map {
GetTag.TagDetail(it.id, it.name)
}.let {
GetTag.Response(it)
}
}

@Transactional(readOnly = true)
fun getAllParentTagsByYear(year: Int): GetTag.TotalTagInfo {
val currentUserId = getAuthenticationPrincipal()
val experiences = experienceReader.readByYearDesc(year, currentUserId)

val experienceGroup = experiences.groupBy { it.parentTagId }

val tagSummaries = experienceGroup.map {
val parentTag = tagReader.readById(it.key)
val strongPoints = TreeSet<UUID>()

it.value.forEach { experience ->
experience.strongPoints.forEach { strongPoint ->
strongPoints.add(strongPoint.id)
}
}

GetTag.TagSummary(
parentTag.id,
parentTag.name,
strongPoints.size,
it.value.size
)
}

return GetTag.TotalTagInfo(
experiences.size,
tagSummaries
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ 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 TOP_RANK_TAG_URL = "$BASE_URL/top-rank"
const val TAG_PATH_VARIABLE_URL = "$BASE_URL/{tagId}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@ class TagController(
private val tagGetService: TagGetService
) {
@GetMapping(TagApi.BASE_URL)
fun getAllParentTags(): GetTag.Response {
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 {
return tagGetService.getParentTagsByYearAndLimit(year, limit)
}

@GetMapping(TagApi.MY_TAG_URL)
fun getUserParentTags(): GetTag.Response {
return tagGetService.getAllParentTagByUserId()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import org.springframework.restdocs.headers.HeaderDocumentation.headerWithName
import org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders
import org.springframework.restdocs.payload.PayloadDocumentation.*
import org.springframework.restdocs.request.RequestDocumentation.parameterWithName
import org.springframework.restdocs.request.RequestDocumentation.pathParameters
import org.springframework.restdocs.request.RequestDocumentation.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.util.*

Expand Down Expand Up @@ -249,8 +248,8 @@ class TagControllerTest : BaseRestDocsTest() {
}

@Test
@DisplayName("상위 태그를 전체조회한다.")
fun getAllParentTagTest() {
@DisplayName("유저가 등록한 상위 태그를 전체 조회한다.")
fun getAllParentTagByUserRegisterTest() {
//given
val tagDetails = arrayListOf(
GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"),
Expand All @@ -259,9 +258,9 @@ class TagControllerTest : BaseRestDocsTest() {

val tagResponse = GetTag.Response(tagDetails)

given(tagController.getAllParentTags()).willReturn(tagResponse)
given(tagController.getUserParentTags()).willReturn(tagResponse)

val request = RestDocumentationRequestBuilders.get(TagApi.BASE_URL)
val request = RestDocumentationRequestBuilders.get(TagApi.MY_TAG_URL)
.header("Authorization", "Bearer Access Token")

//when
Expand All @@ -282,6 +281,103 @@ class TagControllerTest : BaseRestDocsTest() {
)
}

@Test
@DisplayName("최근에 추가된 경험이 있는 순으로 상위 태그 정보를 반환한다.")
fun getTopRankParentTagTest() {
//given
val tagDetails = arrayListOf(
GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"),
GetTag.TagDetail(generateFixture(), "상위 태그 이름 2")
)

val year = 2024
val limit = 6
val tagResponse = GetTag.Response(tagDetails)

given(tagController.getTopRankTagsByLimit(year, limit)).willReturn(tagResponse)

val request = RestDocumentationRequestBuilders.get(TagApi.TOP_RANK_TAG_URL)
.header("Authorization", "Bearer Access Token")
.queryParam("year", year.toString())
.queryParam("limit", limit.toString())

//when
val result = mockMvc.perform(request)

//then
result.andExpect(status().isOk)
.andDo(resultHandler.document(
requestHeaders(
headerWithName("Authorization").description("엑세스 토큰")
),
responseFields(
fieldWithPath("tags").description("상위 태그 리스트"),
fieldWithPath("tags[].id").description("태그 id"),
fieldWithPath("tags[].name").description("태그 이름"),
)
)
)
}

@Test
@DisplayName("필터에 의해 걸러진 태그 정보를 반환한다.")
fun getParentTagsByFilter() {
//given
// val tagDetails = arrayListOf(
// GetTag.TagDetail(generateFixture(), "상위 태그 이름 1"),
// GetTag.TagDetail(generateFixture(), "상위 태그 이름 2")
// )

val tagSummaries = arrayListOf(
GetTag.TagSummary(
UUID.randomUUID(),
"상위 태그 정보 1",
3,
14
),
GetTag.TagSummary(
UUID.randomUUID(),
"상위 태그 정보 2",
1,
7
)
)

val tagResponse = GetTag.TotalTagInfo(
21,
tagSummaries)

val year = 2024

given(tagController.getParentTagsByYear(year)).willReturn(tagResponse)

val request = RestDocumentationRequestBuilders.get(TagApi.BASE_URL)
.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("엑세스 토큰")
), queryParameters(
parameterWithName("year").description("검색 연도")
),
responseFields(
fieldWithPath("totalExperienceCount").description("연도 내 총 경험 개수"),
fieldWithPath("tagInfos").description("상위 태그 정보 배열"),
fieldWithPath("tagInfos[].id").description("상위 태그 id"),
fieldWithPath("tagInfos[].name").description("상위 태그 이름"),
fieldWithPath("tagInfos[].strongPointCount").description("상위 태그 내 역량 키워드 개수"),
fieldWithPath("tagInfos[].experienceCount").description("상위 태그 내 경험 개수"),
)
)
)
}

@Test
@DisplayName("태그를 삭제한다.")
fun deleteTagTest() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface ExperienceRepository {
fun deleteByExperienceId(experienceId: UUID)
fun findByExperienceId(id: UUID): Experience
fun findAllByUserId(userId: UUID): List<Experience>
fun findByUserIdAndYearDesc(year: Int, userId: UUID): List<Experience>
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@ class ExperienceReader(

fun readAllYearsByExistExperience(userId: UUID): List<Int> {
val yearSet : TreeSet<Int> = TreeSet<Int>()
readAllExperienceByUserId(userId).forEach {
readAllByUserId(userId).forEach {
yearSet.add(it.startedAt.year)
}

return yearSet.toList()
}

fun readAllExperienceByUserId(userId: UUID): List<Experience> {
fun readAllByUserId(userId: UUID): List<Experience> {
return experienceRepository.findAllByUserId(userId)
}

fun readByYearDesc(year: Int, userId: UUID): List<Experience> {
return experienceRepository.findByUserIdAndYearDesc(year, userId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import java.util.*

interface TagRepository {
fun save(newTag : Tag)
fun findById(id : UUID) : Tag
fun findAllParentTagsByUserId(userId: UUID): List<Tag>
fun findAllChildTagsByUserId(userId: UUID, parentId: UUID): List<Tag>
fun deleteByTagId(tagId: UUID)
fun isExistById(tagId: UUID): Boolean
fun findByParentTagIds(tagParentTagIds: List<UUID>): List<Tag>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ class TagReader(
fun readAllChildTagsByUserId(userId: UUID, parentId: UUID): List<Tag> {
return tagRepository.findAllChildTagsByUserId(userId, parentId)
}

fun readById(tagId: UUID): Tag {
return tagRepository.findById(tagId)
}

fun readByIds(parentTagIds: List<UUID>): List<Tag> {
return tagRepository.findByParentTagIds(parentTagIds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.bamyanggang.persistence.experience.jpa.entity.ExperienceJpaEntity;
import com.bamyanggang.persistence.experience.jpa.repository.ExperienceJpaRepository;
import com.bamyanggang.persistence.experience.mapper.ExperienceMapper;
import com.bamyanggang.persistence.user.UserRepositoryImpl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
Expand All @@ -16,6 +18,7 @@
public class ExperienceRepositoryImpl implements ExperienceRepository {
private final ExperienceJpaRepository experienceJpaRepository;
private final ExperienceMapper experienceMapper;
private final UserRepositoryImpl userRepositoryImpl;

@Override
public void save(Experience experience) {
Expand All @@ -41,4 +44,14 @@ public List<Experience> findAllByUserId(UUID userId) {
List<ExperienceJpaEntity> userExperienceJpaEntities = experienceJpaRepository.findAllByUserId(userId);
return userExperienceJpaEntities.stream().map(experienceMapper::toExperienceDomainEntity).toList();
}

@Override
public List<Experience> findByUserIdAndYearDesc(int year, UUID userId) {
LocalDateTime startYear = LocalDateTime.of(year, 1, 1, 0, 0);
LocalDateTime endYear = LocalDateTime.of(year, 12, 31, 23, 59);
List<ExperienceJpaEntity> experienceJpaEntities = experienceJpaRepository
.findByUserIdAndCreatedAtBetweenOrderByCreatedAtDesc(userId, startYear, endYear);

return experienceJpaEntities.stream().map(experienceMapper::toExperienceDomainEntity).toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.bamyanggang.persistence.experience.jpa.repository;

import com.bamyanggang.persistence.experience.jpa.entity.ExperienceJpaEntity;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ExperienceJpaRepository extends JpaRepository<ExperienceJpaEntity, UUID> {
List<ExperienceJpaEntity> findAllByUserId(UUID userId);
List<ExperienceJpaEntity> findByUserIdAndCreatedAtBetweenOrderByCreatedAtDesc(UUID userId, LocalDateTime startYear, LocalDateTime endYear);
}
Loading

0 comments on commit 8073abf

Please sign in to comment.