Skip to content

Commit

Permalink
feat: 회원 가입시 최초 북마크 생성 로직 (#718)
Browse files Browse the repository at this point in the history
* feat: 회원 가입시 최초 북마크 생성 로직

* fix: 픽 쿼리 빈 배열 예외 추가
  • Loading branch information
kimminkyeu authored Dec 10, 2024
1 parent 50621f2 commit 22c3350
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,21 @@ public ResponseEntity<PickSliceResponse<PickApiResponse.Pick>> searchPickPaginat
@Parameter(description = "한 페이지에 가져올 픽 개수", example = "20") @RequestParam(required = false, defaultValue = "20"
) int size
) {
Slice<PickResult.Pick> pickResultList = pickSearchService.searchPickPagination(
pickApiMapper.toSearchPaginationCommand(userId, folderIdList, searchTokenList, tagIdList, cursor, size));
var command = pickApiMapper.toSearchPaginationCommand(
userId, folderIdList, searchTokenList, tagIdList, cursor, size
);

Slice<PickResult.Pick> pickResultList = pickSearchService.searchPickPagination(command);

return ResponseEntity.ok(new PickSliceResponse<>(pickApiMapper.toSliceApiResponse(pickResultList)));
}

/**
* 현재 프론트엔드에서 미사용하는 API 입니다.
*/
@Deprecated
@GetMapping("/search/all")
@Operation(summary = "픽 리스트 검색", description = "페이지네이션 처리 되지 않은 픽 리스트 검색")
@Operation(summary = "[Deprecated] 픽 리스트 검색", description = "페이지네이션 처리 되지 않은 픽 리스트 검색")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공")
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package techpick.api.domain.pick.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

