Skip to content

Comments

feat(#345): 내가 찜한 브랜드 및 캠페인 조회 추가#352

Merged
ParkJiYeoung8297 merged 1 commit intodevfrom
feat/#345-get-liked-campaign-brand
Feb 9, 2026
Merged

feat(#345): 내가 찜한 브랜드 및 캠페인 조회 추가#352
ParkJiYeoung8297 merged 1 commit intodevfrom
feat/#345-get-liked-campaign-brand

Conversation

@ParkJiYeoung8297
Copy link
Contributor

@ParkJiYeoung8297 ParkJiYeoung8297 commented Feb 9, 2026

Summary

내가 찜한 브랜드 및 캠페인 조회 추가

Changes

  • 내가 찜한 브랜드 조회
  • 내가 찜한 캠페인 조회
  • 각 조회별 옵션 필터들 기능 구현

Type of Change

  • Bug fix (기존 기능에 영향을 주지 않는 버그 수정)
  • New feature (기존 기능에 영향을 주지 않는 새로운 기능 추가)
  • Breaking change (기존 기능에 영향을 주는 수정)
  • Refactoring (기능 변경 없는 코드 개선)
  • Documentation (문서 수정)
  • Chore (빌드, 설정 등 기타 변경)
  • Release (develop → main 배포)

Related Issues

#345

참고 사항

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ParkJiYeoung8297, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 풀 리퀘스트는 사용자가 찜한 브랜드와 캠페인 목록을 조회할 수 있는 기능을 추가하여 사용자 경험을 향상시키는 것을 목표로 합니다. 사용자는 매칭률, 인기순, 최신순 등 다양한 기준으로 찜 목록을 정렬하여 개인화된 정보를 얻을 수 있습니다. 이를 통해 사용자는 관심 있는 브랜드와 캠페인을 더욱 쉽게 탐색하고 관리할 수 있게 됩니다.

Highlights

  • 찜한 목록 조회 기능 추가: 사용자가 찜한 브랜드 및 캠페인 목록을 조회할 수 있는 새로운 기능이 추가되었습니다.
  • 새로운 서비스 및 컨트롤러 엔드포인트: UserFavoriteService가 추가되어 찜한 목록 조회 로직을 처리하며, UserController/me/favorites/brand/me/favorites/campaign 두 개의 새로운 API 엔드포인트가 추가되었습니다.
  • QueryDSL 기반 리포지토리 구현: 찜한 브랜드 및 캠페인 데이터를 효율적으로 조회하고 정렬하기 위해 QueryDSL을 활용한 UserFavoriteBrandRepositoryImplUserFavoriteCampaignRepositoryImpl이 구현되었습니다.
  • 다양한 정렬 옵션 지원: 찜한 브랜드는 매칭률, 인기순, 최신순으로, 찜한 캠페인은 매칭률, 인기순, 원고료 높은 순, D-Day 임박 순으로 정렬할 수 있는 기능이 제공됩니다.
  • 상세 정보 및 DTO 정의: 찜한 브랜드에 대한 태그, 매칭률, 좋아요 수와 찜한 캠페인에 대한 D-Day, 원고료, 매칭률, 좋아요 수 등 상세 정보를 포함하는 새로운 DTO들이 정의되었습니다.
Changelog
  • src/main/java/com/example/RealMatch/brand/domain/repository/BrandDescribeTagRepository.java
    • findAllByBrandIdIn 메서드가 추가되어 여러 브랜드 ID에 해당하는 태그 목록을 조회할 수 있게 되었습니다.
  • src/main/java/com/example/RealMatch/user/application/service/UserFavoriteService.java
    • 새로운 서비스 파일로, 사용자가 찜한 브랜드와 캠페인 목록을 조회하는 핵심 비즈니스 로직을 포함합니다.
    • 찜한 브랜드 조회 시 태그 정보를 추가로 가져와 DTO에 설정하는 로직이 포함되어 있습니다.
  • src/main/java/com/example/RealMatch/user/domain/entity/enums/FavoriteType.java
    • BRAND, CAMPAIGN 두 가지 유형을 가지는 FavoriteType enum이 새로 추가되었습니다.
  • src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandQueryRepository.java
    • 찜한 브랜드 조회를 위한 쿼리 인터페이스가 정의되었습니다.
  • src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandRepositoryImpl.java
    • UserFavoriteBrandQueryRepository의 QueryDSL 구현체로, 사용자 ID와 정렬 타입에 따라 찜한 브랜드 목록을 조회합니다.
    • 매칭률, 인기순, 최신순으로 정렬하는 로직과 브랜드 좋아요 수, 매칭률을 계산하는 표현식이 포함되어 있습니다.
  • src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignQueryRepository.java
    • 찜한 캠페인 조회를 위한 쿼리 인터페이스가 정의되었습니다.
  • src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignRepositoryImpl.java
    • UserFavoriteCampaignQueryRepository의 QueryDSL 구현체로, 사용자 ID와 정렬 타입에 따라 찜한 캠페인 목록을 조회합니다.
    • 매칭률, 인기순, 원고료 높은 순, D-Day 임박 순으로 정렬하는 로직과 캠페인 좋아요 수, D-Day, 매칭률을 계산하는 표현식이 포함되어 있습니다.
  • src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java
    • UserFavoriteService가 주입되었고, /me/favorites/brand/me/favorites/campaign 두 개의 GET 엔드포인트가 추가되었습니다.
    • 각 엔드포인트는 사용자가 찜한 브랜드 및 캠페인 목록을 정렬 옵션과 함께 조회할 수 있도록 합니다.
  • src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandDto.java
    • 찜한 브랜드의 ID, 이름, 로고 URL, 매칭률, 좋아요 수, 태그 목록을 포함하는 DTO가 추가되었습니다.
  • src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandListResponseDto.java
    • 찜한 브랜드 목록과 총 개수를 포함하는 응답 DTO가 추가되었습니다.
  • src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignDto.java
    • 찜한 캠페인의 ID, 제목, D-Day, 원고료, 할당량, 매칭률, 좋아요 수, 브랜드 ID, 이름, 로고 URL을 포함하는 DTO가 추가되었습니다.
  • src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignListResponseDto.java
    • 찜한 캠페인 목록과 총 개수를 포함하는 응답 DTO가 추가되었습니다.
  • src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java
    • 새로 추가된 찜한 브랜드 및 캠페인 조회 API에 대한 Swagger 문서가 추가되었습니다. 정렬 기준 및 기본값에 대한 설명이 포함되어 있습니다.
Activity
  • 이 풀 리퀘스트에는 현재 기록된 활동이 없습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ParkJiYeoung8297 ParkJiYeoung8297 self-assigned this Feb 9, 2026
@ParkJiYeoung8297 ParkJiYeoung8297 merged commit de93924 into dev Feb 9, 2026
1 check passed
@ParkJiYeoung8297 ParkJiYeoung8297 deleted the feat/#345-get-liked-campaign-brand branch February 9, 2026 20:11
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 사용자가 찜한 브랜드와 캠페인 목록을 조회하는 새로운 API를 추가하는군요. 전반적으로 N+1 문제를 피하기 위해 IN 절을 사용하고, QueryDSL을 활용하여 정렬 기능을 구현한 점이 좋습니다. 몇 가지 개선점을 제안합니다. DTO를 불변(immutable)하게 만들고, 컨트롤러에서 기본 파라미터 처리를 개선하며, QueryDSL 표현식을 더 타입-세이프하게 작성하는 것을 고려해볼 수 있습니다. 자세한 내용은 각 파일에 남긴 코멘트를 참고해주세요.

Comment on lines +60 to +67
brands.forEach(dto ->
dto.setTags(tagMap.getOrDefault(dto.getBrandId(), List.of()))
);

return FavoriteBrandListResponseDto.builder()
.count(brands.size())
.brands(brands)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

FavoriteBrandDto를 불변으로 변경하는 것과 연관하여, forEach를 사용해 DTO 리스트를 직접 수정하는 대신, stream().map()을 사용하여 태그 정보가 포함된 새로운 DTO 객체를 생성하는 것이 좋습니다. 이는 코드의 불변성을 유지하고 가독성을 높이는 데 도움이 됩니다.

Suggested change
brands.forEach(dto ->
dto.setTags(tagMap.getOrDefault(dto.getBrandId(), List.of()))
);
return FavoriteBrandListResponseDto.builder()
.count(brands.size())
.brands(brands)
.build();
final List<FavoriteBrandDto> brandsWithTags = brands.stream()
.map(dto -> new FavoriteBrandDto(
dto.getBrandId(),
dto.getBrandName(),
dto.getBrandLogoUrl(),
dto.getMatchingRatio(),
dto.getLikeCount(),
tagMap.getOrDefault(dto.getBrandId(), List.of())
))
.toList();
return FavoriteBrandListResponseDto.builder()
.count(brandsWithTags.size())
.brands(brandsWithTags)
.build();
References
  1. Avoid using @Setter on domain or DTO objects to prevent uncontrolled external modifications. If mutation is needed, provide specific methods for it.

Comment on lines +107 to +113
private Expression<Long> brandMatchingRatioExpression() {
return Expressions.numberTemplate(
Long.class,
"COALESCE({0}, 0)",
MATCH_BRAND_HISTORY.matchingRatio
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Expressions.numberTemplate를 사용하여 COALESCE 함수를 문자열로 직접 사용하고 있습니다. QueryDSL이 제공하는 타입-세이프한 coalesce() 메소드를 사용하면 코드의 가독성과 데이터베이스 이식성을 높일 수 있습니다.

    private Expression<Long> brandMatchingRatioExpression() {
        return MATCH_BRAND_HISTORY.matchingRatio.coalesce(0L);
    }

Comment on lines +148 to +154
private Expression<Long> campaignMatchingRatioExpression() {
return Expressions.numberTemplate(
Long.class,
"COALESCE({0}, 0)",
MATCH_CAMPAIGN_HISTORY.matchingRatio
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

brandMatchingRatioExpression와 마찬가지로, COALESCE 함수를 문자열 템플릿 대신 타입-세이프한 coalesce() 메소드로 변경하는 것이 좋습니다.

    private Expression<Long> campaignMatchingRatioExpression() {
        return MATCH_CAMPAIGN_HISTORY.matchingRatio.coalesce(0L);
    }

Comment on lines +143 to +150
@RequestParam(required = false) BrandSortType sort
) {
Long userId = userDetails.getUserId();
return CustomResponse.ok(
userFavoriteService.getMyFavoriteBrands(
userId,
sort == null ? BrandSortType.MATCH_SCORE : sort
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

sort 파라미터의 기본값을 삼항 연산자로 처리하고 있습니다. @RequestParamdefaultValue 속성을 사용하면 코드를 더 간결하게 만들 수 있습니다. Spring이 문자열을 BrandSortType enum으로 자동 변환해줍니다. 이렇게 변경하면 서비스나 리포지토리 레이어에서 불필요한 null 체크 로직을 제거할 수도 있습니다.

Suggested change
@RequestParam(required = false) BrandSortType sort
) {
Long userId = userDetails.getUserId();
return CustomResponse.ok(
userFavoriteService.getMyFavoriteBrands(
userId,
sort == null ? BrandSortType.MATCH_SCORE : sort
)
@RequestParam(defaultValue = "MATCH_SCORE") BrandSortType sort
) {
Long userId = userDetails.getUserId();
return CustomResponse.ok(
userFavoriteService.getMyFavoriteBrands(
userId,
sort
)

Comment on lines +158 to +166
@RequestParam(required = false) CampaignSortType sort
) {
Long userId = userDetails.getUserId();

return CustomResponse.ok(
userFavoriteService.getMyFavoriteCampaigns(
userId,
sort == null ? CampaignSortType.MATCH_SCORE : sort
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

getMyFavoriteBrands 메소드와 마찬가지로, sort 파라미터의 기본값 처리를 @RequestParamdefaultValue 속성을 사용하여 개선할 수 있습니다.

Suggested change
@RequestParam(required = false) CampaignSortType sort
) {
Long userId = userDetails.getUserId();
return CustomResponse.ok(
userFavoriteService.getMyFavoriteCampaigns(
userId,
sort == null ? CampaignSortType.MATCH_SCORE : sort
)
@RequestParam(defaultValue = "MATCH_SCORE") CampaignSortType sort
) {
Long userId = userDetails.getUserId();
return CustomResponse.ok(
userFavoriteService.getMyFavoriteCampaigns(
userId,
sort
)

Comment on lines +21 to +23
public void setTags(List<String> tags) {
this.tags = tags;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

DTO 객체는 가능하면 불변(immutable)으로 유지하는 것이 좋습니다. setTags 메소드는 FavoriteBrandDto를 가변(mutable) 상태로 만듭니다. 이 setter를 제거하고, 모든 필드를 생성자를 통해 초기화하도록 하는 것을 고려해보세요. 이렇게 하면 객체의 상태를 예측하기 쉬워지고 부수효과(side effect)를 줄일 수 있습니다.

References
  1. Avoid using @Setter on domain or DTO objects to prevent uncontrolled external modifications. If mutation is needed, provide specific methods for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant