Skip to content

Commit

Permalink
Merge pull request #360 from daedongbread/feat/hot-keyword
Browse files Browse the repository at this point in the history
feat:인기검색 admin api 개발
  • Loading branch information
JayPark7821 authored Nov 10, 2023
2 parents e17883c + f9d972c commit e92be28
Show file tree
Hide file tree
Showing 29 changed files with 1,266 additions and 173 deletions.
19 changes: 19 additions & 0 deletions src/docs/asciidoc/admin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,22 @@ operation::v1/admin/carousels[snippets='http-request,request-headers,response-fi
=== 케러셀 순서 수정 API [PATCH]

operation::v1/admin/carousels/order/update[snippets='http-request,request-headers,request-fields,http-response']

== Admin Hot Keyword APIs

- 인기 검색어 순위 조회 API [GET]
- 인기 검색어 변경 API [PUT]
- 검색어 검색 횟수 조회 API [GET]


=== 인기 검색어 순위 조회 API [GET]

operation::v1/admin/search/hot-keywords/rank[snippets='http-request,request-headers,response-fields,http-response']

=== 검색어 검색 횟수 조회 API [GET]

operation::v1/admin/search/hot-keywords[snippets='http-request,request-headers,request-parameters,response-fields,http-response']

=== 인기 검색어 변경 API [PUT]

operation::v1/admin/search/hot-keywords/rank/update[snippets='http-request,request-headers,request-fields,http-response']
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.depromeet.breadmapbackend.domain.admin.search.dto.HotKeywordResponse;
import com.depromeet.breadmapbackend.domain.admin.search.dto.HotKeywordUpdateRequest;
import com.depromeet.breadmapbackend.domain.admin.search.dto.KeywordStatResponse;
import com.depromeet.breadmapbackend.global.dto.ApiResponse;

import lombok.RequiredArgsConstructor;

/**
* AdminSearchKeywordController
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/

@RestController
@RequestMapping("/v1/admin/search/hot-keywords")
@RequiredArgsConstructor
public class AdminHotKeywordController {

private final AdminHotKeywordService adminHotKeywordService;

@GetMapping
ApiResponse<List<KeywordStatResponse>> getHotKeywordsByKeyword(
@RequestParam(name = "sortType", required = false, defaultValue = "THREE_MONTH") String sortType
) {
return new ApiResponse<>(
adminHotKeywordService.getHotKeywords(SortType.valueOf(sortType.toUpperCase()))
.stream()
.map(Mapper::of)
.toList()
);
}

@GetMapping("/rank")
ApiResponse<List<HotKeywordResponse>> getHotKeywords() {
return new ApiResponse<>(
adminHotKeywordService.getHotKeywordsRank()
.stream()
.map(Mapper::of)
.toList()
);
}

@PutMapping("/rank")
@ResponseStatus(HttpStatus.ACCEPTED)
void updateHotKeywords(@RequestBody HotKeywordUpdateRequest request) {
adminHotKeywordService.updateHotKeywordsRank(
request.HotKeywordList()
.stream()
.map(HotKeywordUpdateRequest.HotKeywordInfo::toEntity)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.List;

/**
* AdminHotKeywordService
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
public interface AdminHotKeywordService {
List<Keyword> getHotKeywords(SortType sortType);

List<HotKeyword> getHotKeywordsRank();

void updateHotKeywordsRank(List<HotKeyword> hotKeywords);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.depromeet.breadmapbackend.global.exception.DaedongException;
import com.depromeet.breadmapbackend.global.exception.DaedongStatus;

import lombok.RequiredArgsConstructor;

/**
* AdminHotKeywordServiceImpl
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/

@Service
@RequiredArgsConstructor
public class AdminHotKeywordServiceImpl implements AdminHotKeywordService {

private final HotKeywordRepository hotKeywordRepository;

@Override
public List<Keyword> getHotKeywords(SortType sortType) {
return Keyword.getMockData().stream()
.sorted(Comparator.comparing((Keyword keyword) ->
switch (sortType) {
case ONE_WEEK -> keyword.getOneWeekCount();
case ONE_MONTH -> keyword.getOneMonthCount();
case THREE_MONTH -> keyword.getThreeMonthCount();
}).reversed())
.toList();
}

@Override
public List<HotKeyword> getHotKeywordsRank() {
return hotKeywordRepository.findAllByOrderByRankAsc();
}

@Override
public void updateHotKeywordsRank(final List<HotKeyword> hotKeywords) {
checkDuplicateKeywords(hotKeywords);
checkDuplicateRank(hotKeywords);

hotKeywordRepository.deleteAll(); // 최대 저장 키워드 50개
hotKeywordRepository.saveAll(hotKeywords);
}

private void checkDuplicateKeywords(final List<HotKeyword> hotKeywords) {
Map<String, Long> keywordCount = hotKeywords.stream()
.collect(Collectors.groupingBy(HotKeyword::getKeyword, Collectors.counting()));
if (keywordCount.values().stream().anyMatch(count -> count > 1)) {
throw new DaedongException(DaedongStatus.DUPLICATED_KEYWORD);
}
}

private void checkDuplicateRank(final List<HotKeyword> hotKeywords) {
Map<Integer, Long> keywordCount = hotKeywords.stream()
.collect(Collectors.groupingBy(HotKeyword::getRank, Collectors.counting()));
if (keywordCount.values().stream().anyMatch(count -> count > 1)) {
throw new DaedongException(DaedongStatus.DUPLICATED_RANK);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import com.depromeet.breadmapbackend.global.BaseEntity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* SearchKeyword
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/

@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class HotKeyword extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String keyword;
private int rank;

private HotKeyword(final String keyword, final int rank) {
this.keyword = keyword;
this.rank = rank;
}

public static HotKeyword createSearchKeyword(final String keyword, final int rank) {
return new HotKeyword(keyword, rank);
}

public void updateRank(final int rank) {
this.rank = rank;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

/**
* HotKeywordRepositoru
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
public interface HotKeywordRepository extends JpaRepository<HotKeyword, Long> {
List<HotKeyword> findAllByOrderByRankAsc();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.List;

import lombok.Getter;

/**
* Keyword
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
@Getter
public class Keyword {
private final Long id;
private final String keyword;
private final long oneWeekCount;
private final long oneMonthCount;
private final long threeMonthCount;

public Keyword(
final Long id,
final String keyword,
final long oneWeekCount,
final long oneMonthCount,
final long threeMonthCount
) {
this.id = id;
this.keyword = keyword;
this.oneWeekCount = oneWeekCount;
this.oneMonthCount = oneMonthCount;
this.threeMonthCount = threeMonthCount;
}

public static List<Keyword> getMockData() {
return List.of(
new Keyword(1L, "소금빵", 1, 265, 73),
new Keyword(2L, "강남역", 2, 212, 653),
new Keyword(3L, "테스트 검색어", 3, 234, 543),
new Keyword(4L, "하하하하", 4, 432, 453),
new Keyword(5L, "호호호", 5, 122, 453),
new Keyword(6L, "이힝", 6, 122, 5673),
new Keyword(7L, "가나다라", 7, 322, 653),
new Keyword(8L, "마바사", 8, 212, 453),
new Keyword(9L, "아자차카", 9, 212, 6573),
new Keyword(10L, "타파하", 1111, 223, 2343),
new Keyword(11L, "abcd", 123, 2123, 1233),
new Keyword(12L, "efg", 1543, 21234, 1233));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import com.depromeet.breadmapbackend.domain.admin.search.dto.HotKeywordResponse;
import com.depromeet.breadmapbackend.domain.admin.search.dto.KeywordStatResponse;

/**
* Mapper
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
public class Mapper {

public static HotKeywordResponse of(HotKeyword hotKeywords) {
return new HotKeywordResponse(
hotKeywords.getKeyword(),
hotKeywords.getRank()
);
}

public static KeywordStatResponse of(Keyword keyword) {
return new KeywordStatResponse(
keyword.getId(),
keyword.getKeyword(),
keyword.getOneWeekCount(),
keyword.getOneMonthCount(),
keyword.getThreeMonthCount()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.depromeet.breadmapbackend.domain.admin.search;

import java.util.Arrays;

import com.depromeet.breadmapbackend.global.exception.DaedongException;
import com.depromeet.breadmapbackend.global.exception.DaedongStatus;

/**
* SearchType
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
public enum SortType {
ONE_WEEK,
ONE_MONTH,
THREE_MONTH,
;

public static SortType find(final String sortType) {
return Arrays.stream(SortType.values())
.filter(type -> type.name().equals(sortType))
.findFirst()
.orElseThrow(() -> new DaedongException(DaedongStatus.NOT_EXISTS_SORT_TYPE));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.depromeet.breadmapbackend.domain.admin.search.dto;

/**
* HotKeywordResponse
*
* @author jaypark
* @version 1.0.0
* @since 11/10/23
*/
public record HotKeywordResponse(
String keyword,
int rank
) {
}
Loading

0 comments on commit e92be28

Please sign in to comment.