Skip to content

Commit 96b6828

Browse files
authored
Develop (#77)
* feature & fix : 유저 최근 검색어 API 구현
1 parent e674d73 commit 96b6828

File tree

4 files changed

+133
-27
lines changed

4 files changed

+133
-27
lines changed

src/main/java/ita/tinybite/domain/party/controller/PartyController.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import ita.tinybite.domain.party.dto.response.PartyListResponse;
1717
import ita.tinybite.domain.party.entity.PartyParticipant;
1818
import ita.tinybite.domain.party.enums.PartyCategory;
19+
import ita.tinybite.domain.party.service.PartySearchService;
1920
import ita.tinybite.domain.party.service.PartyService;
2021
import ita.tinybite.global.response.APIResponse;
2122
import jakarta.validation.Valid;
@@ -34,7 +35,7 @@
3435
public class PartyController {
3536

3637
private final PartyService partyService;
37-
private final JwtTokenProvider jwtTokenProvider;
38+
private final PartySearchService partySearchService;
3839

3940

4041
@Operation(
@@ -459,6 +460,43 @@ public APIResponse<PartyQueryListResponse> getParty(
459460
@RequestParam(defaultValue = "0") int page,
460461
@RequestParam(defaultValue = "20") int size
461462
) {
462-
return APIResponse.success(partyService.searchParty(q, category, page, size));
463+
return APIResponse.success(partySearchService.searchParty(q, category, page, size));
464+
}
465+
466+
@Operation(
467+
summary = "최근 검색어 조회",
468+
description = """
469+
검색 돋보기 클릭 시 보이는 최근 검색어를 조회합니다. <br>
470+
한 번에 20개가 조회됩니다.
471+
"""
472+
)
473+
@GetMapping("/search/log")
474+
public APIResponse<List<String>> getRecentLog() {
475+
return APIResponse.success(partySearchService.getLog());
476+
}
477+
478+
@Operation(
479+
summary = "특정 최근 검색어 삭제",
480+
description = """
481+
최근 검색어에서 특정 검색어를 삭제합니다. <br>
482+
이때 검색어에 대한 Id값은 없고, 최근 검색어 자체를 keyword에 넣어주시면 됩니다.
483+
"""
484+
)
485+
@DeleteMapping("/search/log/{keyword}")
486+
public APIResponse<?> deleteRecentLog(@PathVariable String keyword) {
487+
partySearchService.deleteLog(keyword);
488+
return APIResponse.success();
489+
}
490+
491+
@Operation(
492+
summary = "모든 최근 검색어 삭제",
493+
description = """
494+
특정 유저에 대한 모든 최근 검색어를 삭제합니다.
495+
"""
496+
)
497+
@DeleteMapping("/search/log")
498+
public APIResponse<?> deleteRecentLogAll() {
499+
partySearchService.deleteAllLog();
500+
return APIResponse.success();
463501
}
464502
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package ita.tinybite.domain.party.service;
2+
3+
import ita.tinybite.domain.auth.service.SecurityProvider;
4+
import ita.tinybite.domain.party.dto.request.PartyQueryListResponse;
5+
import ita.tinybite.domain.party.dto.response.PartyCardResponse;
6+
import ita.tinybite.domain.party.entity.Party;
7+
import ita.tinybite.domain.party.enums.ParticipantStatus;
8+
import ita.tinybite.domain.party.enums.PartyCategory;
9+
import ita.tinybite.domain.party.repository.PartyParticipantRepository;
10+
import ita.tinybite.domain.party.repository.PartyRepository;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Page;
13+
import org.springframework.data.domain.PageRequest;
14+
import org.springframework.data.domain.Pageable;
15+
import org.springframework.data.redis.core.StringRedisTemplate;
16+
import org.springframework.stereotype.Service;
17+
import org.springframework.transaction.annotation.Transactional;
18+
19+
import java.util.List;
20+
21+
@Service
22+
@Transactional(readOnly = true)
23+
@RequiredArgsConstructor
24+
public class PartySearchService {
25+
26+
private final PartyRepository partyRepository;
27+
private final PartyParticipantRepository participantRepository;
28+
private final StringRedisTemplate redisTemplate;
29+
private final SecurityProvider securityProvider;
30+
31+
32+
private static final String KEY_PREFIX = "recent_search:";
33+
private String key(Long userId) {
34+
return KEY_PREFIX + userId;
35+
}
36+
37+
// 파티 검색 조회
38+
public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size) {
39+
Long userId = securityProvider.getCurrentUser().getUserId();
40+
41+
// recent_search:{userId}
42+
String key = key(userId);
43+
44+
redisTemplate.opsForZSet().remove(key, q);
45+
redisTemplate.opsForZSet().add(key, q, System.currentTimeMillis());
46+
47+
Pageable pageable = PageRequest.of(page, size);
48+
49+
// category가 없을 시에는 ALL로 처리
50+
Page<Party> result = (category == null || category == PartyCategory.ALL)
51+
? partyRepository.findByTitleContaining(q, pageable)
52+
: partyRepository.findByTitleContainingAndCategory(q, category, pageable);
53+
54+
List<PartyCardResponse> partyCardResponseList = result.stream()
55+
.map(party -> {
56+
int currentParticipants = participantRepository
57+
.countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED);
58+
return PartyCardResponse.from(party, currentParticipants);
59+
})
60+
.toList();
61+
62+
return PartyQueryListResponse.builder()
63+
.parties(partyCardResponseList)
64+
.hasNext(result.hasNext())
65+
.build();
66+
}
67+
68+
69+
// 최근 검색어 20개 조회
70+
public List<String> getLog() {
71+
Long userId = securityProvider.getCurrentUser().getUserId();
72+
return redisTemplate.opsForZSet()
73+
.reverseRange(key(userId), 0, 19)
74+
.stream().toList();
75+
}
76+
77+
public void deleteLog(String keyword) {
78+
Long userId = securityProvider.getCurrentUser().getUserId();
79+
redisTemplate.opsForZSet().remove(key(userId), keyword);
80+
}
81+
82+
public void deleteAllLog() {
83+
Long userId = securityProvider.getCurrentUser().getUserId();
84+
redisTemplate.delete(key(userId));
85+
}
86+
}

src/main/java/ita/tinybite/domain/party/service/PartyService.java

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -668,28 +668,6 @@ private String formatDistanceIfExists(Double distance) {
668668
return distance!= null? DistanceCalculator.formatDistance(distance):null;
669669
}
670670

671-
// 파티 검색 조회
672-
public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size) {
673-
Pageable pageable = PageRequest.of(page, size);
674-
675-
// category가 없을 시에는 ALL로 처리
676-
Page<Party> result = (category == null || category == PartyCategory.ALL)
677-
? partyRepository.findByTitleContaining(q, pageable)
678-
: partyRepository.findByTitleContainingAndCategory(q, category, pageable);
679-
680-
681-
List<PartyCardResponse> partyCardResponseList = result.stream()
682-
.map(party -> {
683-
int currentParticipants = participantRepository
684-
.countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED);
685-
return PartyCardResponse.from(party, currentParticipants);
686-
})
687-
.toList();
688-
689-
return PartyQueryListResponse.builder()
690-
.parties(partyCardResponseList)
691-
.hasNext(result.hasNext())
692-
.build();
693-
}
671+
694672
}
695673

