diff --git a/src/main/java/com/example/RealMatch/brand/domain/repository/BrandDescribeTagRepository.java b/src/main/java/com/example/RealMatch/brand/domain/repository/BrandDescribeTagRepository.java index 4b7b2abf..bb94e027 100644 --- a/src/main/java/com/example/RealMatch/brand/domain/repository/BrandDescribeTagRepository.java +++ b/src/main/java/com/example/RealMatch/brand/domain/repository/BrandDescribeTagRepository.java @@ -11,4 +11,6 @@ public interface BrandDescribeTagRepository extends JpaRepository { List findAllByBrandId(Long brandId); + + List findAllByBrandIdIn(List brandIds); } diff --git a/src/main/java/com/example/RealMatch/user/application/service/UserFavoriteService.java b/src/main/java/com/example/RealMatch/user/application/service/UserFavoriteService.java new file mode 100644 index 00000000..f87d60a8 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/application/service/UserFavoriteService.java @@ -0,0 +1,87 @@ +package com.example.RealMatch.user.application.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.RealMatch.brand.domain.entity.BrandDescribeTag; +import com.example.RealMatch.brand.domain.repository.BrandDescribeTagRepository; +import com.example.RealMatch.match.domain.entity.enums.BrandSortType; +import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; +import com.example.RealMatch.user.domain.repository.UserFavoriteBrandQueryRepository; +import com.example.RealMatch.user.domain.repository.UserFavoriteCampaignQueryRepository; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandListResponseDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignListResponseDto; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserFavoriteService { + + private final UserFavoriteCampaignQueryRepository userFavoriteCampaignQueryRepository; + private final UserFavoriteBrandQueryRepository userFavoriteBrandQueryRepository; + private final BrandDescribeTagRepository brandDescribeTagRepository; + + /* ===== 찜한 브랜드 ===== */ + public FavoriteBrandListResponseDto getMyFavoriteBrands( + Long userId, + BrandSortType sortType + ) { + List brands = + userFavoriteBrandQueryRepository.findFavoriteBrands(userId, sortType); + + if (brands.isEmpty()) { + return FavoriteBrandListResponseDto.empty(); + } + + List brandIds = brands.stream() + .map(FavoriteBrandDto::getBrandId) + .toList(); + + List tags = + brandDescribeTagRepository.findAllByBrandIdIn(brandIds); + + Map> tagMap = tags.stream() + .collect(Collectors.groupingBy( + tag -> tag.getBrand().getId(), + Collectors.mapping( + BrandDescribeTag::getBrandDescribeTag, + Collectors.toList() + ) + )); + + brands.forEach(dto -> + dto.setTags(tagMap.getOrDefault(dto.getBrandId(), List.of())) + ); + + return FavoriteBrandListResponseDto.builder() + .count(brands.size()) + .brands(brands) + .build(); + } + + /* ===== 찜한 캠페인 ===== */ + public FavoriteCampaignListResponseDto getMyFavoriteCampaigns( + Long userId, + CampaignSortType sortType + ) { + List campaigns = + userFavoriteCampaignQueryRepository.findFavoriteCampaigns(userId, sortType); + + if (campaigns.isEmpty()) { + return FavoriteCampaignListResponseDto.empty(); + } + + return FavoriteCampaignListResponseDto.builder() + .count(campaigns.size()) + .campaigns(campaigns) + .build(); + } +} diff --git a/src/main/java/com/example/RealMatch/user/domain/entity/enums/FavoriteType.java b/src/main/java/com/example/RealMatch/user/domain/entity/enums/FavoriteType.java new file mode 100644 index 00000000..696b37e4 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/domain/entity/enums/FavoriteType.java @@ -0,0 +1,5 @@ +package com.example.RealMatch.user.domain.entity.enums; + +public enum FavoriteType { + BRAND, CAMPAIGN +} diff --git a/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandQueryRepository.java b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandQueryRepository.java new file mode 100644 index 00000000..284ffc77 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandQueryRepository.java @@ -0,0 +1,14 @@ +package com.example.RealMatch.user.domain.repository; + +import java.util.List; + +import com.example.RealMatch.match.domain.entity.enums.BrandSortType; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandDto; + +public interface UserFavoriteBrandQueryRepository { + + List findFavoriteBrands( + Long userId, + BrandSortType sortType + ); +} diff --git a/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandRepositoryImpl.java b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandRepositoryImpl.java new file mode 100644 index 00000000..9b028fc0 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteBrandRepositoryImpl.java @@ -0,0 +1,114 @@ +package com.example.RealMatch.user.domain.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.example.RealMatch.brand.domain.entity.QBrand; +import com.example.RealMatch.brand.domain.entity.QBrandLike; +import com.example.RealMatch.match.domain.entity.QMatchBrandHistory; +import com.example.RealMatch.match.domain.entity.enums.BrandSortType; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandDto; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class UserFavoriteBrandRepositoryImpl + implements UserFavoriteBrandQueryRepository { + + private final JPAQueryFactory queryFactory; + + // ✅ Checkstyle 대응: static final → 대문자 상수 + private static final QBrandLike BRAND_LIKE = QBrandLike.brandLike; + private static final QBrand BRAND = QBrand.brand; + private static final QMatchBrandHistory MATCH_BRAND_HISTORY = + QMatchBrandHistory.matchBrandHistory; + + @Override + public List findFavoriteBrands( + Long userId, + BrandSortType sortType + ) { + List> orderSpecifiers = + buildBrandOrderSpecifiers(sortType); + + return queryFactory + .select(Projections.constructor( + FavoriteBrandDto.class, + BRAND.id, + BRAND.brandName, + BRAND.logoUrl, + brandMatchingRatioExpression(), + brandLikeCountExpression(), + Expressions.nullExpression(List.class) // tags는 나중에 채움 + )) + .from(BRAND_LIKE) + .join(BRAND_LIKE.brand, BRAND) + .leftJoin(MATCH_BRAND_HISTORY).on( + MATCH_BRAND_HISTORY.brand.id.eq(BRAND.id), + MATCH_BRAND_HISTORY.user.id.eq(userId), + MATCH_BRAND_HISTORY.isDeprecated.eq(false) + ) + .where(BRAND_LIKE.user.id.eq(userId)) + .orderBy(orderSpecifiers.toArray(new OrderSpecifier[0])) + .fetch(); + } + + private List> buildBrandOrderSpecifiers( + BrandSortType sortType + ) { + if (sortType == null) { + sortType = BrandSortType.MATCH_SCORE; + } + + return switch (sortType) { + case MATCH_SCORE -> List.of( + MATCH_BRAND_HISTORY.matchingRatio.desc().nullsLast(), + BRAND.id.asc() + ); + + case POPULARITY -> List.of( + popularityDesc(), + MATCH_BRAND_HISTORY.matchingRatio.desc().nullsLast(), + BRAND.id.asc() + ); + + case NEWEST -> List.of( + BRAND.createdAt.desc(), + BRAND.id.asc() + ); + }; + } + + private OrderSpecifier popularityDesc() { + return new OrderSpecifier<>( + com.querydsl.core.types.Order.DESC, + brandLikeCountExpression(), + OrderSpecifier.NullHandling.NullsLast + ); + } + + private Expression brandLikeCountExpression() { + QBrandLike bl = QBrandLike.brandLike; + + return JPAExpressions + .select(bl.id.count()) + .from(bl) + .where(bl.brand.id.eq(BRAND.id)); + } + + private Expression brandMatchingRatioExpression() { + return Expressions.numberTemplate( + Long.class, + "COALESCE({0}, 0)", + MATCH_BRAND_HISTORY.matchingRatio + ); + } +} diff --git a/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignQueryRepository.java b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignQueryRepository.java new file mode 100644 index 00000000..da2a1b58 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignQueryRepository.java @@ -0,0 +1,14 @@ +package com.example.RealMatch.user.domain.repository; + +import java.util.List; + +import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignDto; + +public interface UserFavoriteCampaignQueryRepository { + + List findFavoriteCampaigns( + Long userId, + CampaignSortType sortType + ); +} diff --git a/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignRepositoryImpl.java b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignRepositoryImpl.java new file mode 100644 index 00000000..307c6d9c --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/domain/repository/UserFavoriteCampaignRepositoryImpl.java @@ -0,0 +1,155 @@ +package com.example.RealMatch.user.domain.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.example.RealMatch.brand.domain.entity.QBrand; +import com.example.RealMatch.campaign.domain.entity.QCampaign; +import com.example.RealMatch.campaign.domain.entity.QCampaignLike; +import com.example.RealMatch.match.domain.entity.QMatchCampaignHistory; +import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignDto; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class UserFavoriteCampaignRepositoryImpl + implements UserFavoriteCampaignQueryRepository { + + private final JPAQueryFactory queryFactory; + + // ✅ Checkstyle 대응: static final → 대문자 상수 + private static final QCampaignLike CAMPAIGN_LIKE = QCampaignLike.campaignLike; + private static final QCampaign CAMPAIGN = QCampaign.campaign; + private static final QBrand BRAND = QBrand.brand; + private static final QMatchCampaignHistory MATCH_CAMPAIGN_HISTORY = + QMatchCampaignHistory.matchCampaignHistory; + + @Override + public List findFavoriteCampaigns( + Long userId, + CampaignSortType sortType + ) { + List> orderSpecifiers = + buildCampaignOrderSpecifiers(sortType); + + return queryFactory + .select(Projections.constructor( + FavoriteCampaignDto.class, + CAMPAIGN.id, + CAMPAIGN.title, + dDayExpression(), + CAMPAIGN.rewardAmount, + CAMPAIGN.quota, + campaignMatchingRatioExpression(), + campaignLikeCountExpression(), + BRAND.id, + BRAND.brandName, + BRAND.logoUrl + )) + .from(CAMPAIGN_LIKE) + .join(CAMPAIGN_LIKE.campaign, CAMPAIGN) + .join(CAMPAIGN.brand, BRAND) + .leftJoin(MATCH_CAMPAIGN_HISTORY).on( + MATCH_CAMPAIGN_HISTORY.campaign.id.eq(CAMPAIGN.id), + MATCH_CAMPAIGN_HISTORY.user.id.eq(userId), + MATCH_CAMPAIGN_HISTORY.isDeprecated.eq(false) + ) + .where(CAMPAIGN_LIKE.user.id.eq(userId)) + .orderBy(orderSpecifiers.toArray(new OrderSpecifier[0])) + .fetch(); + } + + private List> buildCampaignOrderSpecifiers( + CampaignSortType sortType + ) { + if (sortType == null) { + sortType = CampaignSortType.MATCH_SCORE; + } + + return switch (sortType) { + case MATCH_SCORE -> List.of( + campaignMatchingRatioDesc(), + popularityDesc(), + campaignIdAsc() + ); + + case POPULARITY -> List.of( + popularityDesc(), + campaignMatchingRatioDesc(), + campaignIdAsc() + ); + + case REWARD_AMOUNT -> List.of( + CAMPAIGN.rewardAmount.desc().nullsLast(), + campaignMatchingRatioDesc(), + popularityDesc(), + campaignIdAsc() + ); + + case D_DAY -> List.of( + dDayAscNullsLast(), + campaignMatchingRatioDesc(), + popularityDesc(), + campaignIdAsc() + ); + }; + } + + private OrderSpecifier campaignMatchingRatioDesc() { + return MATCH_CAMPAIGN_HISTORY.matchingRatio.desc().nullsLast(); + } + + private OrderSpecifier popularityDesc() { + return new OrderSpecifier<>( + com.querydsl.core.types.Order.DESC, + campaignLikeCountExpression(), + OrderSpecifier.NullHandling.NullsLast + ); + } + + private OrderSpecifier campaignIdAsc() { + return CAMPAIGN.id.asc(); + } + + private OrderSpecifier dDayAscNullsLast() { + return new OrderSpecifier<>( + com.querydsl.core.types.Order.ASC, + dDayExpression(), + OrderSpecifier.NullHandling.NullsLast + ); + } + + private Expression campaignLikeCountExpression() { + QCampaignLike cl = QCampaignLike.campaignLike; + + return JPAExpressions + .select(cl.id.count()) + .from(cl) + .where(cl.campaign.id.eq(CAMPAIGN.id)); + } + + private Expression dDayExpression() { + return Expressions.numberTemplate( + Integer.class, + "DATEDIFF({0}, CURDATE())", + CAMPAIGN.recruitEndDate + ); + } + + private Expression campaignMatchingRatioExpression() { + return Expressions.numberTemplate( + Long.class, + "COALESCE({0}, 0)", + MATCH_CAMPAIGN_HISTORY.matchingRatio + ); + } +} diff --git a/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java b/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java index 34abbbfc..d6e0bd34 100644 --- a/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java +++ b/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java @@ -12,11 +12,16 @@ import com.example.RealMatch.global.config.jwt.CustomUserDetails; import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.match.domain.entity.enums.BrandSortType; +import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; import com.example.RealMatch.match.presentation.dto.request.MatchRequestDto; +import com.example.RealMatch.user.application.service.UserFavoriteService; import com.example.RealMatch.user.application.service.UserFeatureService; import com.example.RealMatch.user.application.service.UserService; import com.example.RealMatch.user.application.service.UserWithdrawService; import com.example.RealMatch.user.presentation.dto.request.MyEditInfoRequestDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandListResponseDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignListResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyEditInfoResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyFeatureResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyLoginResponseDto; @@ -39,6 +44,7 @@ public class UserController implements UserSwagger { private final UserService userService; private final UserFeatureService userFeatureService; private final UserWithdrawService userWithdrawService; + private final UserFavoriteService userFavoriteService; @Override @GetMapping("/me") @@ -129,4 +135,35 @@ public CustomResponse withdraw( userWithdrawService.withdraw(userDetails.getUserId()); return CustomResponse.ok(null); } + + @Override + @GetMapping("/me/favorites/brand") + public CustomResponse getMyFavoriteBrands( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestParam(required = false) BrandSortType sort + ) { + Long userId = userDetails.getUserId(); + return CustomResponse.ok( + userFavoriteService.getMyFavoriteBrands( + userId, + sort == null ? BrandSortType.MATCH_SCORE : sort + ) + ); + } + + @Override + @GetMapping("/me/favorites/campaign") + public CustomResponse getMyFavoriteCampaigns( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestParam(required = false) CampaignSortType sort + ) { + Long userId = userDetails.getUserId(); + + return CustomResponse.ok( + userFavoriteService.getMyFavoriteCampaigns( + userId, + sort == null ? CampaignSortType.MATCH_SCORE : sort + ) + ); + } } diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandDto.java new file mode 100644 index 00000000..3d0bb694 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandDto.java @@ -0,0 +1,24 @@ +package com.example.RealMatch.user.presentation.dto.response; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FavoriteBrandDto { + + private Long brandId; + private String brandName; + private String brandLogoUrl; + + private Long matchingRatio; // 99, 98, 79 … + private Long likeCount; // 항상 true (찜 목록) + + private List tags; // #청정자극 #저자극 … + + public void setTags(List tags) { + this.tags = tags; + } +} diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandListResponseDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandListResponseDto.java new file mode 100644 index 00000000..0fa8132b --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteBrandListResponseDto.java @@ -0,0 +1,23 @@ +package com.example.RealMatch.user.presentation.dto.response; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class FavoriteBrandListResponseDto { + + private Integer count; + private List brands; + + public static FavoriteBrandListResponseDto empty() { + return FavoriteBrandListResponseDto.builder() + .count(0) + .brands(List.of()) + .build(); + } +} diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignDto.java new file mode 100644 index 00000000..8e2de4c0 --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignDto.java @@ -0,0 +1,23 @@ +package com.example.RealMatch.user.presentation.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FavoriteCampaignDto { + + private Long campaignId; + private String campaignTitle; + private Integer dDay; + private Long rewardAmount; + private Integer quota; + + private Long matchingRatio; // user 기준 + private Long likeCount; + + private Long brandId; + private String brandName; + private String brandLogoUrl; +} + diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignListResponseDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignListResponseDto.java new file mode 100644 index 00000000..1a28b8fd --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/response/FavoriteCampaignListResponseDto.java @@ -0,0 +1,23 @@ +package com.example.RealMatch.user.presentation.dto.response; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class FavoriteCampaignListResponseDto { + + private Integer count; + private List campaigns; + + public static FavoriteCampaignListResponseDto empty() { + return FavoriteCampaignListResponseDto.builder() + .count(0) + .campaigns(List.of()) + .build(); + } +} diff --git a/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java b/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java index 9e736f23..c6e8cfe5 100644 --- a/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java +++ b/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java @@ -7,8 +7,12 @@ import com.example.RealMatch.global.config.jwt.CustomUserDetails; import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.match.domain.entity.enums.BrandSortType; +import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; import com.example.RealMatch.match.presentation.dto.request.MatchRequestDto; import com.example.RealMatch.user.presentation.dto.request.MyEditInfoRequestDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandListResponseDto; +import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignListResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyEditInfoResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyFeatureResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyLoginResponseDto; @@ -19,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -120,15 +125,15 @@ CustomResponse updateMyFeature( @Operation( summary = "닉네임 중복 체크 API", description = """ - 회원가입 및 닉네임 변경 시 사용할 닉네임 중복 체크 API입니다. - - user 테이블의 nickname 컬럼에서 닉네임이 있으면 "available": false 반환 - 없으면 "available": true 반환 - - 닉네임 형식 및 길이 조건 - - 형식: 한글, 영문, 숫자만 허용 (특수문자 및 공백 불가) - - 길이: 2자 이상 10자 이하 허용 - """ + 회원가입 및 닉네임 변경 시 사용할 닉네임 중복 체크 API입니다. + + user 테이블의 nickname 컬럼에서 닉네임이 있으면 "available": false 반환 + 없으면 "available": true 반환 + + 닉네임 형식 및 길이 조건 + - 형식: 한글, 영문, 숫자만 허용 (특수문자 및 공백 불가) + - 길이: 2자 이상 10자 이하 허용 + """ ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "중복 체크 성공"), @@ -147,4 +152,59 @@ CustomResponse checkNicknameAvailable( CustomResponse withdraw( @Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails ); + + @Operation( + summary = "내가 찜한 브랜드 조회 By 박지영", + description = """ + 사용자가 찜한 브랜드 목록을 조회합니다. + + 🔹 정렬 기준(sort)에 따라 결과가 정렬됩니다. + - MATCH_SCORE : 사용자 맞춤 브랜드 매칭률 순 + - POPULARITY : 브랜드 인기순 (좋아요 수 기준) + - NEWEST : 브랜드 최신순 (생성일 기준) + + 🔹 sort 파라미터를 전달하지 않으면 기본값은 MATCH_SCORE 입니다. + """ + ) + CustomResponse getMyFavoriteBrands( + @Parameter(hidden = true) + @AuthenticationPrincipal CustomUserDetails userDetails, + + @Parameter( + description = "정렬 기준 (기본값: MATCH_SCORE)", + required = false, + schema = @Schema(implementation = BrandSortType.class) + ) + @RequestParam(required = false) + BrandSortType sort + ); + + + @Operation( + summary = "내가 찜한 캠페인 조회 By 박지영", + description = """ + 사용자가 찜한 캠페인 목록을 조회합니다. + + 🔹 정렬 기준(sort)에 따라 결과가 정렬됩니다. + - MATCH_SCORE : 사용자 맞춤 캠페인 매칭률 순 + - POPULARITY : 캠페인 인기순 (좋아요 수 기준) + - REWARD_AMOUNT: 원고료 높은 순 + - D_DAY : 모집 마감 임박 순 (D-Day 기준) + + 🔹 sort 파라미터를 전달하지 않으면 기본값은 MATCH_SCORE 입니다. + """ + ) + CustomResponse getMyFavoriteCampaigns( + + @Parameter(hidden = true) + @AuthenticationPrincipal CustomUserDetails userDetails, + + @Parameter( + description = "정렬 기준 (기본값: MATCH_SCORE)", + required = false, + schema = @Schema(implementation = CampaignSortType.class) + ) + @RequestParam(required = false) + CampaignSortType sort + ); }