Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import turip.common.exception.ErrorResponse;
import turip.content.controller.dto.response.content.ContentsDetailWithLoadableResponse;
import turip.favorite.controller.dto.request.FavoriteContentRequest;
import turip.favorite.controller.dto.response.FavoriteContentCountResponse;
import turip.favorite.controller.dto.response.FavoriteContentResponse;
import turip.favorite.service.FavoriteContentService;

Expand Down Expand Up @@ -261,6 +262,76 @@ public ResponseEntity<ContentsDetailWithLoadableResponse> readMyFavoriteContents
return ResponseEntity.ok(response);
}

@Operation(
summary = "내 콘텐츠 찜(북마크) 수 조회 api",
description = "콘텐츠 찜(북마크)이 몇 개인지 반환한다."
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공 예시",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = FavoriteContentCountResponse.class),
examples = @ExampleObject(
name = "success",
summary = "북마크 수 조회 성공",
value = """
{
"count": 13
}
"""
)
)
),
@ApiResponse(
responseCode = "401",
description = "실패 예시",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(
name = "access token expired",
summary = "만료된 access token",
value = """
{
"tag": "ACCESS_TOKEN_EXPIRED",
"message": "access token이 만료됐습니다."
}
"""
),
@ExampleObject(
name = "invalid signature access token",
summary = "서명값이 올바르지 않은 access token",
value = """
{
"tag": "ACCESS_TOKEN_SIGNATURE_INVALID",
"message": "access token이 위조됐습니다."
}
"""
),
@ExampleObject(
name = "unauthorized",
summary = "알 수 없는 이유로 인증 실패",
value = """
{
"tag": "UNAUTHORIZED",
"message": "토큰 기반 인증에 실패했습니다."
}
"""
)
}
)
)
})
@GetMapping("/count")
public ResponseEntity<FavoriteContentCountResponse> readBookmarkCount(
@Parameter(hidden = true) @AuthAccount Account account) {
FavoriteContentCountResponse response = favoriteContentService.countByAccount(account);
return ResponseEntity.ok(response);
}

@Operation(
summary = "북마크 삭제 api",
description = "컨텐츠 북마크를 취소한다."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package turip.favorite.controller.dto.response;

public record FavoriteContentCountResponse(int count) {

public static FavoriteContentCountResponse from(int count) {
return new FavoriteContentCountResponse(count);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,7 @@ Slice<Content> findMyFavoriteContentsByAccountId(

boolean existsByAccount(Account account);

int countByAccount(Account account);

void deleteByAccount(Account account);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import turip.content.repository.ContentRepository;
import turip.content.service.ContentPlaceService;
import turip.favorite.controller.dto.request.FavoriteContentRequest;
import turip.favorite.controller.dto.response.FavoriteContentCountResponse;
import turip.favorite.controller.dto.response.FavoriteContentResponse;
import turip.favorite.domain.FavoriteContent;
import turip.favorite.repository.FavoriteContentRepository;
Expand Down Expand Up @@ -64,6 +65,11 @@ public boolean existsByAccount(Account account) {
return favoriteContentRepository.existsByAccount(account);
}

public FavoriteContentCountResponse countByAccount(Account account) {
int count = favoriteContentRepository.countByAccount(account);
return FavoriteContentCountResponse.from(count);
}

@Transactional
public void remove(Account account, Long contentId) {
Content content = contentRepository.findById(contentId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,42 @@ void readMyFavoriteContents_success() {
.body("loadable", is(false));
}
}

@DisplayName("/api/v1/bookmarks/count GET 콘텐츠 찜 수 조회 테스트")
@Nested
class ReadBookmarkCount {

@DisplayName("성공 시 200 OK 코드와 북마크 수를 반환한다")
@Test
void readBookmarkCount1() {
// given
jdbcTemplate.update(
"INSERT INTO creator (profile_image, channel_name) VALUES ('https://image.example.com/creator.jpg', 'Travel')");
jdbcTemplate.update(
"INSERT INTO country (name, image_url) VALUES ('대한민국', 'https://image.example.com/korea.jpg')");
jdbcTemplate.update(
"INSERT INTO city (name, country_id, province_id, image_url) VALUES ('서울', 1, null,'https://image.example.com/seoul.jpg')");
Long accountId = testDataHelper.insertAccount();
jdbcTemplate.update("INSERT INTO guest (account_id, device_fid) VALUES (?, 'testDeviceFid')", accountId);

// 3개의 콘텐츠와 북마크 생성
for (int i = 1; i <= 3; i++) {
jdbcTemplate.update(
"INSERT INTO content (creator_id, city_id, url, title, uploaded_date) VALUES (1, 1, 'https://youtube.com/watch?v=test"
+ i + "', '테스트 영상" + i + "', '2025-08-0" + i + "')");
jdbcTemplate.update(
"INSERT INTO favorite_content (account_id, content_id, created_at) VALUES (?, " + i
+ ", CURRENT_TIMESTAMP)",
accountId);
}

// when & then
RestAssured.given().port(port)
.header("device-fid", "testDeviceFid")
.when().get("/api/v1/bookmarks/count")
.then()
.statusCode(200)
.body("count", is(3));
}
}
}