Skip to content

Commit

Permalink
[FEAT] 추천 링크로 부터 픽 생성 API 구현 (#737)
Browse files Browse the repository at this point in the history
* feat: 픽 존재여부 확인을 위한 메소드 구현

* feat: 폴더 id로 조회 메소드 구현

* feat: 추천 링크로 픽 생성 api 구현

* refactor: 컨트롤러 응답 타입 수정

* feat: 응답 타입에서 부모 폴더 정보 제거

* feat: 픽 추가시 이벤트 발행
  • Loading branch information
Gyaak authored Dec 11, 2024
1 parent 164246d commit beab617
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import techpick.api.application.pick.dto.PickApiRequest;
import techpick.api.application.pick.dto.PickApiResponse;
import techpick.api.application.pick.dto.PickSliceResponse;
import techpick.api.domain.folder.service.FolderService;
import techpick.api.domain.pick.dto.PickResult;
import techpick.api.domain.pick.exception.ApiPickException;
import techpick.api.domain.pick.service.PickBulkService;
Expand All @@ -50,6 +51,7 @@ public class PickApiController {
private final PickSearchService pickSearchService;
private final PickBulkService pickBulkService;
private final EventMessenger eventMessenger;
private final FolderService folderService;

@GetMapping
@Operation(summary = "폴더 리스트 내 픽 리스트 조회", description = "해당 폴더 리스트 각각의 픽 리스트를 조회합니다.")
Expand All @@ -65,8 +67,8 @@ public ResponseEntity<List<PickApiResponse.FolderPickListWithViewCount>> getFold
);
return ResponseEntity.ok(
folderPickList.stream()
.map(pickApiMapper::toApiFolderPickListWithViewCount)
.toList());
.map(pickApiMapper::toApiFolderPickListWithViewCount)
.toList());
}

@GetMapping("/search")
Expand Down Expand Up @@ -112,8 +114,8 @@ public ResponseEntity<List<PickApiResponse.Pick>> searchPick(
pickApiMapper.toSearchCommand(userId, request));

List<PickApiResponse.Pick> pickResponseList = pickList.stream()
.map(pickApiMapper::toApiResponse)
.toList();
.map(pickApiMapper::toApiResponse)
.toList();
return ResponseEntity.ok(pickResponseList);
}

Expand Down Expand Up @@ -154,9 +156,9 @@ public ResponseEntity<PickApiResponse.PickExists> doesUserHasPickWithGivenUrl(
@LoginUserId Long userId, @RequestParam String link
) {
var response = pickService.findPickUrl(userId, link)
.map(pickApiMapper::toApiResponse)
.map(pick -> new PickApiResponse.PickExists(true, pick))
.orElseGet(() -> new PickApiResponse.PickExists(false, null));
.map(pickApiMapper::toApiResponse)
.map(pick -> new PickApiResponse.PickExists(true, pick))
.orElseGet(() -> new PickApiResponse.PickExists(false, null));
return ResponseEntity.ok(response);
}

Expand Down Expand Up @@ -199,6 +201,32 @@ public ResponseEntity<PickApiResponse.Pick> savePick(@LoginUserId Long userId,
return ResponseEntity.ok(response);
}

@PostMapping("/recommend")
@Operation(
summary = "추천 링크로 픽 생성",
description = "추천 링크로 픽을 생성합니다. 이미 픽으로 등록된 링크의 경우 기존 픽 정보를 응답으로 보냅니다."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "픽 생성 성공"),
@ApiResponse(responseCode = "403", description = "접근할 수 없는 폴더")
})
public ResponseEntity<PickApiResponse.CreateFromRecommend> savePickFromRecommend(@LoginUserId Long userId,
@Valid @RequestBody PickApiRequest.Create request) {
boolean existPick;
PickResult.Pick result;
if (pickService.existPickByUrl(userId, request.linkInfo().url())) {
existPick = true;
result = pickService.getPickUrl(userId, request.linkInfo().url());
} else {
existPick = false;
var command = pickApiMapper.toCreateCommand(userId, request);
result = pickService.saveNewPick(command);
}
var event = new PickCreateEvent(userId, result.id(), result.linkInfo().url());
eventMessenger.send(event);
return ResponseEntity.ok(new PickApiResponse.CreateFromRecommend(existPick, result));
}