src/main/java/ita/tinybite/global/sms/service/SmsAuthService.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public SmsAuthService(SmsService smsService, RedisTemplate<String, String> redis
2727
this.authCodeGenerator = AuthCodeGenerator.getInstance();
2828
}
2929

30+
private static final String KEY_PREFIX = "sms:";
31+
private String key(String phone) {
32+
return KEY_PREFIX + phone;
33+
}
3034
/**
3135
* 1. 인증코드 생성 <br>
3236
* 2. 주어진 폰번호로 인증코드 전송 <br>
@@ -37,7 +41,7 @@ public void send(String phone) {
3741

3842
String smsAuthCode = authCodeGenerator.getAuthCode();
3943
smsService.send(phone.replaceAll("-", ""), smsAuthCode);
40-
redisTemplate.opsForValue().set(phone, smsAuthCode, EXPIRE_TIME, TimeUnit.MILLISECONDS);
44+
redisTemplate.opsForValue().set(key(phone), smsAuthCode, EXPIRE_TIME, TimeUnit.MILLISECONDS);
4145
}
4246

4347
/**
@@ -48,7 +52,7 @@ public void send(String phone) {
4852
public void check(CheckReqDto req) {
4953
validatePhoneNumber(req.phone());
5054

51-
String authCode = redisTemplate.opsForValue().get(req.phone());
55+
String authCode = redisTemplate.opsForValue().get(key(req.phone()));
5256
if(authCode == null)
5357
throw BusinessException.of(AuthErrorCode.EXPIRED_AUTH_CODE);
5458

0 commit comments

Comments
 (0)