diff --git a/src/docs/asciidoc/achievement.adoc b/src/docs/asciidoc/achievement.adoc index 5944255..fc805c6 100644 --- a/src/docs/asciidoc/achievement.adoc +++ b/src/docs/asciidoc/achievement.adoc @@ -3,6 +3,7 @@ BloomBackend Team 2024-09-12 :toc: left :toclevels: 2 +:source-highlighter: highlightjs :sectlinks: == 성취도 API diff --git a/src/docs/asciidoc/credit.adoc b/src/docs/asciidoc/credit.adoc new file mode 100644 index 0000000..9f02df0 --- /dev/null +++ b/src/docs/asciidoc/credit.adoc @@ -0,0 +1,27 @@ += API 문서 +BloomBackend Team +2024-08-27 +:toc: left +:toclevels: 2 +:source-highlighter: highlightjs +:sectlinks: + +== 크레딧 API + +=== 1. 아이템 구매 api + +==== HTTP request + +include::{snippets}/api-credit-test/purchase/http-request.adoc[] + +==== request body + +include::{snippets}/api-credit-test/purchase/request-fields.adoc[] + +==== HTTP response + +include::{snippets}/api-credit-test/purchase/http-response.adoc[] + +==== response body + +include::{snippets}/api-credit-test/purchase/response-fields.adoc[] diff --git a/src/docs/asciidoc/global/item.adoc b/src/docs/asciidoc/global/item.adoc index b57fc06..2d4ce82 100644 --- a/src/docs/asciidoc/global/item.adoc +++ b/src/docs/asciidoc/global/item.adoc @@ -8,7 +8,7 @@ BloomBackend Team == 아이템 API -=== 1. 유저 정보 업데이트 +=== 1. 구매가능한 아이템 목록 조회 ==== HTTP request @@ -21,3 +21,17 @@ include::{snippets}/api-item-test/get-items/http-response.adoc[] ==== response body include::{snippets}/api-item-test/get-items/response-fields.adoc[] + +=== 2. 유저 인벤토리 목록 조회 + +==== HTTP request + +include::{snippets}/api-item-test/get-user-items/http-request.adoc[] + +==== HTTP response + +include::{snippets}/api-item-test/get-user-items/http-response.adoc[] + +==== response body + +include::{snippets}/api-item-test/get-user-items/response-fields.adoc[] diff --git a/src/main/generated/com/example/bloombackend/achievement/entity/QDailyAchievementEntity.java b/src/main/generated/com/example/bloombackend/achievement/entity/QDailyAchievementEntity.java index b53d019..b0e4e68 100644 --- a/src/main/generated/com/example/bloombackend/achievement/entity/QDailyAchievementEntity.java +++ b/src/main/generated/com/example/bloombackend/achievement/entity/QDailyAchievementEntity.java @@ -26,7 +26,7 @@ public class QDailyAchievementEntity extends EntityPathBase createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); - public final QFlowerEntity flower; + public final com.example.bloombackend.item.entity.items.QSeedEntity flower; public final NumberPath id = createNumber("id", Long.class); @@ -50,7 +50,7 @@ public QDailyAchievementEntity(PathMetadata metadata, PathInits inits) { public QDailyAchievementEntity(Class type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); - this.flower = inits.isInitialized("flower") ? new QFlowerEntity(forProperty("flower")) : null; + this.flower = inits.isInitialized("flower") ? new com.example.bloombackend.item.entity.items.QSeedEntity(forProperty("flower")) : null; this.user = inits.isInitialized("user") ? new com.example.bloombackend.user.entity.QUserEntity(forProperty("user")) : null; } diff --git a/src/main/generated/com/example/bloombackend/achievement/entity/QFlowerEntity.java b/src/main/generated/com/example/bloombackend/achievement/entity/QFlowerEntity.java deleted file mode 100644 index 385b015..0000000 --- a/src/main/generated/com/example/bloombackend/achievement/entity/QFlowerEntity.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.example.bloombackend.achievement.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QFlowerEntity is a Querydsl query type for FlowerEntity - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QFlowerEntity extends EntityPathBase { - - private static final long serialVersionUID = -1091552578L; - - public static final QFlowerEntity flowerEntity = new QFlowerEntity("flowerEntity"); - - public final StringPath description = createString("description"); - - public final StringPath iconUrl = createString("iconUrl"); - - public final NumberPath id = createNumber("id", Long.class); - - public QFlowerEntity(String variable) { - super(FlowerEntity.class, forVariable(variable)); - } - - public QFlowerEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QFlowerEntity(PathMetadata metadata) { - super(FlowerEntity.class, metadata); - } - -} - diff --git a/src/main/generated/com/example/bloombackend/credit/entity/QPurchaseLogEntity.java b/src/main/generated/com/example/bloombackend/credit/entity/QPurchaseLogEntity.java new file mode 100644 index 0000000..ce15c56 --- /dev/null +++ b/src/main/generated/com/example/bloombackend/credit/entity/QPurchaseLogEntity.java @@ -0,0 +1,47 @@ +package com.example.bloombackend.credit.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QPurchaseLogEntity is a Querydsl query type for PurchaseLogEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPurchaseLogEntity extends EntityPathBase { + + private static final long serialVersionUID = 969509316L; + + public static final QPurchaseLogEntity purchaseLogEntity = new QPurchaseLogEntity("purchaseLogEntity"); + + public final NumberPath amount = createNumber("amount", Integer.class); + + public final NumberPath balance = createNumber("balance", Integer.class); + + public final EnumPath creditType = createEnum("creditType", CreditType.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath itemId = createNumber("itemId", Long.class); + + public final NumberPath userId = createNumber("userId", Long.class); + + public QPurchaseLogEntity(String variable) { + super(PurchaseLogEntity.class, forVariable(variable)); + } + + public QPurchaseLogEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPurchaseLogEntity(PathMetadata metadata) { + super(PurchaseLogEntity.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/bloombackend/credit/entity/QUserCreditEntity.java b/src/main/generated/com/example/bloombackend/credit/entity/QUserCreditEntity.java new file mode 100644 index 0000000..7934d10 --- /dev/null +++ b/src/main/generated/com/example/bloombackend/credit/entity/QUserCreditEntity.java @@ -0,0 +1,55 @@ +package com.example.bloombackend.credit.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QUserCreditEntity is a Querydsl query type for UserCreditEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserCreditEntity extends EntityPathBase { + + private static final long serialVersionUID = 1485991529L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUserCreditEntity userCreditEntity = new QUserCreditEntity("userCreditEntity"); + + public final NumberPath balance = createNumber("balance", Integer.class); + + public final EnumPath creditType = createEnum("creditType", CreditType.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final com.example.bloombackend.user.entity.QUserEntity user; + + public QUserCreditEntity(String variable) { + this(UserCreditEntity.class, forVariable(variable), INITS); + } + + public QUserCreditEntity(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUserCreditEntity(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUserCreditEntity(PathMetadata metadata, PathInits inits) { + this(UserCreditEntity.class, metadata, inits); + } + + public QUserCreditEntity(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.user = inits.isInitialized("user") ? new com.example.bloombackend.user.entity.QUserEntity(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/com/example/bloombackend/item/entity/QItemEntity.java b/src/main/generated/com/example/bloombackend/item/entity/QItemEntity.java index ed54c5f..520c58b 100644 --- a/src/main/generated/com/example/bloombackend/item/entity/QItemEntity.java +++ b/src/main/generated/com/example/bloombackend/item/entity/QItemEntity.java @@ -19,17 +19,17 @@ public class QItemEntity extends EntityPathBase { public static final QItemEntity itemEntity = new QItemEntity("itemEntity"); - public final NumberPath id = createNumber("id", Long.class); + public final DatePath endDate = createDate("endDate", java.time.LocalDate.class); - public final StringPath imgUrl = createString("imgUrl"); + public final NumberPath id = createNumber("id", Long.class); - public final BooleanPath isSale = createBoolean("isSale"); + public final BooleanPath isDefault = createBoolean("isDefault"); public final StringPath name = createString("name"); public final NumberPath price = createNumber("price", Integer.class); - public final EnumPath type = createEnum("type", ItemType.class); + public final StringPath thumbnailImgUrl = createString("thumbnailImgUrl"); public QItemEntity(String variable) { super(ItemEntity.class, forVariable(variable)); diff --git a/src/main/generated/com/example/bloombackend/item/entity/QUserItemEntity.java b/src/main/generated/com/example/bloombackend/item/entity/QUserItemEntity.java new file mode 100644 index 0000000..307ea18 --- /dev/null +++ b/src/main/generated/com/example/bloombackend/item/entity/QUserItemEntity.java @@ -0,0 +1,53 @@ +package com.example.bloombackend.item.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QUserItemEntity is a Querydsl query type for UserItemEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserItemEntity extends EntityPathBase { + + private static final long serialVersionUID = -443148835L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUserItemEntity userItemEntity = new QUserItemEntity("userItemEntity"); + + public final NumberPath id = createNumber("id", Long.class); + + public final QItemEntity item; + + public final NumberPath userId = createNumber("userId", Long.class); + + public QUserItemEntity(String variable) { + this(UserItemEntity.class, forVariable(variable), INITS); + } + + public QUserItemEntity(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUserItemEntity(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUserItemEntity(PathMetadata metadata, PathInits inits) { + this(UserItemEntity.class, metadata, inits); + } + + public QUserItemEntity(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.item = inits.isInitialized("item") ? new QItemEntity(forProperty("item")) : null; + } + +} + diff --git a/src/main/generated/com/example/bloombackend/item/entity/items/QSeedEntity.java b/src/main/generated/com/example/bloombackend/item/entity/items/QSeedEntity.java new file mode 100644 index 0000000..6a89275 --- /dev/null +++ b/src/main/generated/com/example/bloombackend/item/entity/items/QSeedEntity.java @@ -0,0 +1,59 @@ +package com.example.bloombackend.item.entity.items; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QSeedEntity is a Querydsl query type for SeedEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QSeedEntity extends EntityPathBase { + + private static final long serialVersionUID = -2120067970L; + + public static final QSeedEntity seedEntity = new QSeedEntity("seedEntity"); + + public final com.example.bloombackend.item.entity.QItemEntity _super = new com.example.bloombackend.item.entity.QItemEntity(this); + + public final StringPath bigIconUrl = createString("bigIconUrl"); + + //inherited + public final DatePath endDate = _super.endDate; + + //inherited + public final NumberPath id = _super.id; + + //inherited + public final BooleanPath isDefault = _super.isDefault; + + //inherited + public final StringPath name = _super.name; + + //inherited + public final NumberPath price = _super.price; + + public final StringPath smallIconUrl = createString("smallIconUrl"); + + //inherited + public final StringPath thumbnailImgUrl = _super.thumbnailImgUrl; + + public QSeedEntity(String variable) { + super(SeedEntity.class, forVariable(variable)); + } + + public QSeedEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QSeedEntity(PathMetadata metadata) { + super(SeedEntity.class, metadata); + } + +} + diff --git a/src/main/java/com/example/bloombackend/achievement/entity/DailyAchievementEntity.java b/src/main/java/com/example/bloombackend/achievement/entity/DailyAchievementEntity.java index 4386ef4..4930996 100644 --- a/src/main/java/com/example/bloombackend/achievement/entity/DailyAchievementEntity.java +++ b/src/main/java/com/example/bloombackend/achievement/entity/DailyAchievementEntity.java @@ -1,55 +1,66 @@ package com.example.bloombackend.achievement.entity; +import java.time.LocalDateTime; + import com.example.bloombackend.achievement.controller.dto.response.DailyAchievementResponse; +import com.example.bloombackend.item.entity.items.SeedEntity; import com.example.bloombackend.user.entity.UserEntity; -import jakarta.persistence.*; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "daily_achievement") public class DailyAchievementEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private UserEntity user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "flower_id", nullable = false) - private FlowerEntity flower; - - @Column(name = "achievement_level", nullable = false) - private int achievementLevel = 0; - - @Column(updatable = false) - private LocalDateTime createdAt = LocalDateTime.now(); - - public DailyAchievementEntity(UserEntity user, FlowerEntity flower) { - this.user = user; - this.flower = flower; - } - - public int getAchievementLevel() { - return achievementLevel; - } - - public void increaseAchievementLevel(int increaseBy) { - if (achievementLevel + increaseBy >= 0 && achievementLevel + increaseBy <= 9) { - achievementLevel += increaseBy; - } - } - - public DailyAchievementResponse toDto() { - return new DailyAchievementResponse(createdAt.toLocalDate(), getIconUrl(), achievementLevel); - } - - private String getIconUrl() { - return achievementLevel == 9 ? flower.getIconUrl() : AchievementIcon.fromLevel(achievementLevel).getUrl(); - } -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "flower_id", nullable = false) + private SeedEntity flower; + + @Column(name = "achievement_level", nullable = false) + private int achievementLevel = 0; + + @Column(updatable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + public DailyAchievementEntity(UserEntity user, SeedEntity flower) { + this.user = user; + this.flower = flower; + } + + public int getAchievementLevel() { + return achievementLevel; + } + + public void increaseAchievementLevel(int increaseBy) { + if (achievementLevel + increaseBy >= 0 && achievementLevel + increaseBy <= 9) { + achievementLevel += increaseBy; + } + } + + public DailyAchievementResponse toDto() { + return new DailyAchievementResponse(createdAt.toLocalDate(), getIconUrl(), achievementLevel); + } + + private String getIconUrl() { + return achievementLevel == 9 ? flower.getBigIconUrl() : AchievementIcon.fromLevel(achievementLevel).getUrl(); + } +} diff --git a/src/main/java/com/example/bloombackend/achievement/entity/FlowerEntity.java b/src/main/java/com/example/bloombackend/achievement/entity/FlowerEntity.java deleted file mode 100644 index d84e832..0000000 --- a/src/main/java/com/example/bloombackend/achievement/entity/FlowerEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.bloombackend.achievement.entity; - -import jakarta.persistence.*; -import lombok.NoArgsConstructor; - -@Entity -@NoArgsConstructor -@Table(name = "flower") -public class FlowerEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "icon_url", nullable = false) - private String iconUrl; - - @Column(name = "description") - private String description; - - public FlowerEntity(String iconUrl, String description) { - this.iconUrl = iconUrl; - this.description = description; - } - - public Long getId() { - return id; - } - - public String getIconUrl() { - return iconUrl; - } -} \ No newline at end of file diff --git a/src/main/java/com/example/bloombackend/achievement/repository/FlowerRepository.java b/src/main/java/com/example/bloombackend/achievement/repository/FlowerRepository.java deleted file mode 100644 index 13c5a92..0000000 --- a/src/main/java/com/example/bloombackend/achievement/repository/FlowerRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.bloombackend.achievement.repository; - -import com.example.bloombackend.achievement.entity.FlowerEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface FlowerRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/com/example/bloombackend/achievement/repository/querydsl/DailyAchievementRepositoryImpl.java b/src/main/java/com/example/bloombackend/achievement/repository/querydsl/DailyAchievementRepositoryImpl.java index 97e62d1..0a1ab98 100644 --- a/src/main/java/com/example/bloombackend/achievement/repository/querydsl/DailyAchievementRepositoryImpl.java +++ b/src/main/java/com/example/bloombackend/achievement/repository/querydsl/DailyAchievementRepositoryImpl.java @@ -1,63 +1,62 @@ package com.example.bloombackend.achievement.repository.querydsl; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + import com.example.bloombackend.achievement.controller.dto.response.MonthlyAchievementResponse; import com.example.bloombackend.achievement.controller.response.DailyFlowerResponse; import com.example.bloombackend.achievement.entity.QDailyAchievementEntity; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; - public class DailyAchievementRepositoryImpl implements DailyAchievementRepositoryCustom { - private final JPAQueryFactory queryFactory; - - public DailyAchievementRepositoryImpl(JPAQueryFactory queryFactory) { - this.queryFactory = queryFactory; - } - - public List getRecentSixMonthsAchievements(Long userId) { - QDailyAchievementEntity dailyAchievement = QDailyAchievementEntity.dailyAchievementEntity; - - LocalDate now = LocalDate.now(); - LocalDateTime startOfSixthMonth = now.minusMonths(6).withDayOfMonth(1).atStartOfDay(); - LocalDate lastMonth = now.minusMonths(1); - LocalDateTime endOfLastMonth = lastMonth.withDayOfMonth(lastMonth.lengthOfMonth()).atTime(LocalTime.MAX); - - return queryFactory - .select(Projections.constructor( - MonthlyAchievementResponse.class, - dailyAchievement.createdAt.yearMonth(), - dailyAchievement.achievementLevel.when(9).then(1).otherwise(0).sum() - )) - .from(dailyAchievement) - .where( - dailyAchievement.user.id.eq(userId), - dailyAchievement.createdAt.between(startOfSixthMonth, endOfLastMonth) - ) - .groupBy(dailyAchievement.createdAt.yearMonth()) - .fetch(); - } - - public DailyFlowerResponse getDailyFlower(Long userId) { - QDailyAchievementEntity dailyAchievement = QDailyAchievementEntity.dailyAchievementEntity; - - LocalDate now = LocalDate.now(); - LocalDateTime startOfToday = now.atStartOfDay(); - LocalDateTime endOfToday = now.atTime(LocalTime.MAX); - - return queryFactory - .select(Projections.constructor( - DailyFlowerResponse.class, - dailyAchievement.flower.id, - dailyAchievement.flower.iconUrl)) - .from(dailyAchievement) - .where( - dailyAchievement.user.id.eq(userId), - dailyAchievement.createdAt.between(startOfToday, endOfToday) - ) - .fetchOne(); - } + private final JPAQueryFactory queryFactory; + + public DailyAchievementRepositoryImpl(JPAQueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + public List getRecentSixMonthsAchievements(Long userId) { + QDailyAchievementEntity dailyAchievement = QDailyAchievementEntity.dailyAchievementEntity; + + LocalDate now = LocalDate.now(); + LocalDateTime startOfSixthMonth = now.minusMonths(5).withDayOfMonth(1).atStartOfDay(); + LocalDateTime endOfCurrentMonth = now.withDayOfMonth(now.lengthOfMonth()).atTime(23, 59, 59); + + return queryFactory + .select(Projections.constructor( + MonthlyAchievementResponse.class, + dailyAchievement.createdAt.yearMonth(), + dailyAchievement.achievementLevel.when(9).then(1).otherwise(0).sum() + )) + .from(dailyAchievement) + .where( + dailyAchievement.user.id.eq(userId), + dailyAchievement.createdAt.between(startOfSixthMonth, endOfCurrentMonth) + ) + .groupBy(dailyAchievement.createdAt.yearMonth()) + .fetch(); + } + + public DailyFlowerResponse getDailyFlower(Long userId) { + QDailyAchievementEntity dailyAchievement = QDailyAchievementEntity.dailyAchievementEntity; + + LocalDate now = LocalDate.now(); + LocalDateTime startOfToday = now.atStartOfDay(); + LocalDateTime endOfToday = now.atTime(LocalTime.MAX); + + return queryFactory + .select(Projections.constructor( + DailyFlowerResponse.class, + dailyAchievement.flower.id, + dailyAchievement.flower.bigIconUrl)) + .from(dailyAchievement) + .where( + dailyAchievement.user.id.eq(userId), + dailyAchievement.createdAt.between(startOfToday, endOfToday) + ) + .fetchOne(); + } } \ No newline at end of file diff --git a/src/main/java/com/example/bloombackend/achievement/service/AchievementService.java b/src/main/java/com/example/bloombackend/achievement/service/AchievementService.java index 8cd0d86..9689feb 100644 --- a/src/main/java/com/example/bloombackend/achievement/service/AchievementService.java +++ b/src/main/java/com/example/bloombackend/achievement/service/AchievementService.java @@ -1,146 +1,153 @@ package com.example.bloombackend.achievement.service; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.example.bloombackend.achievement.controller.dto.request.AchievementLevelUpdateRequest; import com.example.bloombackend.achievement.controller.dto.request.FlowerRegisterRequest; -import com.example.bloombackend.achievement.controller.dto.response.*; +import com.example.bloombackend.achievement.controller.dto.response.AchievementLevelUpdateResponse; +import com.example.bloombackend.achievement.controller.dto.response.DailyAchievementResponse; +import com.example.bloombackend.achievement.controller.dto.response.MonthlyAchievementResponse; +import com.example.bloombackend.achievement.controller.dto.response.MonthlyDataResponse; +import com.example.bloombackend.achievement.controller.dto.response.RecentSixMonthDataResponse; import com.example.bloombackend.achievement.controller.response.DailyFlowerResponse; import com.example.bloombackend.achievement.entity.DailyAchievementEntity; -import com.example.bloombackend.achievement.entity.FlowerEntity; import com.example.bloombackend.achievement.repository.DailyAchievementRepository; -import com.example.bloombackend.achievement.repository.FlowerRepository; +import com.example.bloombackend.achievement.service.prompt.AchievementAIPrompt; +import com.example.bloombackend.global.AIUtil; +import com.example.bloombackend.item.entity.items.SeedEntity; +import com.example.bloombackend.item.repository.SeedRepository; import com.example.bloombackend.user.entity.UserEntity; import com.example.bloombackend.user.repository.UserRepository; -import jakarta.persistence.EntityNotFoundException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import com.example.bloombackend.global.AIUtil; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import com.example.bloombackend.achievement.service.prompt.AchievementAIPrompt; +import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; +import jakarta.persistence.EntityNotFoundException; @Service public class AchievementService { - - private final DailyAchievementRepository dailyAchievementRepository; - private final FlowerRepository flowerRepository; - private final UserRepository userRepository; - private final AIUtil aiUtil; - private final ObjectMapper objectMapper; - - public AchievementService(DailyAchievementRepository dailyAchievementRepository, FlowerRepository flowerRepository, UserRepository userRepository, AIUtil aiUtil, ObjectMapper objectMapper) { - this.dailyAchievementRepository = dailyAchievementRepository; - this.flowerRepository = flowerRepository; - this.userRepository = userRepository; - this.aiUtil = aiUtil; - this.objectMapper = objectMapper; - } - - @Transactional - public void setDailyFlower(Long userId, FlowerRegisterRequest request) { - UserEntity user = getUserEntity(userId); - FlowerEntity flower = getFlowerEntity(request); - if (isFlowerRegistered(userId)) { - throw new IllegalArgumentException("flower already registered for today"); - } - dailyAchievementRepository.save(new DailyAchievementEntity(user, flower)); - } - - private boolean isFlowerRegistered(Long userId) { - LocalDate now = LocalDate.now(); - LocalDateTime startOfToday = now.atStartOfDay(); - LocalDateTime endOfToday = now.atTime(LocalTime.MAX); - return dailyAchievementRepository.existsByUserIdAndCreatedAtBetween(userId, startOfToday, endOfToday); - } - - private FlowerEntity getFlowerEntity(FlowerRegisterRequest request) { - return flowerRepository.findById(request.flowerId()) - .orElseThrow(() -> new EntityNotFoundException("Flower not found:" + request.flowerId())); - } - - private UserEntity getUserEntity(Long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new EntityNotFoundException("User not found:" + userId)); - } - - @Transactional - public AchievementLevelUpdateResponse updateAchievementLevel(Long userId, AchievementLevelUpdateRequest request) { - DailyAchievementEntity dailyAchievement = getDailyAchievementEntity(userId); - dailyAchievement.increaseAchievementLevel(request.increaseBy()); - dailyAchievementRepository.save(dailyAchievement); - return new AchievementLevelUpdateResponse(dailyAchievement.getAchievementLevel()); - } - - @Transactional(readOnly = true) - public MonthlyDataResponse getMonthlyAchievements(Long userId, String month) { - List dailyData = getMonthlyAchievementEntities(userId, month).stream() - .map(DailyAchievementEntity::toDto) - .toList(); - return new MonthlyDataResponse(dailyData); - } - - private DailyAchievementEntity getDailyAchievementEntity(Long userId) { - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = startOfDay.plusDays(1).minusSeconds(1); - return dailyAchievementRepository.findFirstByUserIdAndCreatedAtBetween(userId, startOfDay, endOfDay) - .orElseThrow(() -> new EntityNotFoundException("Daily achievement not found for user:" + userId)); - } - - private List getMonthlyAchievementEntities(Long userId, String month) { - LocalDateTime startOfMonth = LocalDate.parse(month + "-01").atStartOfDay(); - LocalDateTime endOfMonth = startOfMonth.plusMonths(1).minusSeconds(1); - return dailyAchievementRepository.findByUserIdAndCreatedAtBetween(userId, startOfMonth, endOfMonth); - } - - @Transactional(readOnly = true) - public RecentSixMonthDataResponse getRecentSixMonthsAchievements(Long userId) { - List monthlyAchievements = getMonthlyAchievements(userId); - double averageBloomed = calculateAverageBloomed(monthlyAchievements); - String summary = generateAchievementSummary(monthlyAchievements, averageBloomed); - return new RecentSixMonthDataResponse(monthlyAchievements, averageBloomed, summary); - } - - private List getMonthlyAchievements(Long userId) { - return dailyAchievementRepository.getRecentSixMonthsAchievements(userId); - } - - private double calculateAverageBloomed(List monthlyAchievements) { - return monthlyAchievements.stream() - .mapToInt(MonthlyAchievementResponse::bloomed) - .average() - .orElse(0); - } - - private String generateAchievementSummary(List monthlyAchievements, double averageBloomed) { - String monthlyData = serializeMonthlyAchievements(monthlyAchievements); - String prompt = createAIPrompt(monthlyData, averageBloomed); - return aiUtil.generateCompletion(prompt); - } - - private String serializeMonthlyAchievements(List monthlyAchievements) { - try { - return objectMapper.writeValueAsString(monthlyAchievements); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - private String createAIPrompt(String monthlyData, double averageBloomed) { - return String.format(AchievementAIPrompt.ACHIEVEMENT_SUMMARY_PROMPT, monthlyData, Math.round(averageBloomed)); - } - - @Transactional(readOnly = true) - public DailyFlowerResponse getDailyFlower(Long userId) { - return dailyAchievementRepository.getDailyFlower(userId); - } - - @Transactional(readOnly = true) - public DailyAchievementResponse getDailyAchievement(final Long userId) { - DailyAchievementEntity dailyAchievement = getDailyAchievementEntity(userId); - return dailyAchievement.toDto(); - } -} \ No newline at end of file + private final DailyAchievementRepository dailyAchievementRepository; + private final SeedRepository seedRepository; + private final UserRepository userRepository; + private final AIUtil aiUtil; + private final ObjectMapper objectMapper; + + public AchievementService(DailyAchievementRepository dailyAchievementRepository, SeedRepository seedRepository, + UserRepository userRepository, AIUtil aiUtil, ObjectMapper objectMapper) { + this.dailyAchievementRepository = dailyAchievementRepository; + this.seedRepository = seedRepository; + this.userRepository = userRepository; + this.aiUtil = aiUtil; + this.objectMapper = objectMapper; + } + + @Transactional + public void setDailyFlower(Long userId, FlowerRegisterRequest request) { + UserEntity user = getUserEntity(userId); + SeedEntity flower = getFlowerEntity(request); + if (isFlowerRegistered(userId)) { + throw new IllegalArgumentException("flower already registered for today"); + } + dailyAchievementRepository.save(new DailyAchievementEntity(user, flower)); + } + + private boolean isFlowerRegistered(Long userId) { + LocalDate now = LocalDate.now(); + LocalDateTime startOfToday = now.atStartOfDay(); + LocalDateTime endOfToday = now.atTime(LocalTime.MAX); + return dailyAchievementRepository.existsByUserIdAndCreatedAtBetween(userId, startOfToday, endOfToday); + } + + private SeedEntity getFlowerEntity(FlowerRegisterRequest request) { + return seedRepository.findById(request.flowerId()) + .orElseThrow(() -> new EntityNotFoundException("Flower not found:" + request.flowerId())); + } + + private UserEntity getUserEntity(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("User not found:" + userId)); + } + + @Transactional + public AchievementLevelUpdateResponse updateAchievementLevel(Long userId, AchievementLevelUpdateRequest request) { + DailyAchievementEntity dailyAchievement = getDailyAchievementEntity(userId); + dailyAchievement.increaseAchievementLevel(request.increaseBy()); + dailyAchievementRepository.save(dailyAchievement); + return new AchievementLevelUpdateResponse(dailyAchievement.getAchievementLevel()); + } + + @Transactional(readOnly = true) + public MonthlyDataResponse getMonthlyAchievements(Long userId, String month) { + List dailyData = getMonthlyAchievementEntities(userId, month).stream() + .map(DailyAchievementEntity::toDto) + .toList(); + return new MonthlyDataResponse(dailyData); + } + + private DailyAchievementEntity getDailyAchievementEntity(Long userId) { + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = startOfDay.plusDays(1).minusSeconds(1); + return dailyAchievementRepository.findFirstByUserIdAndCreatedAtBetween(userId, startOfDay, endOfDay) + .orElseThrow(() -> new EntityNotFoundException("Daily achievement not found for user:" + userId)); + } + + private List getMonthlyAchievementEntities(Long userId, String month) { + LocalDateTime startOfMonth = LocalDate.parse(month + "-01").atStartOfDay(); + LocalDateTime endOfMonth = startOfMonth.plusMonths(1).minusSeconds(1); + return dailyAchievementRepository.findByUserIdAndCreatedAtBetween(userId, startOfMonth, endOfMonth); + } + + @Transactional(readOnly = true) + public RecentSixMonthDataResponse getRecentSixMonthsAchievements(Long userId) { + List monthlyAchievements = getMonthlyAchievements(userId); + double averageBloomed = calculateAverageBloomed(monthlyAchievements); + String summary = generateAchievementSummary(monthlyAchievements, averageBloomed); + return new RecentSixMonthDataResponse(monthlyAchievements, averageBloomed, summary); + } + + private List getMonthlyAchievements(Long userId) { + return dailyAchievementRepository.getRecentSixMonthsAchievements(userId); + } + + private double calculateAverageBloomed(List monthlyAchievements) { + return monthlyAchievements.stream() + .mapToInt(MonthlyAchievementResponse::bloomed) + .average() + .orElse(0); + } + + private String generateAchievementSummary(List monthlyAchievements, + double averageBloomed) { + String monthlyData = serializeMonthlyAchievements(monthlyAchievements); + String prompt = createAIPrompt(monthlyData, averageBloomed); + return aiUtil.generateCompletion(prompt); + } + + private String serializeMonthlyAchievements(List monthlyAchievements) { + try { + return objectMapper.writeValueAsString(monthlyAchievements); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private String createAIPrompt(String monthlyData, double averageBloomed) { + return String.format(AchievementAIPrompt.ACHIEVEMENT_SUMMARY_PROMPT, monthlyData, averageBloomed); + } + + @Transactional(readOnly = true) + public DailyFlowerResponse getDailyFlower(Long userId) { + return dailyAchievementRepository.getDailyFlower(userId); + } + + @Transactional(readOnly = true) + public DailyAchievementResponse getDailyAchievement(final Long userId) { + DailyAchievementEntity dailyAchievement = getDailyAchievementEntity(userId); + return dailyAchievement.toDto(); + } +} diff --git a/src/main/java/com/example/bloombackend/credit/controller/CreditController.java b/src/main/java/com/example/bloombackend/credit/controller/CreditController.java new file mode 100644 index 0000000..990f45d --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/controller/CreditController.java @@ -0,0 +1,29 @@ +package com.example.bloombackend.credit.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.bloombackend.credit.controller.dto.request.PurchaseRequest; +import com.example.bloombackend.credit.controller.dto.response.PurchaseResponse; +import com.example.bloombackend.credit.service.CreditService; +import com.example.bloombackend.global.config.annotation.CurrentUser; + +@RestController +@RequestMapping("/api/credit") +public class CreditController { + private final CreditService creditService; + + public CreditController(CreditService creditService) { + this.creditService = creditService; + } + + @PostMapping("/purchase") + public ResponseEntity purchase( + @CurrentUser Long userId, + @RequestBody PurchaseRequest purchaseRequest) { + return ResponseEntity.ok(creditService.purchaseItem(userId, purchaseRequest)); + } +} diff --git a/src/main/java/com/example/bloombackend/credit/controller/dto/request/PurchaseRequest.java b/src/main/java/com/example/bloombackend/credit/controller/dto/request/PurchaseRequest.java new file mode 100644 index 0000000..c0e07ba --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/controller/dto/request/PurchaseRequest.java @@ -0,0 +1,9 @@ +package com.example.bloombackend.credit.controller.dto.request; + +import com.example.bloombackend.credit.entity.CreditType; + +public record PurchaseRequest( + Long itemId, + CreditType creditType +) { +} diff --git a/src/main/java/com/example/bloombackend/credit/controller/dto/response/PurchaseResponse.java b/src/main/java/com/example/bloombackend/credit/controller/dto/response/PurchaseResponse.java new file mode 100644 index 0000000..4f0d8c6 --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/controller/dto/response/PurchaseResponse.java @@ -0,0 +1,10 @@ +package com.example.bloombackend.credit.controller.dto.response; + +import com.example.bloombackend.credit.service.dto.UserCreditInfo; +import com.example.bloombackend.item.controller.dto.response.info.ItemInfo; + +public record PurchaseResponse( + ItemInfo purchasedItem, + UserCreditInfo balance +) { +} diff --git a/src/main/java/com/example/bloombackend/credit/entity/CreditType.java b/src/main/java/com/example/bloombackend/credit/entity/CreditType.java new file mode 100644 index 0000000..4e7d6f5 --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/entity/CreditType.java @@ -0,0 +1,15 @@ +package com.example.bloombackend.credit.entity; + +public enum CreditType { + BUTTON("단추"), CASH("현금"); + + private final String label; + + CreditType(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} diff --git a/src/main/java/com/example/bloombackend/credit/entity/PurchaseLogEntity.java b/src/main/java/com/example/bloombackend/credit/entity/PurchaseLogEntity.java new file mode 100644 index 0000000..8bcf15b --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/entity/PurchaseLogEntity.java @@ -0,0 +1,48 @@ +package com.example.bloombackend.credit.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "purchase_log") +public class PurchaseLogEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "purchase_log_id") + private Long id; + + @Column + private Long userId; + + @Column(name = "item_id") + private Long itemId; + + @Enumerated(EnumType.STRING) + @Column(name = "credit_type") + private CreditType creditType; + + @Column(name = "amount") + private int amount; + + @Column(name = "balance") + private int balance; + + @Builder + public PurchaseLogEntity(Long userId, Long itemId, CreditType creditType, int amount, int balance) { + this.userId = userId; + this.itemId = itemId; + this.creditType = creditType; + this.amount = amount; + this.balance = balance; + } +} diff --git a/src/main/java/com/example/bloombackend/credit/entity/UserCreditEntity.java b/src/main/java/com/example/bloombackend/credit/entity/UserCreditEntity.java new file mode 100644 index 0000000..4452f06 --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/entity/UserCreditEntity.java @@ -0,0 +1,53 @@ +package com.example.bloombackend.credit.entity; + +import com.example.bloombackend.user.entity.UserEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "user_credit") +public class UserCreditEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_id") + @Getter + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + + @Enumerated(EnumType.STRING) + @Column(name = "credit_type") + private CreditType creditType; + + @Column(name = "balance") + @Getter + private int balance; + + @Builder + public UserCreditEntity(UserEntity user, CreditType creditType, int balance) { + this.user = user; + this.creditType = creditType; + this.balance = balance; + } + + public int updateBalance(int amount) { + return this.balance += amount; + } +} diff --git a/src/main/java/com/example/bloombackend/credit/repository/PurchaseLogRepository.java b/src/main/java/com/example/bloombackend/credit/repository/PurchaseLogRepository.java new file mode 100644 index 0000000..71f2eaa --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/repository/PurchaseLogRepository.java @@ -0,0 +1,8 @@ +package com.example.bloombackend.credit.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.bloombackend.credit.entity.PurchaseLogEntity; + +public interface PurchaseLogRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/bloombackend/credit/repository/UserCreditRepository.java b/src/main/java/com/example/bloombackend/credit/repository/UserCreditRepository.java new file mode 100644 index 0000000..82184b7 --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/repository/UserCreditRepository.java @@ -0,0 +1,12 @@ +package com.example.bloombackend.credit.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.bloombackend.credit.entity.CreditType; +import com.example.bloombackend.credit.entity.UserCreditEntity; + +public interface UserCreditRepository extends JpaRepository { + Optional findUserCreditEntityByUserIdAndCreditType(Long userId, CreditType creditType); +} diff --git a/src/main/java/com/example/bloombackend/credit/service/CreditService.java b/src/main/java/com/example/bloombackend/credit/service/CreditService.java new file mode 100644 index 0000000..f20cccd --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/service/CreditService.java @@ -0,0 +1,89 @@ +package com.example.bloombackend.credit.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.bloombackend.credit.controller.dto.request.PurchaseRequest; +import com.example.bloombackend.credit.controller.dto.response.PurchaseResponse; +import com.example.bloombackend.credit.entity.CreditType; +import com.example.bloombackend.credit.entity.PurchaseLogEntity; +import com.example.bloombackend.credit.entity.UserCreditEntity; +import com.example.bloombackend.credit.repository.PurchaseLogRepository; +import com.example.bloombackend.credit.repository.UserCreditRepository; +import com.example.bloombackend.credit.service.dto.UserCreditInfo; +import com.example.bloombackend.item.controller.dto.response.info.ItemInfo; +import com.example.bloombackend.item.service.ItemService; +import com.example.bloombackend.user.entity.UserEntity; + +import jakarta.persistence.EntityNotFoundException; + +@Service +public class CreditService { + private static final int INIT_BUTTON_CREDIT = 0; + private static final int INIT_CASH_CREDIT = 0; + + private final UserCreditRepository userCreditRepository; + private final PurchaseLogRepository purchaseLogRepository; + private final ItemService itemService; + + public CreditService(UserCreditRepository userCreditRepository, PurchaseLogRepository purchaseLogRepository, + ItemService itemService) { + this.userCreditRepository = userCreditRepository; + this.purchaseLogRepository = purchaseLogRepository; + this.itemService = itemService; + } + + @Transactional + public UserCreditInfo createUserCredit(UserEntity user) { + return new UserCreditInfo( + initCredit(user, CreditType.BUTTON, INIT_BUTTON_CREDIT).getBalance(), + initCredit(user, CreditType.CASH, INIT_CASH_CREDIT).getBalance()); + } + + private UserCreditEntity initCredit(UserEntity user, CreditType creditType, int initialBalance) { + return userCreditRepository.save( + UserCreditEntity.builder() + .creditType(creditType) + .balance(initialBalance) + .user(user) + .build() + ); + } + + @Transactional(readOnly = true) + public UserCreditInfo getUserCredit(Long userId) { + return new UserCreditInfo(getBalance(userId, CreditType.BUTTON), getBalance(userId, CreditType.CASH)); + } + + private int getBalance(Long userId, CreditType creditType) { + return userCreditRepository.findUserCreditEntityByUserIdAndCreditType(userId, creditType) + .map(UserCreditEntity::getBalance) + .orElseThrow(() -> new EntityNotFoundException("User not found with ID: " + userId)); + } + + @Transactional + public PurchaseResponse purchaseItem(Long userId, PurchaseRequest request) { + ItemInfo purchasedItem = itemService.addUserItem(userId, request.itemId()); + int balance = updateUserCredit(userId, -purchasedItem.price(), request.creditType()); + createPurchaseLog(userId, purchasedItem, request.creditType(), balance); + return new PurchaseResponse(purchasedItem, getUserCredit(userId)); + } + + private int updateUserCredit(Long userId, int amount, CreditType creditType) { + UserCreditEntity userCredit = userCreditRepository.findUserCreditEntityByUserIdAndCreditType(userId, creditType) + .orElseThrow(() -> new EntityNotFoundException("UserItem not found")); + return userCredit.updateBalance(amount); + } + + private void createPurchaseLog(Long userId, ItemInfo item, CreditType creditType, int balance) { + purchaseLogRepository.save( + PurchaseLogEntity.builder() + .userId(userId) + .itemId(item.id()) + .amount(item.price()) + .balance(balance) + .creditType(creditType) + .build() + ); + } +} diff --git a/src/main/java/com/example/bloombackend/credit/service/dto/UserCreditInfo.java b/src/main/java/com/example/bloombackend/credit/service/dto/UserCreditInfo.java new file mode 100644 index 0000000..a2c4f3a --- /dev/null +++ b/src/main/java/com/example/bloombackend/credit/service/dto/UserCreditInfo.java @@ -0,0 +1,7 @@ +package com.example.bloombackend.credit.service.dto; + +public record UserCreditInfo( + int buttonBalance, + int cashBalance +) { +} diff --git a/src/main/java/com/example/bloombackend/global/config/CacheConfig.java b/src/main/java/com/example/bloombackend/global/config/CacheConfig.java new file mode 100644 index 0000000..45ae6e2 --- /dev/null +++ b/src/main/java/com/example/bloombackend/global/config/CacheConfig.java @@ -0,0 +1,14 @@ +package com.example.bloombackend.global.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CacheConfig { + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("defaultItems"); + } +} diff --git a/src/main/java/com/example/bloombackend/item/controller/ItemController.java b/src/main/java/com/example/bloombackend/item/controller/ItemController.java index 24baa73..ac610e1 100644 --- a/src/main/java/com/example/bloombackend/item/controller/ItemController.java +++ b/src/main/java/com/example/bloombackend/item/controller/ItemController.java @@ -5,7 +5,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.example.bloombackend.global.config.annotation.CurrentUser; import com.example.bloombackend.item.controller.dto.response.ItemsResponse; +import com.example.bloombackend.item.controller.dto.response.UserItemsResponse; import com.example.bloombackend.item.service.ItemService; @RestController @@ -19,6 +21,13 @@ public ItemController(ItemService itemService) { @GetMapping("/sale") public ResponseEntity getItems() { - return ResponseEntity.ok(itemService.getItems()); + return ResponseEntity.ok(itemService.getOnSaleItems()); + } + + @GetMapping("/inventory") + public ResponseEntity getUserItems( + @CurrentUser Long userId + ) { + return ResponseEntity.ok(itemService.getUserItems(userId)); } } diff --git a/src/main/java/com/example/bloombackend/item/controller/dto/response/UserItemsResponse.java b/src/main/java/com/example/bloombackend/item/controller/dto/response/UserItemsResponse.java new file mode 100644 index 0000000..3103155 --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/controller/dto/response/UserItemsResponse.java @@ -0,0 +1,11 @@ +package com.example.bloombackend.item.controller.dto.response; + +import java.util.List; + +import com.example.bloombackend.item.controller.dto.response.info.ItemInfo; + +public record UserItemsResponse( + List defaultItems, + List purchasedItems +) { +} diff --git a/src/main/java/com/example/bloombackend/item/controller/dto/response/info/ItemInfo.java b/src/main/java/com/example/bloombackend/item/controller/dto/response/info/ItemInfo.java index b24959d..aed8e1d 100644 --- a/src/main/java/com/example/bloombackend/item/controller/dto/response/info/ItemInfo.java +++ b/src/main/java/com/example/bloombackend/item/controller/dto/response/info/ItemInfo.java @@ -1,7 +1,8 @@ package com.example.bloombackend.item.controller.dto.response.info; +import java.time.LocalDate; + import com.example.bloombackend.item.entity.ItemEntity; -import com.example.bloombackend.item.entity.ItemType; import lombok.Builder; @@ -10,15 +11,17 @@ public record ItemInfo( Long id, String name, Integer price, - String imgUrl, - ItemType type + String thumbnailUrl, + String type, + LocalDate endDate ) { public static ItemInfo from(ItemEntity item) { return ItemInfo.builder() .id(item.getId()) .name(item.getName()) .price(item.getPrice()) - .imgUrl(item.getImgUrl()) - .type(item.getType()).build(); + .thumbnailUrl(item.getThumbnailImgUrl()) + .type(item.getType()) + .endDate(item.getEndDate()).build(); } } diff --git a/src/main/java/com/example/bloombackend/item/entity/ItemEntity.java b/src/main/java/com/example/bloombackend/item/entity/ItemEntity.java index 2c0e7df..90dc07c 100644 --- a/src/main/java/com/example/bloombackend/item/entity/ItemEntity.java +++ b/src/main/java/com/example/bloombackend/item/entity/ItemEntity.java @@ -1,50 +1,52 @@ package com.example.bloombackend.item.entity; +import java.time.LocalDate; + import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import jakarta.persistence.Table; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn(name = "item_type") @Table(name = "item") -@Getter -public class ItemEntity { +@SuperBuilder +public abstract class ItemEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "item_id") + @Getter private Long id; - @Enumerated(EnumType.STRING) - @Column(name = "type", nullable = false) - private ItemType type; - @Column(name = "name", nullable = false) + @Getter private String name; @Column(name = "price", nullable = false) + @Getter private Integer price; - @Column(name = "img_url", nullable = false) - private String imgUrl; + @Column(name = "thumbnail", nullable = false) + @Getter + private String thumbnailImgUrl; + + @Column(name = "is_default") + private Boolean isDefault; - @Column(name = "is_sale") - private Boolean isSale; + @Column(name = "end_date") + @Getter + private LocalDate endDate; - @Builder - public ItemEntity(ItemType type, String name, Integer price, String imgUrl, Boolean isSale) { - this.type = type; - this.name = name; - this.price = price; - this.imgUrl = imgUrl; - this.isSale = isSale; - } + public abstract String getType(); } diff --git a/src/main/java/com/example/bloombackend/item/entity/ItemType.java b/src/main/java/com/example/bloombackend/item/entity/ItemType.java index 52a18dd..3982b3a 100644 --- a/src/main/java/com/example/bloombackend/item/entity/ItemType.java +++ b/src/main/java/com/example/bloombackend/item/entity/ItemType.java @@ -1,7 +1,7 @@ package com.example.bloombackend.item.entity; public enum ItemType { - SEED("씨앗"), SEASON("시즌"), BUTTON("단추"), DECO("장식"); + SEED("씨앗"), DECO("장식"); private final String label; diff --git a/src/main/java/com/example/bloombackend/item/entity/UserItemEntity.java b/src/main/java/com/example/bloombackend/item/entity/UserItemEntity.java new file mode 100644 index 0000000..9d8a14e --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/entity/UserItemEntity.java @@ -0,0 +1,39 @@ +package com.example.bloombackend.item.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "user_item") +@Getter +public class UserItemEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_item_id") + private Long id; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id", nullable = false) + private ItemEntity item; + + @Builder + public UserItemEntity(Long userId, ItemEntity item) { + this.userId = userId; + this.item = item; + } +} diff --git a/src/main/java/com/example/bloombackend/item/entity/items/SeedEntity.java b/src/main/java/com/example/bloombackend/item/entity/items/SeedEntity.java new file mode 100644 index 0000000..1cd6e39 --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/entity/items/SeedEntity.java @@ -0,0 +1,29 @@ +package com.example.bloombackend.item.entity.items; + +import com.example.bloombackend.item.entity.ItemEntity; +import com.example.bloombackend.item.entity.ItemType; + +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("SEED") +@SuperBuilder +public class SeedEntity extends ItemEntity { + @Column(name = "small_icon_url", nullable = false) + private String smallIconUrl; + + @Getter + @Column(name = "big_icon_url", nullable = false) + private String bigIconUrl; + + public String getType() { + return ItemType.SEED.toString(); + } +} diff --git a/src/main/java/com/example/bloombackend/item/repository/ItemRepository.java b/src/main/java/com/example/bloombackend/item/repository/ItemRepository.java index 452ba7d..b71c462 100644 --- a/src/main/java/com/example/bloombackend/item/repository/ItemRepository.java +++ b/src/main/java/com/example/bloombackend/item/repository/ItemRepository.java @@ -5,7 +5,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.example.bloombackend.item.entity.ItemEntity; +import com.example.bloombackend.item.repository.querydsl.ItemRepositoryCustom; -public interface ItemRepository extends JpaRepository { - List findByIsSale(Boolean isSale); +public interface ItemRepository extends JpaRepository, ItemRepositoryCustom { + List findByIsDefault(boolean isDefault); } diff --git a/src/main/java/com/example/bloombackend/item/repository/SeedRepository.java b/src/main/java/com/example/bloombackend/item/repository/SeedRepository.java new file mode 100644 index 0000000..adf611a --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/repository/SeedRepository.java @@ -0,0 +1,10 @@ +package com.example.bloombackend.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.bloombackend.item.entity.items.SeedEntity; + +@Repository("SeedRepository") +public interface SeedRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/bloombackend/item/repository/UserItemRepository.java b/src/main/java/com/example/bloombackend/item/repository/UserItemRepository.java new file mode 100644 index 0000000..36ea658 --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/repository/UserItemRepository.java @@ -0,0 +1,11 @@ +package com.example.bloombackend.item.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.bloombackend.item.entity.UserItemEntity; + +public interface UserItemRepository extends JpaRepository { + List findByUserId(Long id); +} diff --git a/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustom.java b/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustom.java new file mode 100644 index 0000000..9e10812 --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustom.java @@ -0,0 +1,9 @@ +package com.example.bloombackend.item.repository.querydsl; + +import java.util.List; + +import com.example.bloombackend.item.entity.ItemEntity; + +public interface ItemRepositoryCustom { + List findOnSaleItems(); +} diff --git a/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustomImpl.java b/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustomImpl.java new file mode 100644 index 0000000..87f5adf --- /dev/null +++ b/src/main/java/com/example/bloombackend/item/repository/querydsl/ItemRepositoryCustomImpl.java @@ -0,0 +1,28 @@ +package com.example.bloombackend.item.repository.querydsl; + +import java.time.LocalDate; +import java.util.List; + +import com.example.bloombackend.item.entity.ItemEntity; +import com.example.bloombackend.item.entity.QItemEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; + +public class ItemRepositoryCustomImpl implements ItemRepositoryCustom { + private final JPAQueryFactory queryFactory; + + public ItemRepositoryCustomImpl(JPAQueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + @Override + public List findOnSaleItems() { + QItemEntity item = QItemEntity.itemEntity; + + return queryFactory.selectFrom(item) + .where( + item.isDefault.eq(false), + item.endDate.goe(LocalDate.now()) + ) + .fetch(); + } +} diff --git a/src/main/java/com/example/bloombackend/item/service/ItemService.java b/src/main/java/com/example/bloombackend/item/service/ItemService.java index 295e34f..05e3d18 100644 --- a/src/main/java/com/example/bloombackend/item/service/ItemService.java +++ b/src/main/java/com/example/bloombackend/item/service/ItemService.java @@ -2,29 +2,74 @@ import java.util.List; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.example.bloombackend.item.controller.dto.response.ItemsResponse; +import com.example.bloombackend.item.controller.dto.response.UserItemsResponse; import com.example.bloombackend.item.controller.dto.response.info.ItemInfo; import com.example.bloombackend.item.entity.ItemEntity; +import com.example.bloombackend.item.entity.UserItemEntity; import com.example.bloombackend.item.repository.ItemRepository; +import com.example.bloombackend.item.repository.UserItemRepository; + +import jakarta.persistence.EntityNotFoundException; @Service public class ItemService { - private static final boolean IS_ON_SALE = true; + private static final boolean IS_DEFAULT = true; private final ItemRepository itemRepository; + private final UserItemRepository userItemRepository; - public ItemService(ItemRepository itemRepository) { + public ItemService(ItemRepository itemRepository, UserItemRepository userItemRepository) { this.itemRepository = itemRepository; + this.userItemRepository = userItemRepository; } - public ItemsResponse getItems() { - List items = itemRepository.findByIsSale(IS_ON_SALE); + @Transactional(readOnly = true) + public ItemsResponse getOnSaleItems() { + List items = itemRepository.findOnSaleItems(); return new ItemsResponse( items.stream() .map(ItemInfo::from) .toList() ); } + + @Transactional(readOnly = true) + public UserItemsResponse getUserItems(Long userId) { + return new UserItemsResponse(getDefaultItems(), getPurchasedItems(userId)); + } + + @Cacheable("defaultItems") + public List getDefaultItems() { + List items = itemRepository.findByIsDefault(IS_DEFAULT); + return items.stream() + .map(ItemInfo::from) + .toList(); + } + + private List getPurchasedItems(Long userId) { + List items = userItemRepository.findByUserId(userId); + return items.stream() + .map(UserItemEntity::getItem) + .map(ItemInfo::from) + .toList(); + } + + @Transactional + public ItemInfo addUserItem(Long userId, Long itemId) { + return ItemInfo.from(userItemRepository.save( + UserItemEntity.builder() + .userId(userId) + .item(findItemById(itemId)).build() + ).getItem()); + } + + public ItemEntity findItemById(Long itemId) { + return itemRepository.findById(itemId) + .orElseThrow(() -> new EntityNotFoundException("Item not fount:" + itemId)); + } } diff --git a/src/main/java/com/example/bloombackend/user/controller/dto/response/UserInfoResponse.java b/src/main/java/com/example/bloombackend/user/controller/dto/response/UserInfoResponse.java index a3d5334..6d7d276 100644 --- a/src/main/java/com/example/bloombackend/user/controller/dto/response/UserInfoResponse.java +++ b/src/main/java/com/example/bloombackend/user/controller/dto/response/UserInfoResponse.java @@ -1,4 +1,10 @@ package com.example.bloombackend.user.controller.dto.response; -public record UserInfoResponse(String nickname, String age, String gender, boolean isSurvey) { +import com.example.bloombackend.credit.service.dto.UserCreditInfo; +import com.example.bloombackend.user.controller.dto.response.info.UserInfo; + +import lombok.Builder; + +@Builder +public record UserInfoResponse(UserInfo userInfo, UserCreditInfo creditInfo) { } diff --git a/src/main/java/com/example/bloombackend/user/controller/dto/response/info/UserInfo.java b/src/main/java/com/example/bloombackend/user/controller/dto/response/info/UserInfo.java new file mode 100644 index 0000000..11bbfab --- /dev/null +++ b/src/main/java/com/example/bloombackend/user/controller/dto/response/info/UserInfo.java @@ -0,0 +1,4 @@ +package com.example.bloombackend.user.controller.dto.response.info; + +public record UserInfo(String nickname, String age, String gender, boolean isSurvey) { +} diff --git a/src/main/java/com/example/bloombackend/user/entity/UserEntity.java b/src/main/java/com/example/bloombackend/user/entity/UserEntity.java index 0be84b7..0d8eb4a 100644 --- a/src/main/java/com/example/bloombackend/user/entity/UserEntity.java +++ b/src/main/java/com/example/bloombackend/user/entity/UserEntity.java @@ -6,7 +6,7 @@ import org.hibernate.annotations.UpdateTimestamp; import com.example.bloombackend.oauth.OAuthProvider; -import com.example.bloombackend.user.controller.dto.response.UserInfoResponse; +import com.example.bloombackend.user.controller.dto.response.info.UserInfo; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -78,8 +78,8 @@ public void updateUserSurveyInfo(String newName, Age age, Gender gender, boolean this.isSurvey = true; } - public UserInfoResponse getUserInfo() { - return new UserInfoResponse(name, age.toString(), gender.toString(), isSurvey); + public UserInfo getUserInfo() { + return new UserInfo(name, age.toString(), gender.toString(), isSurvey); } public Long getId() { diff --git a/src/main/java/com/example/bloombackend/user/service/UserService.java b/src/main/java/com/example/bloombackend/user/service/UserService.java index ce45b98..2783cc0 100644 --- a/src/main/java/com/example/bloombackend/user/service/UserService.java +++ b/src/main/java/com/example/bloombackend/user/service/UserService.java @@ -4,6 +4,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.example.bloombackend.credit.service.CreditService; +import com.example.bloombackend.credit.service.dto.UserCreditInfo; import com.example.bloombackend.oauth.controller.dto.response.KakaoInfoResponse; import com.example.bloombackend.user.controller.dto.request.UserRegisterInfoRequest; import com.example.bloombackend.user.controller.dto.response.UserInfoResponse; @@ -17,10 +19,12 @@ @Service public class UserService { private final UserRepository userRepository; + private final CreditService creditService; @Autowired - public UserService(UserRepository userRepository) { + public UserService(UserRepository userRepository, CreditService creditService) { this.userRepository = userRepository; + this.creditService = creditService; } public Long findOrCreateUser(KakaoInfoResponse response) { @@ -49,11 +53,14 @@ public UserInfoResponse registerUserInfo(Long userId, UserRegisterInfoRequest re UserEntity user = findUserById(userId); user.updateUserSurveyInfo(request.nickname(), Age.valueOf(request.age()), Gender.valueOf(request.gender()), request.isSurvey()); - return user.getUserInfo(); + + UserCreditInfo creditInfo = creditService.createUserCredit(user); + + return new UserInfoResponse(user.getUserInfo(), creditInfo); } @Transactional(readOnly = true) public UserInfoResponse getUserInfo(Long userId) { - return findUserById(userId).getUserInfo(); + return new UserInfoResponse(findUserById(userId).getUserInfo(), creditService.getUserCredit(userId)); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dc5ff93..92501f8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + cache: + type: simple config: import: "optional:application-secret.yml" datasource: @@ -30,3 +32,4 @@ oauth: token-url: https://kauth.kakao.com/oauth/token user-info-url: https://kapi.kakao.com/v2/user/me user-name-attribute: id + diff --git a/src/test/java/com/example/bloombackend/restdocs/AchievementRestDocsTest.java b/src/test/java/com/example/bloombackend/restdocs/AchievementRestDocsTest.java index 0a8076e..68ddbd2 100644 --- a/src/test/java/com/example/bloombackend/restdocs/AchievementRestDocsTest.java +++ b/src/test/java/com/example/bloombackend/restdocs/AchievementRestDocsTest.java @@ -1,5 +1,16 @@ package com.example.bloombackend.restdocs; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.LocalDateTime; import com.example.bloombackend.achievement.controller.dto.request.AchievementLevelUpdateRequest; import com.example.bloombackend.achievement.controller.dto.request.FlowerRegisterRequest; import com.example.bloombackend.achievement.entity.DailyAchievementEntity; @@ -24,9 +35,19 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import java.lang.reflect.Field; -import java.time.LocalDateTime; - +import com.example.bloombackend.achievement.controller.dto.request.AchievementLevelUpdateRequest; +import com.example.bloombackend.achievement.controller.dto.request.FlowerRegisterRequest; +import com.example.bloombackend.achievement.entity.DailyAchievementEntity; +import com.example.bloombackend.achievement.repository.DailyAchievementRepository; +import com.example.bloombackend.global.AIUtil; +import com.example.bloombackend.global.config.JwtTokenProvider; +import com.example.bloombackend.item.entity.items.SeedEntity; +import com.example.bloombackend.item.repository.SeedRepository; +import com.example.bloombackend.oauth.OAuthProvider; +import com.example.bloombackend.user.entity.UserEntity; +import com.example.bloombackend.user.repository.UserRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.transaction.Transactional; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -43,6 +64,202 @@ @Transactional public class AchievementRestDocsTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private SeedRepository seedRepository; + + @Autowired + private DailyAchievementRepository dailyAchievementRepository; + + @Autowired + private UserRepository userRepository; + + @SpyBean + private JwtTokenProvider jwtTokenProvider; + + @SpyBean + private AIUtil aiUtil; + + private UserEntity testUser; + + private SeedEntity flowerEntity; + + private String mockToken; + + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() throws Exception { + objectMapper = new ObjectMapper(); + mockToken = "jwtToken"; + testUser = userRepository.save(new UserEntity(OAuthProvider.KAKAO, "testUser", "testId")); + doReturn(testUser.getId()).when(jwtTokenProvider).getUserIdFromToken(mockToken); + + // 외부 API 호출의 경우 테스트 시 Mocking 처리 + doReturn("AI 총평 데이터").when(aiUtil).generateCompletion(anyString()); + + // 테스트 용 꽃 엔티티 등록 + flowerEntity = seedRepository.save( + SeedEntity.builder() + .name("장미 씨앗") + .endDate(LocalDate.of(9999, 12, 31)) + .isDefault(false) + .thumbnailImgUrl("썸네일이미지") + .smallIconUrl("./resources/static/flower-icons/rose-small.svg") + .bigIconUrl("./resources/static/flower-icons/rose.svg") + .price(300).build() + ); + + // 7월 성취도 기록 생성 + for (int day = 1; day <= 10; day++) { + DailyAchievementEntity achievement = new DailyAchievementEntity(testUser, flowerEntity); + createAchievement(achievement, LocalDateTime.of(2024, 7, day, 0, 0), 9); // 7월 1일부터 5일까지 성취도 9 + } + + // 8월 성취도 기록 생성 + for (int day = 1; day <= 15; day++) { + DailyAchievementEntity achievement = new DailyAchievementEntity(testUser, flowerEntity); + createAchievement(achievement, LocalDateTime.of(2024, 8, day, 0, 0), 9); // 8월 1일부터 5일까지 성취도 9 + } + + // 9월 성취도 기록 생성 + DailyAchievementEntity achievement1 = new DailyAchievementEntity(testUser, flowerEntity); + DailyAchievementEntity achievement2 = new DailyAchievementEntity(testUser, flowerEntity); + DailyAchievementEntity achievement3 = new DailyAchievementEntity(testUser, flowerEntity); + createAchievement(achievement1, LocalDateTime.of(2024, 9, 1, 0, 0), 4); + createAchievement(achievement2, LocalDateTime.of(2024, 9, 2, 0, 0), 8); + createAchievement(achievement3, LocalDateTime.of(2024, 9, 7, 0, 0), 9); + } + + @Test + @DisplayName("API - 오늘의 꽃 등록") + void setDailyFlowerTest() throws Exception { + //given + FlowerRegisterRequest request = new FlowerRegisterRequest(flowerEntity.getId()); + + //when & then + mockMvc.perform(post("/api/achievement/flower") + .header("Authorization", mockToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(document("achievement/set-daily-flower", + requestFields( + fieldWithPath("flowerId").description("등록할 꽃 ID") + ) + )); + } + + @Test + @DisplayName("API - 성취도 레벨 업데이트") + void updateAchievementLevelTest() throws Exception { + //given + dailyAchievementRepository.save(new DailyAchievementEntity(testUser, flowerEntity)); + AchievementLevelUpdateRequest request = new AchievementLevelUpdateRequest(1); + + //when & then + mockMvc.perform(patch("/api/achievement") + .header("Authorization", mockToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(document("achievement/update-achievement-level", + requestFields( + fieldWithPath("increaseBy").description("증가 시킬 성취도 단계 값") + ), + responseFields( + fieldWithPath("updatedLevel").description("업데이트된 성취도 단계") + ) + )); + } + + @Test + @DisplayName("API - 월간 성취도 조회") + void getMonthlyAchievementsTest() throws Exception { + //when & then + mockMvc.perform(get("/api/achievement/monthly") + .header("Authorization", mockToken) + .param("month", "2024-09")) + .andExpect(status().isOk()) + .andDo(document("achievement/get-monthly-achievements", + queryParameters( + parameterWithName("month").description("조회할 월 (YYYY-MM)") + ), + responseFields( + fieldWithPath("dailyData[]").description("일별 데이터"), + fieldWithPath("dailyData[].date").description("날짜"), + fieldWithPath("dailyData[].flowerIconUrl").description("설정한 꽃 아이콘 URL"), + fieldWithPath("dailyData[].achievementLevel").description("성취 단계") + ) + )); + } + + @Test + @DisplayName("API - 최근 6개월 성취도 조회") + void getRecentSixMonthsAchievementsTest() throws Exception { + //when & then + mockMvc.perform(get("/api/achievement/recent-six-months") + .header("Authorization", mockToken)) + .andExpect(status().isOk()) + .andDo(document("achievement/get-recent-six-months-achievements", + responseFields( + fieldWithPath("monthlyData[]").description("월별 데이터"), + fieldWithPath("monthlyData[].month").description("조회된 월"), + fieldWithPath("monthlyData[].bloomed").description("꽃이 핀 횟수"), + fieldWithPath("averageBloomed").description("평균 꽃이 핀 횟수"), + fieldWithPath("aiSummary").description("AI 총평") + ) + )); + } + + @Test + @DisplayName("API - 오늘의 꽃 조회") + void getDailyFlowerTest() throws Exception { + //given + dailyAchievementRepository.save(new DailyAchievementEntity(testUser, flowerEntity)); + + //when & then + mockMvc.perform(get("/api/achievement/flower") + .header("Authorization", mockToken)) + .andExpect(status().isOk()) + .andDo(document("achievement/get-daily-flower", + responseFields( + fieldWithPath("flowerId").description("오늘의 꽃 ID"), + fieldWithPath("iconUrl").description("오늘의 꽃 아이콘 URL") + ) + )); + } + + @Test + @DisplayName("API - 오늘의 성취도 조회") + void getTodayAchievementTest() throws Exception { + //given + dailyAchievementRepository.save(new DailyAchievementEntity(testUser, flowerEntity)); + + //when & then + mockMvc.perform(get("/api/achievement") + .header("Authorization", mockToken)) + .andExpect(status().isOk()) + .andDo(document("achievement/get-today-achievement", + responseFields( + fieldWithPath("date").description("날짜"), + fieldWithPath("flowerIconUrl").description("설정한 꽃 아이콘 URL"), + fieldWithPath("achievementLevel").description("성취 단계") + ) + )); + } + + private void createAchievement(DailyAchievementEntity achievement, LocalDateTime dateTime, + int achievementLevel) throws Exception { + Field createdAtField = DailyAchievementEntity.class.getDeclaredField("createdAt"); + createdAtField.setAccessible(true); + createdAtField.set(achievement, dateTime); + achievement.increaseAchievementLevel(achievementLevel); + dailyAchievementRepository.save(achievement); + } +} @Autowired private MockMvc mockMvc; diff --git a/src/test/java/com/example/bloombackend/restdocs/BottleMessageRestDocsTest.java b/src/test/java/com/example/bloombackend/restdocs/BottleMessageRestDocsTest.java index 83dfeca..4d64119 100644 --- a/src/test/java/com/example/bloombackend/restdocs/BottleMessageRestDocsTest.java +++ b/src/test/java/com/example/bloombackend/restdocs/BottleMessageRestDocsTest.java @@ -76,7 +76,6 @@ public class BottleMessageRestDocsTest { @BeforeEach void setUp() { objectMapper = new ObjectMapper(); - mockToken = "jwtToken"; testUser = userRepository.save(new UserEntity(OAuthProvider.KAKAO, "testUser", "testId")); doNothing().when(jwtTokenProvider).validateAccessToken(mockToken); doReturn(testUser.getId()).when(jwtTokenProvider).getUserIdFromToken(mockToken); diff --git a/src/test/java/com/example/bloombackend/restdocs/CreditRestDocsTest.java b/src/test/java/com/example/bloombackend/restdocs/CreditRestDocsTest.java new file mode 100644 index 0000000..ec5a077 --- /dev/null +++ b/src/test/java/com/example/bloombackend/restdocs/CreditRestDocsTest.java @@ -0,0 +1,139 @@ +package com.example.bloombackend.restdocs; + +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.example.bloombackend.credit.controller.dto.request.PurchaseRequest; +import com.example.bloombackend.credit.entity.CreditType; +import com.example.bloombackend.credit.entity.UserCreditEntity; +import com.example.bloombackend.credit.repository.UserCreditRepository; +import com.example.bloombackend.global.config.JwtTokenProvider; +import com.example.bloombackend.item.entity.items.SeedEntity; +import com.example.bloombackend.item.repository.SeedRepository; +import com.example.bloombackend.oauth.OAuthProvider; +import com.example.bloombackend.user.entity.UserEntity; +import com.example.bloombackend.user.repository.UserRepository; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.transaction.Transactional; + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureRestDocs +@Transactional +public class CreditRestDocsTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private SeedRepository seedRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserCreditRepository userCreditRepository; + + @SpyBean + private JwtTokenProvider jwtTokenProvider; + + private UserEntity testUser; + + private String mockToken; + + private ObjectMapper objectMapper; + + private SeedEntity limitFlower; + + private UserCreditEntity button; + + private UserCreditEntity cash; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockToken = "jwtToken"; + testUser = userRepository.save(new UserEntity(OAuthProvider.KAKAO, "testUser", "testId")); + doReturn(testUser.getId()).when(jwtTokenProvider).getUserIdFromToken(mockToken); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String limitDate = "2025-01-01"; + + button = userCreditRepository.save( + UserCreditEntity.builder() + .creditType(CreditType.BUTTON) + .balance(500) + .user(testUser) + .build() + ); + + cash = userCreditRepository.save( + UserCreditEntity.builder() + .creditType(CreditType.CASH) + .balance(0) + .user(testUser) + .build() + ); + + limitFlower = seedRepository.save( + SeedEntity.builder() + .name("시즌한정 씨앗") + .endDate(LocalDate.parse(limitDate, formatter)) + .isDefault(false) + .thumbnailImgUrl("썸네일이미지") + .smallIconUrl("./resources/static/flower-icons/freesia-small.svg") + .bigIconUrl("./resources/static/flower-icons/freesia.svg") + .price(300).build() + ); + + } + + @Test + @DisplayName("API - 아이템 구매") + void purchaseItemTest() throws Exception { + // given + PurchaseRequest request = new PurchaseRequest(limitFlower.getId(), CreditType.BUTTON); + + // when & then + mockMvc.perform(post("/api/credit/purchase") + .header("Authorization", mockToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(document("api-credit-test/purchase", + preprocessResponse(prettyPrint()), + requestFields( + fieldWithPath("itemId").description("구매할 아이템 ID"), + fieldWithPath("creditType").description("사용할 크레딧 타입 (BUTTON, CASH)")), + responseFields( + fieldWithPath("purchasedItem").description("구매한 아이템 정보"), + fieldWithPath("purchasedItem.id").description("아이템 ID"), + fieldWithPath("purchasedItem.name").description("아이템 이름"), + fieldWithPath("purchasedItem.price").description("아이템 가격"), + fieldWithPath("purchasedItem.thumbnailUrl").description("아이템 썸네일 이미지 URL"), + fieldWithPath("purchasedItem.type").description("아이템 타입"), + fieldWithPath("purchasedItem.endDate").description("판매 종료일"), + fieldWithPath("balance").description("남은 크레딧 잔액"), + fieldWithPath("balance.buttonBalance").description("남은 버튼 잔액"), + fieldWithPath("balance.cashBalance").description("남은 현금 잔액")) + )); + } +} diff --git a/src/test/java/com/example/bloombackend/restdocs/ItemRestDocsTest.java b/src/test/java/com/example/bloombackend/restdocs/ItemRestDocsTest.java index 5267006..6606c7b 100644 --- a/src/test/java/com/example/bloombackend/restdocs/ItemRestDocsTest.java +++ b/src/test/java/com/example/bloombackend/restdocs/ItemRestDocsTest.java @@ -3,9 +3,13 @@ import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,10 +21,14 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.example.bloombackend.credit.entity.CreditType; +import com.example.bloombackend.credit.entity.UserCreditEntity; +import com.example.bloombackend.credit.repository.UserCreditRepository; import com.example.bloombackend.global.config.JwtTokenProvider; -import com.example.bloombackend.item.entity.ItemEntity; -import com.example.bloombackend.item.entity.ItemType; -import com.example.bloombackend.item.repository.ItemRepository; +import com.example.bloombackend.item.entity.UserItemEntity; +import com.example.bloombackend.item.entity.items.SeedEntity; +import com.example.bloombackend.item.repository.SeedRepository; +import com.example.bloombackend.item.repository.UserItemRepository; import com.example.bloombackend.oauth.OAuthProvider; import com.example.bloombackend.user.entity.UserEntity; import com.example.bloombackend.user.repository.UserRepository; @@ -37,11 +45,17 @@ public class ItemRestDocsTest { private MockMvc mockMvc; @Autowired - private ItemRepository itemRepository; + private SeedRepository seedRepository; @Autowired private UserRepository userRepository; + @Autowired + private UserItemRepository userItemRepository; + + @Autowired + private UserCreditRepository userCreditRepository; + @SpyBean private JwtTokenProvider jwtTokenProvider; @@ -51,9 +65,17 @@ public class ItemRestDocsTest { private ObjectMapper objectMapper; - private ItemEntity item1; - private ItemEntity item2; - private ItemEntity limitItem; + private SeedEntity rose; + + private SeedEntity limitFlower; + + private SeedEntity defaultFlower; + + private UserItemEntity userItem; + + private UserCreditEntity button; + + private UserCreditEntity cash; @BeforeEach void setUp() { @@ -62,34 +84,62 @@ void setUp() { testUser = userRepository.save(new UserEntity(OAuthProvider.KAKAO, "testUser", "testId")); doReturn(testUser.getId()).when(jwtTokenProvider).getUserIdFromToken(mockToken); - item1 = itemRepository.save( - ItemEntity.builder() - .type(ItemType.SEED) - .name("장미 씨앗") - .imgUrl("http://...") - .price(300) - .isSale(true) + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String limitDate = "2025-01-01"; + + button = userCreditRepository.save( + UserCreditEntity.builder() + .creditType(CreditType.BUTTON) + .balance(0) + .user(testUser) .build() ); - item2 = itemRepository.save( - ItemEntity.builder() - .type(ItemType.SEED) - .name("프리지아 씨앗") - .imgUrl("http://...") - .price(350) - .isSale(true) + cash = userCreditRepository.save( + UserCreditEntity.builder() + .creditType(CreditType.CASH) + .balance(0) + .user(testUser) .build() ); - limitItem = itemRepository.save( - ItemEntity.builder() - .type(ItemType.SEASON) - .name("프리지아 씨앗") - .imgUrl("http://...") - .price(550) - .isSale(false) - .build() + rose = seedRepository.save( + SeedEntity.builder() + .name("장미 씨앗") + .endDate(LocalDate.of(9999, 12, 31)) + .isDefault(false) + .thumbnailImgUrl("썸네일이미지") + .smallIconUrl("./resources/static/flower-icons/rose-small.svg") + .bigIconUrl("./resources/static/flower-icons/rose.svg") + .price(300).build() + ); + + limitFlower = seedRepository.save( + SeedEntity.builder() + .name("시즌한정 씨앗") + .endDate(LocalDate.parse(limitDate, formatter)) + .isDefault(false) + .thumbnailImgUrl("썸네일이미지") + .smallIconUrl("./resources/static/flower-icons/freesia-small.svg") + .bigIconUrl("./resources/static/flower-icons/freesia.svg") + .price(300).build() + ); + + defaultFlower = seedRepository.save( + SeedEntity.builder() + .name("기본씨앗 씨앗") + .endDate(LocalDate.of(9999, 12, 31)) + .isDefault(true) + .thumbnailImgUrl("썸네일이미지") + .smallIconUrl("./resources/static/flower-icons/freesia-small.svg") + .bigIconUrl("./resources/static/flower-icons/freesia.svg") + .price(300).build() + ); + + userItem = userItemRepository.save( + UserItemEntity.builder() + .userId(testUser.getId()) + .item(limitFlower).build() ); } @@ -102,15 +152,50 @@ void getItemsTest() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("api-item-test/get-items", + preprocessResponse(prettyPrint()), responseFields( fieldWithPath("items[]").description("판매 중인 아이템 목록"), fieldWithPath("items[].id").description("아이템 ID"), fieldWithPath("items[].name").description("아이템 이름"), fieldWithPath("items[].price").description("아이템 가격"), - fieldWithPath("items[].imgUrl").description("아이템 이미지 URL"), - fieldWithPath("items[].type").description("아이템 유형") + fieldWithPath("items[].thumbnailUrl").description("아이템 구매용 이미지 URL"), + fieldWithPath("items[].type").description("아이템 유형"), + fieldWithPath("items[].endDate").description( + "판매 마감일 +" + "\n" + + "- 시즌 한정 아이템의 경우, 실제 마감일이 표시됩니다. +" + "\n" + + "- 지속 판매 아이템의 경우, \"9999-12-31\"로 고정됩니다.") ) )); } + @Test + @DisplayName("API - 유저의 인벤토리 아이템 목록 조회") + void getUserItemsTest() throws Exception { + // when & then + // 테스트 실행 + mockMvc.perform(get("/api/item/inventory") + .header("Authorization", mockToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("api-item-test/get-user-items", + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("defaultItems[]").description("유저가 가진 기본 아이템 목록"), + fieldWithPath("defaultItems[].id").description("아이템 ID"), + fieldWithPath("defaultItems[].name").description("아이템 이름"), + fieldWithPath("defaultItems[].price").description("아이템 가격"), + fieldWithPath("defaultItems[].thumbnailUrl").description("아이템 썸네일 이미지 URL"), + fieldWithPath("defaultItems[].type").description("아이템 타입"), + fieldWithPath("defaultItems[].endDate").description("판매 종료일"), + + fieldWithPath("purchasedItems[]").description("유저가 구매한 아이템 목록"), + fieldWithPath("purchasedItems[].id").description("아이템 ID"), + fieldWithPath("purchasedItems[].name").description("아이템 이름"), + fieldWithPath("purchasedItems[].price").description("아이템 가격"), + fieldWithPath("purchasedItems[].thumbnailUrl").description("아이템 썸네일 이미지 URL"), + fieldWithPath("purchasedItems[].type").description("아이템 타입"), + fieldWithPath("purchasedItems[].endDate").description("판매 종료일") + ) + )); + } } diff --git a/src/test/java/com/example/bloombackend/restdocs/UserRestDocsTest.java b/src/test/java/com/example/bloombackend/restdocs/UserRestDocsTest.java index aecdb46..9a7ccc2 100644 --- a/src/test/java/com/example/bloombackend/restdocs/UserRestDocsTest.java +++ b/src/test/java/com/example/bloombackend/restdocs/UserRestDocsTest.java @@ -3,6 +3,7 @@ import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -20,10 +21,9 @@ import com.example.bloombackend.oauth.util.JwtTokenProvider; import com.example.bloombackend.oauth.OAuthProvider; import com.example.bloombackend.user.controller.dto.request.UserRegisterInfoRequest; -import com.example.bloombackend.user.entity.Age; -import com.example.bloombackend.user.entity.Gender; import com.example.bloombackend.user.entity.UserEntity; import com.example.bloombackend.user.repository.UserRepository; +import com.example.bloombackend.user.service.UserService; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; @@ -39,6 +39,9 @@ public class UserRestDocsTest { @Autowired private UserRepository userRepository; + @Autowired + private UserService userService; + @SpyBean private JwtTokenProvider jwtTokenProvider; @@ -62,28 +65,25 @@ void setUp() throws Exception { @Test @DisplayName("API - 설문에 따른 유저 정보 업데이트") void updateUserInfoTest() throws Exception { - //given + // given UserRegisterInfoRequest request = new UserRegisterInfoRequest("원지", "FROM_18_TO_24", "F", true); - //when & then + // when & then mockMvc.perform(put("/api/user") .header("Authorization", mockToken) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andDo(document("api-user-test/update-info", + preprocessResponse(prettyPrint()), responseFields( - fieldWithPath("nickname").description("변경할 유저의 닉네임"), - fieldWithPath("age").description( + fieldWithPath("userInfo.nickname").description("변경할 유저의 닉네임"), + fieldWithPath("userInfo.age").description( "유저의 나잇대(UNDER_18, FROM_18_TO_24, FROM_25_TO_34, FROM_35_TO_44, OVER_45)"), - fieldWithPath("gender").description("유저의 성별(M,F,O)"), - fieldWithPath("isSurvey").description("유저의 설문 완료 여부") - ), - responseFields( - fieldWithPath("nickname").description("변경할 유저의 닉네임"), - fieldWithPath("age").description("유저의 나잇대"), - fieldWithPath("gender").description("유저의 성별"), - fieldWithPath("isSurvey").description("유저의 설문 완료 여부") + fieldWithPath("userInfo.gender").description("유저의 성별(M,F,O)"), + fieldWithPath("userInfo.isSurvey").description("유저의 설문 완료 여부"), + fieldWithPath("creditInfo.buttonBalance").description("유저의 버튼 크레딧 잔액"), + fieldWithPath("creditInfo.cashBalance").description("유저의 현금 크레딧 잔액") ) )); } @@ -91,20 +91,24 @@ void updateUserInfoTest() throws Exception { @Test @DisplayName("API - 유저 정보 조회") void getUserInfoTest() throws Exception { - //given - testUser.updateUserSurveyInfo("원지", Age.FROM_18_TO_24, Gender.F, true); + // given + UserRegisterInfoRequest request = new UserRegisterInfoRequest("원지", "FROM_18_TO_24", "F", true); + userService.registerUserInfo(testUser.getId(), request); - //when & then + // when & then mockMvc.perform(get("/api/user") .header("Authorization", mockToken) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("api-user-test/get-info", + preprocessResponse(prettyPrint()), responseFields( - fieldWithPath("nickname").description("변경할 유저의 닉네임"), - fieldWithPath("age").description("유저의 나잇대"), - fieldWithPath("gender").description("유저의 성별"), - fieldWithPath("isSurvey").description("유저의 설문 완료 여부") + fieldWithPath("userInfo.nickname").description("유저의 닉네임"), + fieldWithPath("userInfo.age").description("유저의 나잇대"), + fieldWithPath("userInfo.gender").description("유저의 성별"), + fieldWithPath("userInfo.isSurvey").description("유저의 설문 완료 여부"), + fieldWithPath("creditInfo.buttonBalance").description("유저의 버튼 크레딧 잔액"), + fieldWithPath("creditInfo.cashBalance").description("유저의 현금 크레딧 잔액") ) )); }