@PatchMapping
@Operation(summary = "픽 내용 수정", description = "픽 내용(제목, 메모)을 수정합니다.")
@ApiResponses(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.List;

import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import techpick.api.domain.link.dto.LinkInfo;
import techpick.api.domain.pick.dto.PickResult;

Expand Down Expand Up @@ -53,4 +52,10 @@ public record PickExists(
PickApiResponse.Pick pick
) {
}

public record CreateFromRecommend(
boolean exist,
PickResult.Pick pick
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import techpick.api.annotation.LoginUserIdDistributedLock;
import techpick.api.domain.folder.dto.FolderCommand;
import techpick.api.domain.folder.dto.FolderMapper;
import techpick.api.domain.folder.dto.FolderResult;
import techpick.api.domain.folder.exception.ApiFolderException;
import techpick.api.infrastructure.folder.FolderDataHandler;
import techpick.api.annotation.LoginUserIdDistributedLock;
import techpick.api.infrastructure.pick.PickDataHandler;
import techpick.api.infrastructure.sharedFolder.SharedFolderDataHandler;
import techpick.core.model.folder.Folder;
Expand Down Expand Up @@ -62,6 +62,13 @@ public List<FolderResult> getBasicFolderList(Long userId) {
.toList();
}

@Transactional(readOnly = true)
public FolderResult getFolder(Long userId, Long folderId) {
Folder folder = folderDataHandler.getFolder(folderId);
validateFolderAccess(userId, folder);
return folderMapper.toResult(folder);
}

/**
* 생성하려는 폴더가 미분류폴더, 휴지통이 아닌지 검증합니다.
* */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
import techpick.api.domain.ranking.service.RankingService;
import techpick.api.domain.tag.exception.ApiTagException;
import techpick.api.infrastructure.folder.FolderDataHandler;
import techpick.api.infrastructure.link.LinkDataHandler;
import techpick.api.infrastructure.pick.PickDataHandler;
import techpick.api.infrastructure.tag.TagDataHandler;
import techpick.core.dto.UrlWithCount;
import techpick.core.model.folder.Folder;
import techpick.core.model.folder.FolderType;
import techpick.core.model.link.Link;
import techpick.core.model.pick.Pick;
import techpick.core.model.tag.Tag;

Expand All @@ -40,6 +42,13 @@ public class PickService {
private final PickMapper pickMapper;
private final FolderDataHandler folderDataHandler;
private final RankingService rankingService;
private final LinkDataHandler linkDataHandler;

@Transactional(readOnly = true)
public boolean existPickByUrl(Long userId, String url) {
Link link = linkDataHandler.getLink(url);
return pickDataHandler.existsByUserIdAndLink(userId, link);
}

@Transactional(readOnly = true)
public PickResult.Pick getPick(PickCommand.Read command) {
Expand All @@ -51,7 +60,6 @@ public PickResult.Pick getPick(PickCommand.Read command) {
@Transactional(readOnly = true)
public PickResult.Pick getPickUrl(Long userId, String url) {
Pick pick = pickDataHandler.getPickUrl(userId, url);
validatePickAccess(userId, pick.getId());
return pickMapper.toPickResult(pick);
}

Expand All @@ -69,17 +77,17 @@ public List<PickResult.Pick> getFolderChildPickList(Long userId, Long folderId)
List<Pick> pickList = pickDataHandler.getPickListPreservingOrder(folder.getChildPickIdOrderedList());

return pickList.stream()
.map(pickMapper::toPickResult)
.toList();
.map(pickMapper::toPickResult)
.toList();
}

// 폴더 리스트가 넘어오면, 각 폴더 내부에 있는 픽 리스트 조회
@Transactional(readOnly = true)
public List<PickResult.FolderPickWithViewCountList> getFolderListChildPickList(PickCommand.ReadList command) {
return command.folderIdList().stream()
.peek(folderId -> validateFolderAccess(command.userId(), folderId)) // 폴더 접근 유효성 검사
.map(this::getFolderChildPickResultList)
.toList();
.peek(folderId -> validateFolderAccess(command.userId(), folderId)) // 폴더 접근 유효성 검사
.map(this::getFolderChildPickResultList)
.toList();
}

@LoginUserIdDistributedLock
Expand Down Expand Up @@ -141,20 +149,20 @@ private PickResult.FolderPickWithViewCountList getFolderChildPickResultList(Long
// 여기서 pick 주간 인기 데이터 반환
Map<String, UrlWithCount> viewCountMap
= rankingService.getUrlRanking(10)
.weeklyUrlViewRanking().stream()
.collect(Collectors.toMap(UrlWithCount::url, Function.identity()));
.weeklyUrlViewRanking().stream()
.collect(Collectors.toMap(UrlWithCount::url, Function.identity()));

List<PickResult.PickWithViewCount> pickResultList
= pickList.stream()
.map(pickMapper::toPickResult)
.map(pickResult -> {
var urlWithCount = viewCountMap.get(pickResult.linkInfo().url());
if (Objects.isNull(urlWithCount)) {
return pickMapper.toPickResultWithViewCount(pickResult, false, null);
}
return pickMapper.toPickResultWithViewCount(pickResult, true, urlWithCount.count());
})
.toList();
.map(pickMapper::toPickResult)
.map(pickResult -> {
var urlWithCount = viewCountMap.get(pickResult.linkInfo().url());
if (Objects.isNull(urlWithCount)) {
return pickMapper.toPickResultWithViewCount(pickResult, false, null);
}
return pickMapper.toPickResultWithViewCount(pickResult, true, urlWithCount.count());
})
.toList();
return pickMapper.toPickResultList(folderId, pickResultList);
}

Expand Down

0 comments on commit beab617

Please sign in to comment.