Expand Down Expand Up @@ -31,25 +32,34 @@ public class PickSearchService {

@Transactional(readOnly = true)
public Slice<PickResult.Pick> searchPickPagination(PickCommand.SearchPagination command) {
List<Long> folderIdList = command.folderIdList();
List<Long> tagIdList = command.tagIdList();

List<Long> folderIdList = command.folderIdList();
if (ObjectUtils.isNotEmpty(folderIdList)) {
folderIdList.removeAll(Collections.singletonList(null));
for (Long folderId : folderIdList) {
validateFolderAccess(command.userId(), folderId);
validateFolderRootSearch(folderId);
}
}

List<Long> tagIdList = command.tagIdList();
if (ObjectUtils.isNotEmpty(tagIdList)) {
tagIdList.removeAll(Collections.singletonList(null));
for (Long tagId : tagIdList) {
validateTagAccess(command.userId(), tagId);
}
}

return pickQuery.searchPickPagination(command.userId(), folderIdList,
command.searchTokenList(), command.tagIdList(),
command.cursor(), command.size());
List<String> searchTokenList = command.searchTokenList();
if (ObjectUtils.isNotEmpty(searchTokenList)) {
searchTokenList.removeAll(Arrays.asList("", null));
}

return pickQuery.searchPickPagination(
command.userId(),
folderIdList, searchTokenList, tagIdList,
command.cursor(), command.size()
);
}

@Transactional(readOnly = true)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
package techpick.api.domain.user.service;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import techpick.api.domain.user.service.strategy.StarterFolderStrategy;
import techpick.core.model.folder.Folder;
import techpick.core.model.folder.FolderRepository;
import techpick.core.model.user.User;
import techpick.core.model.user.UserRepository;
import techpick.security.model.OAuth2UserInfo;

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final FolderRepository folderRepository;
private final InitialFolderStrategy initialFolderStrategy;

public UserService(
UserRepository userRepository,
FolderRepository folderRepository,
@Qualifier(RankingBasedStrategy.QUALIFIER) InitialFolderStrategy initialFolderStrategy
) {
this.userRepository = userRepository;
this.folderRepository = folderRepository;
this.initialFolderStrategy = initialFolderStrategy;
}
private final StarterFolderStrategy folderStrategy;

public void createUser(OAuth2UserInfo userInfo) {
if (!userRepository.existsBySocialProviderId(userInfo.getName())) {
Expand All @@ -42,6 +33,6 @@ private void createBasicFolder(User user) {
folderRepository.save(Folder.createEmptyUnclassifiedFolder(user));
folderRepository.save(Folder.createEmptyRecycleBinFolder(user));
var root = folderRepository.save(Folder.createEmptyRootFolder(user));
initialFolderStrategy.initFolder(user, root);
folderStrategy.initFolder(user, root);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package techpick.api.domain.user.service.strategy;

import techpick.core.model.folder.Folder;
import techpick.core.model.user.User;

public interface ContentInitStrategy {
void initContent(User user, Folder parentFolder);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package techpick.api.domain.user.service.strategy;

import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InitStrategyConfiguration {

@Bean
public List<ContentInitStrategy> contentInitStrategies(
@Qualifier("app-manual") ContentInitStrategy strategy1,
@Qualifier("hot-contents") ContentInitStrategy strategy2
) {
return List.of(strategy1, strategy2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package techpick.api.domain.user.service.strategy;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.api.domain.link.service.LinkService;
import techpick.api.domain.pick.dto.PickCommand;
import techpick.api.domain.pick.service.PickService;
import techpick.core.model.folder.Folder;
import techpick.core.model.user.User;

@Slf4j
@Component
@Qualifier("app-manual")
@RequiredArgsConstructor
public class ManualInitStrategy implements ContentInitStrategy {

private final PickService pickService;
private final LinkService linkService;

private final List<String> MANUAL_URLS = List.of(
"https://positive-airboat-4de.notion.site/15841a7fba6580c59591e2d5d1c2414b?pvs=4",
"https://positive-airboat-4de.notion.site/15841a7fba65808b8636e15e6c6d9679?pvs=4",
"https://positive-airboat-4de.notion.site/15841a7fba65809d89a6dceb89060f70?pvs=4",
"https://positive-airboat-4de.notion.site/15841a7fba6580f78caee50c069a1247?pvs=4"
);

@Override
public void initContent(User user, Folder parentFolder) {
for (var url : MANUAL_URLS) {
var linkInfo = linkService.saveLinkAndUpdateOgTag(url);
var command = new PickCommand.Create(
user.getId(), linkInfo.title(), new ArrayList<>(), parentFolder.getId(), linkInfo
);
pickService.saveNewPick(command);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package techpick.api.domain.user.service;
package techpick.api.domain.user.service.strategy;

import java.time.LocalDate;
import java.util.ArrayList;
Expand All @@ -17,35 +17,29 @@
import techpick.api.infrastructure.ranking.RankingApi;
import techpick.core.dto.UrlWithCount;
import techpick.core.model.folder.Folder;
import techpick.core.model.folder.FolderRepository;
import techpick.core.model.user.User;

@Slf4j
@Component
@Qualifier("hot-contents")
@RequiredArgsConstructor
@Qualifier(RankingBasedStrategy.QUALIFIER)
public class RankingBasedStrategy implements InitialFolderStrategy {
public class RankingInitStrategy implements ContentInitStrategy {

public static final String QUALIFIER = "ranking";

private static final String MONTHLY_FOLDER_NAME = "시작하기";
private static final Integer LOAD_LIMIT = 15;

private final FolderRepository folderRepository;
private final RankingApi rankingApi;
private final PickService pickService;
private final LinkService linkService;

@Override
public void initFolder(User user, Folder root) {
var monthlyFolder = folderRepository.save(Folder.createEmptyGeneralFolder(user, root, MONTHLY_FOLDER_NAME));
public void initContent(User user, Folder parentFolder) {
var currentDay = LocalDate.now();
var before1Day = currentDay.minusDays(1);
var before30Days = currentDay.minusDays(30);
var monthlyRanking = rankingApi
.getUrlRankingByViewCount(before30Days, before1Day, LOAD_LIMIT)
.getBody();
savePickFromRankingList(user.getId(), monthlyRanking, monthlyFolder.getId());
savePickFromRankingList(user.getId(), monthlyRanking, parentFolder.getId());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package techpick.api.domain.user.service.strategy;

import java.util.List;

import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.api.domain.folder.dto.FolderCommand;
import techpick.api.infrastructure.folder.FolderDataHandler;
import techpick.core.model.folder.Folder;
import techpick.core.model.user.User;

@Slf4j
@Component
@RequiredArgsConstructor
public class StarterFolderStrategy {

private static final String FOLDER_NAME = "시작하기";

private final FolderDataHandler folderDataHandler;

private final List<ContentInitStrategy> contentStrategies;

public void initFolder(User user, Folder parentFolder) {
var command = new FolderCommand.Create(user.getId(), FOLDER_NAME, parentFolder.getId());
var folder = folderDataHandler.saveFolder(command);
for (var strategy : contentStrategies) {
strategy.initContent(user, folder);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ public List<PickResult.Pick> getPickList(Long userId, List<Long> folderIdList) {
}

List<Long> pickIdList = folderIdList.stream()
.flatMap(folderId -> getChildPickIdOrderedList(folderId).stream())
.toList();
.flatMap(folderId -> getChildPickIdOrderedList(folderId).stream())
.toList();

if (pickIdList.isEmpty()) {
return List.of();
}

String orderListStr = pickIdList.stream()
.map(String::valueOf)
.collect(Collectors.joining(", "));
.map(String::valueOf)
.collect(Collectors.joining(", "));

Expression<Integer> orderByField = Expressions.template(Integer.class,
"FIELD({0}, " + orderListStr + ")", pick.id);
Expand Down Expand Up @@ -86,8 +86,10 @@ public List<PickResult.Pick> searchPick(Long userId, List<Long> folderIdList, Li
}

// TODO: 본인 픽이 아닌 다른 사람의 픽도 검색하고 싶다면 userId 부분 제거
public Slice<PickResult.Pick> searchPickPagination(Long userId, List<Long> folderIdList, List<String> searchTokenList,
List<Long> tagIdList, Long cursor, int size) {
public Slice<PickResult.Pick> searchPickPagination(
Long userId, List<Long> folderIdList, List<String> searchTokenList,
List<Long> tagIdList, Long cursor, int size
) {

List<PickResult.Pick> pickList = jpaQueryFactory
.select(pickResultFields()) // dto로 반환
Expand Down Expand Up @@ -166,18 +168,19 @@ private BooleanExpression searchTokenListCondition(List<String> searchTokenList)
}

return searchTokenList.stream()
.map(token -> {
StringTokenizer stringTokenizer = new StringTokenizer(token);
BooleanExpression combinedCondition = null;
while (stringTokenizer.hasMoreTokens()) {
String part = stringTokenizer.nextToken().toLowerCase();
BooleanExpression condition = pick.title.lower().like("%" + part + "%");
combinedCondition = (combinedCondition == null) ? condition : combinedCondition.and(condition);
}
return combinedCondition;
})
.reduce(BooleanExpression::and)
.orElse(null);
.map(token -> {
StringTokenizer stringTokenizer = new StringTokenizer(token);
BooleanExpression combinedCondition = null;
while (stringTokenizer.hasMoreTokens()) {
String part = stringTokenizer.nextToken().toLowerCase();
BooleanExpression condition = pick.title.lower().like("%" + part + "%");
combinedCondition =
(combinedCondition == null) ? condition : combinedCondition.and(condition);
}
return combinedCondition;
})
.reduce(BooleanExpression::and)
.orElse(null);
}

private BooleanExpression tagIdListCondition(List<Long> tagIdList) {
Expand All @@ -189,7 +192,7 @@ private BooleanExpression tagIdListCondition(List<Long> tagIdList) {
.selectFrom(pickTag)
.where(
pickTag.pick.id.eq(pick.id)
.and(pickTag.tag.id.in(tagIdList)))
.and(pickTag.tag.id.in(tagIdList)))
.groupBy(pickTag.pick.id)
.having(pickTag.tag.id.count().eq((long)tagIdList.size()))
.exists();
Expand Down

0 comments on commit 22c3350

Please sign in to comment.