diff --git a/Api/src/main/java/allchive/server/api/search/controller/SearchController.java b/Api/src/main/java/allchive/server/api/search/controller/SearchController.java index d476f5a5..e03afe90 100644 --- a/Api/src/main/java/allchive/server/api/search/controller/SearchController.java +++ b/Api/src/main/java/allchive/server/api/search/controller/SearchController.java @@ -4,10 +4,9 @@ import allchive.server.api.search.model.dto.request.SearchRequest; import allchive.server.api.search.model.dto.response.SearchListResponse; import allchive.server.api.search.model.dto.response.SearchResponse; +import allchive.server.api.search.model.dto.response.SearchVoListResponse; import allchive.server.api.search.model.enums.ArchivingType; -import allchive.server.api.search.service.GetLatestSearchListUseCase; -import allchive.server.api.search.service.GetRelativeSearchListUseCase; -import allchive.server.api.search.service.SearchArchivingUseCase; +import allchive.server.api.search.service.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -26,25 +25,39 @@ public class SearchController { private final SearchArchivingUseCase searchArchivingUseCase; private final GetLatestSearchListUseCase getLatestSearchListUseCase; private final GetRelativeSearchListUseCase getRelativeSearchListUseCase; + private final DeleteLatestSearchUseCase deleteLatestSearchUseCase; + private final RenewalTitleDataUseCase renewalTitleDataUseCase; @Operation(summary = "검색어를 검색합니다.") - @PostMapping + @GetMapping public SearchResponse searchArchiving( @ParameterObject @PageableDefault(size = 10) Pageable pageable, @RequestParam("type") ArchivingType type, - @RequestBody SearchRequest request) { - return searchArchivingUseCase.execute(pageable, type, request); + @RequestParam("word") String word) { + return searchArchivingUseCase.execute(pageable, type, word); } @Operation(summary = "최근 검색어 목록을 가져옵니다.", description = "5개만 드릴게요") @GetMapping(value = "/latest") - public SearchListResponse getLatestSearchList() { + public SearchVoListResponse getLatestSearchList() { return getLatestSearchListUseCase.execute(); } + @Operation(summary = "최근 검색어를 삭제합니다.") + @DeleteMapping(value = "/latest/{latestId}") + public void deleteLatestSearch(@PathVariable("latestId") Long latestId) { + deleteLatestSearchUseCase.execute(latestId); + } + @Operation(summary = "검색어 자동 완성") - @PostMapping(value = "/relation") - public SearchListResponse getRelativeSearchList(@RequestBody SearchRequest request) { - return getRelativeSearchListUseCase.execute(request); + @GetMapping(value = "/relation") + public SearchListResponse getRelativeSearchList(@RequestParam("word") String word) { + return getRelativeSearchListUseCase.execute(word); + } + + @Operation(summary = "자동 완성 데이터 강제 리뉴얼", deprecated = true) + @GetMapping(value = "/relation/force") + public void forceRenewalTitleData() { + renewalTitleDataUseCase.executeForce(); } } diff --git a/Api/src/main/java/allchive/server/api/search/model/dto/response/SearchVoListResponse.java b/Api/src/main/java/allchive/server/api/search/model/dto/response/SearchVoListResponse.java new file mode 100644 index 00000000..b922e9c2 --- /dev/null +++ b/Api/src/main/java/allchive/server/api/search/model/dto/response/SearchVoListResponse.java @@ -0,0 +1,21 @@ +package allchive.server.api.search.model.dto.response; + + +import allchive.server.api.search.model.vo.LatestSearchVo; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class SearchVoListResponse { + private List keywords; + + @Builder + private SearchVoListResponse(List keywords) { + this.keywords = keywords; + } + + public static SearchVoListResponse from(List keywords) { + return SearchVoListResponse.builder().keywords(keywords).build(); + } +} diff --git a/Api/src/main/java/allchive/server/api/search/model/vo/LatestSearchVo.java b/Api/src/main/java/allchive/server/api/search/model/vo/LatestSearchVo.java new file mode 100644 index 00000000..ac251aaf --- /dev/null +++ b/Api/src/main/java/allchive/server/api/search/model/vo/LatestSearchVo.java @@ -0,0 +1,29 @@ +package allchive.server.api.search.model.vo; + + +import allchive.server.domain.domains.search.domain.LatestSearch; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class LatestSearchVo { + @Schema(description = "최근 검색 내용") + private String word; + + @Schema(description = "최근 검색 내용 고유번호") + private Long latestSearchId; + + @Builder + private LatestSearchVo(String word, Long latestSearchId) { + this.word = word; + this.latestSearchId = latestSearchId; + } + + public static LatestSearchVo from(LatestSearch latestSearch) { + return LatestSearchVo.builder() + .word(latestSearch.getKeyword()) + .latestSearchId(latestSearch.getId()) + .build(); + } +} diff --git a/Api/src/main/java/allchive/server/api/search/service/DeleteLatestSearchUseCase.java b/Api/src/main/java/allchive/server/api/search/service/DeleteLatestSearchUseCase.java new file mode 100644 index 00000000..83d1f24a --- /dev/null +++ b/Api/src/main/java/allchive/server/api/search/service/DeleteLatestSearchUseCase.java @@ -0,0 +1,27 @@ +package allchive.server.api.search.service; + + +import allchive.server.api.config.security.SecurityUtil; +import allchive.server.core.annotation.UseCase; +import allchive.server.domain.domains.search.adaptor.LatestSearchAdaptor; +import allchive.server.domain.domains.search.validator.LatestSearchValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +public class DeleteLatestSearchUseCase { + private final LatestSearchValidator latestSearchValidator; + private final LatestSearchAdaptor latestSearchAdaptor; + + @Transactional + public void execute(Long latestSearchId) { + Long userId = SecurityUtil.getCurrentUserId(); + validateExecution(latestSearchId, userId); + latestSearchAdaptor.deleteByIdAndUserId(latestSearchId, userId); + } + + private void validateExecution(Long latestSearchId, Long userId) { + latestSearchValidator.validateExistByIdAndUserId(latestSearchId, userId); + } +} diff --git a/Api/src/main/java/allchive/server/api/search/service/GetLatestSearchListUseCase.java b/Api/src/main/java/allchive/server/api/search/service/GetLatestSearchListUseCase.java index a392af40..f1836264 100644 --- a/Api/src/main/java/allchive/server/api/search/service/GetLatestSearchListUseCase.java +++ b/Api/src/main/java/allchive/server/api/search/service/GetLatestSearchListUseCase.java @@ -2,10 +2,10 @@ import allchive.server.api.config.security.SecurityUtil; -import allchive.server.api.search.model.dto.response.SearchListResponse; +import allchive.server.api.search.model.dto.response.SearchVoListResponse; +import allchive.server.api.search.model.vo.LatestSearchVo; import allchive.server.core.annotation.UseCase; import allchive.server.domain.domains.search.adaptor.LatestSearchAdaptor; -import allchive.server.domain.domains.search.domain.LatestSearch; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -16,12 +16,12 @@ public class GetLatestSearchListUseCase { private final LatestSearchAdaptor latestSearchAdaptor; @Transactional(readOnly = true) - public SearchListResponse execute() { + public SearchVoListResponse execute() { Long userId = SecurityUtil.getCurrentUserId(); - List keywords = + List keywords = latestSearchAdaptor.findAllByUserIdOrderByCreatedAt(userId).stream() - .map(LatestSearch::getKeyword) + .map(LatestSearchVo::from) .toList(); - return SearchListResponse.from(keywords); + return SearchVoListResponse.from(keywords); } } diff --git a/Api/src/main/java/allchive/server/api/search/service/GetRelativeSearchListUseCase.java b/Api/src/main/java/allchive/server/api/search/service/GetRelativeSearchListUseCase.java index 744c1305..a5745048 100644 --- a/Api/src/main/java/allchive/server/api/search/service/GetRelativeSearchListUseCase.java +++ b/Api/src/main/java/allchive/server/api/search/service/GetRelativeSearchListUseCase.java @@ -20,13 +20,13 @@ public class GetRelativeSearchListUseCase { private final RedisTemplate redisTemplate; @Transactional - public SearchListResponse execute(SearchRequest request) { + public SearchListResponse execute(String word) { ZSetOperations zSetOperations = redisTemplate.opsForZSet(); List autoCompleteList = new ArrayList<>(); - Long rank = zSetOperations.rank(SEARCH_KEY, request.getKeyword()); + Long rank = zSetOperations.rank(SEARCH_KEY, word); if (rank != null) { Set rangeList = zSetOperations.range(SEARCH_KEY, rank, rank + 1000); - autoCompleteList = getAutoCompleteList(rangeList, request.getKeyword()); + autoCompleteList = getAutoCompleteList(rangeList, word); } return SearchListResponse.from(autoCompleteList); } diff --git a/Api/src/main/java/allchive/server/api/search/service/RenewalTitleDataUseCase.java b/Api/src/main/java/allchive/server/api/search/service/RenewalTitleDataUseCase.java index 3f204ba7..6ab96628 100644 --- a/Api/src/main/java/allchive/server/api/search/service/RenewalTitleDataUseCase.java +++ b/Api/src/main/java/allchive/server/api/search/service/RenewalTitleDataUseCase.java @@ -21,12 +21,20 @@ public class RenewalTitleDataUseCase { private final ArchivingAdaptor archivingAdaptor; private final RedisTemplate redisTemplate; - private static final int TIME_LIMIT = 1; - @Scheduled(cron = "0 0 3 * * *") @Transactional(readOnly = true) public void executeSchedule() { log.info("renewal title scheduler on"); + renewalData(); + log.info("renewal title scheduler off"); + } + + @Transactional(readOnly = true) + public void executeForce() { + renewalData(); + } + + private void renewalData() { redisTemplate.delete(SEARCH_KEY); Set archivings = new HashSet<>(archivingAdaptor.findAllByPublicStatus(Boolean.TRUE)); @@ -35,15 +43,14 @@ public void executeSchedule() { redisTemplate .opsForZSet() .add(SEARCH_KEY, archiving.getTitle().trim() + ASTERISK, 0); - for (int index = 1; index < archiving.getTitle().length(); index++) { + for (int index = 0; index <= archiving.getTitle().length(); index++) { redisTemplate .opsForZSet() .add( SEARCH_KEY, - archiving.getTitle().trim().substring(0, index - 1), + archiving.getTitle().trim().substring(0, index), 0); } }); - log.info("renewal title scheduler off"); } } diff --git a/Api/src/main/java/allchive/server/api/search/service/SearchArchivingUseCase.java b/Api/src/main/java/allchive/server/api/search/service/SearchArchivingUseCase.java index df4bc9ea..8c4bb03a 100644 --- a/Api/src/main/java/allchive/server/api/search/service/SearchArchivingUseCase.java +++ b/Api/src/main/java/allchive/server/api/search/service/SearchArchivingUseCase.java @@ -4,7 +4,6 @@ import allchive.server.api.archiving.model.dto.response.ArchivingResponse; import allchive.server.api.common.slice.SliceResponse; import allchive.server.api.config.security.SecurityUtil; -import allchive.server.api.search.model.dto.request.SearchRequest; import allchive.server.api.search.model.dto.response.SearchResponse; import allchive.server.api.search.model.enums.ArchivingType; import allchive.server.core.annotation.UseCase; @@ -32,23 +31,23 @@ public class SearchArchivingUseCase { private final LatestSearchDomainService latestSearchDomainService; @Transactional - public SearchResponse execute(Pageable pageable, ArchivingType type, SearchRequest request) { + public SearchResponse execute(Pageable pageable, ArchivingType type, String word) { Long userId = SecurityUtil.getCurrentUserId(); SliceResponse my = null; SliceResponse community = null; - renewalLatestSearch(userId, request.getKeyword()); + renewalLatestSearch(userId, word); switch (type) { case ALL -> { - my = getMyArchivings(userId, request.getKeyword(), pageable); - community = getCommunityArchivings(userId, request.getKeyword(), pageable); + my = getMyArchivings(userId, word, pageable); + community = getCommunityArchivings(userId, word, pageable); return SearchResponse.forAll(my, community); } case MY -> { - my = getMyArchivings(userId, request.getKeyword(), pageable); + my = getMyArchivings(userId, word, pageable); return SearchResponse.forMy(my); } case COMMUNITY -> { - community = getCommunityArchivings(userId, request.getKeyword(), pageable); + community = getCommunityArchivings(userId, word, pageable); return SearchResponse.forCommunity(community); } } diff --git a/Api/src/main/java/allchive/server/api/user/model/dto/response/GetUserProfileResponse.java b/Api/src/main/java/allchive/server/api/user/model/dto/response/GetUserProfileResponse.java index c0af16dd..5620b9a9 100644 --- a/Api/src/main/java/allchive/server/api/user/model/dto/response/GetUserProfileResponse.java +++ b/Api/src/main/java/allchive/server/api/user/model/dto/response/GetUserProfileResponse.java @@ -24,12 +24,17 @@ public class GetUserProfileResponse { @Schema(defaultValue = "0", description = "공개 아카이브 개수") private int publicArchivingCount; - @Schema(defaultValue = "0", description = "모든 아카이브 개수") private int archivingCount; @Builder - public GetUserProfileResponse(String nickname, String imgUrl, int linkCount, int imgCount, int publicArchivingCount, int archivingCount) { + public GetUserProfileResponse( + String nickname, + String imgUrl, + int linkCount, + int imgCount, + int publicArchivingCount, + int archivingCount) { this.nickname = nickname; this.imgUrl = imgUrl; this.linkCount = linkCount; diff --git a/Api/src/main/java/allchive/server/api/user/model/mapper/UserMapper.java b/Api/src/main/java/allchive/server/api/user/model/mapper/UserMapper.java index 18d84afa..ebd1fe40 100644 --- a/Api/src/main/java/allchive/server/api/user/model/mapper/UserMapper.java +++ b/Api/src/main/java/allchive/server/api/user/model/mapper/UserMapper.java @@ -17,6 +17,7 @@ public GetUserProfileResponse toGetUserProfileResponse( imgCount += archiving.getImgCnt(); publicArchivingCount += archiving.getPublicStatus() ? 1 : 0; } - return GetUserProfileResponse.of(user, linkCount, imgCount, publicArchivingCount, archivingList.size()); + return GetUserProfileResponse.of( + user, linkCount, imgCount, publicArchivingCount, archivingList.size()); } } diff --git a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java index e8694e3c..f45bea0b 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java +++ b/Domain/src/main/java/allchive/server/domain/domains/archiving/service/ArchivingDomainService.java @@ -5,9 +5,8 @@ import allchive.server.domain.domains.archiving.adaptor.ArchivingAdaptor; import allchive.server.domain.domains.archiving.domain.Archiving; import allchive.server.domain.domains.archiving.domain.enums.Category; -import java.util.List; - import allchive.server.domain.domains.content.domain.enums.ContentType; +import java.util.List; import lombok.RequiredArgsConstructor; @DomainService diff --git a/Domain/src/main/java/allchive/server/domain/domains/search/adaptor/LatestSearchAdaptor.java b/Domain/src/main/java/allchive/server/domain/domains/search/adaptor/LatestSearchAdaptor.java index c1087477..ad2f38aa 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/search/adaptor/LatestSearchAdaptor.java +++ b/Domain/src/main/java/allchive/server/domain/domains/search/adaptor/LatestSearchAdaptor.java @@ -3,6 +3,7 @@ import allchive.server.core.annotation.Adaptor; import allchive.server.domain.domains.search.domain.LatestSearch; +import allchive.server.domain.domains.search.exception.exceptions.LatestSearchNotFoundException; import allchive.server.domain.domains.search.repository.LatestSearchRepository; import java.util.List; import lombok.RequiredArgsConstructor; @@ -27,4 +28,14 @@ public void save(LatestSearch newSearch) { public void deleteAllByUserId(Long userId) { latestSearchRepository.deleteAllByUserId(userId); } + + public LatestSearch findByIdAndUserId(Long latestSearchId, Long userId) { + return latestSearchRepository + .findByIdAndUserId(latestSearchId, userId) + .orElseThrow(() -> LatestSearchNotFoundException.EXCEPTION); + } + + public void deleteByIdAndUserId(Long latestSearchId, Long userId) { + latestSearchRepository.deleteByIdAndUserId(latestSearchId, userId); + } } diff --git a/Domain/src/main/java/allchive/server/domain/domains/search/exception/SearchErrorCode.java b/Domain/src/main/java/allchive/server/domain/domains/search/exception/SearchErrorCode.java new file mode 100644 index 00000000..5cb874bb --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/domains/search/exception/SearchErrorCode.java @@ -0,0 +1,22 @@ +package allchive.server.domain.domains.search.exception; + +import static allchive.server.core.consts.AllchiveConst.*; + +import allchive.server.core.dto.ErrorReason; +import allchive.server.core.error.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SearchErrorCode implements BaseErrorCode { + LATEST_SEARCH_NOT_FOUND(NOT_FOUND, "LATESTSEARCH_404_1", "최근 검색어를 찾을 수 없습니다."); + private int status; + private String code; + private String reason; + + @Override + public ErrorReason getErrorReason() { + return ErrorReason.of(status, code, reason); + } +} diff --git a/Domain/src/main/java/allchive/server/domain/domains/search/exception/exceptions/LatestSearchNotFoundException.java b/Domain/src/main/java/allchive/server/domain/domains/search/exception/exceptions/LatestSearchNotFoundException.java new file mode 100644 index 00000000..da24ca92 --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/domains/search/exception/exceptions/LatestSearchNotFoundException.java @@ -0,0 +1,14 @@ +package allchive.server.domain.domains.search.exception.exceptions; + + +import allchive.server.core.error.BaseErrorException; +import allchive.server.domain.domains.search.exception.SearchErrorCode; + +public class LatestSearchNotFoundException extends BaseErrorException { + + public static final BaseErrorException EXCEPTION = new LatestSearchNotFoundException(); + + private LatestSearchNotFoundException() { + super(SearchErrorCode.LATEST_SEARCH_NOT_FOUND); + } +} diff --git a/Domain/src/main/java/allchive/server/domain/domains/search/repository/LatestSearchRepository.java b/Domain/src/main/java/allchive/server/domain/domains/search/repository/LatestSearchRepository.java index 20ca1d86..aa8738d3 100644 --- a/Domain/src/main/java/allchive/server/domain/domains/search/repository/LatestSearchRepository.java +++ b/Domain/src/main/java/allchive/server/domain/domains/search/repository/LatestSearchRepository.java @@ -3,10 +3,15 @@ import allchive.server.domain.domains.search.domain.LatestSearch; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface LatestSearchRepository extends JpaRepository { List findAllByUserIdOrderByCreatedAt(Long userId); void deleteAllByUserId(Long userId); + + Optional findByIdAndUserId(Long latestSearchId, Long userId); + + void deleteByIdAndUserId(Long latestSearchId, Long userId); } diff --git a/Domain/src/main/java/allchive/server/domain/domains/search/validator/LatestSearchValidator.java b/Domain/src/main/java/allchive/server/domain/domains/search/validator/LatestSearchValidator.java new file mode 100644 index 00000000..05b91078 --- /dev/null +++ b/Domain/src/main/java/allchive/server/domain/domains/search/validator/LatestSearchValidator.java @@ -0,0 +1,16 @@ +package allchive.server.domain.domains.search.validator; + + +import allchive.server.core.annotation.Validator; +import allchive.server.domain.domains.search.adaptor.LatestSearchAdaptor; +import lombok.RequiredArgsConstructor; + +@Validator +@RequiredArgsConstructor +public class LatestSearchValidator { + private final LatestSearchAdaptor latestSearchAdaptor; + + public void validateExistByIdAndUserId(Long latestSearchId, Long userId) { + latestSearchAdaptor.findByIdAndUserId(latestSearchId, userId); + } +}