diff --git a/build.gradle b/build.gradle index dce7e33..10b8caa 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ description = 'Spring_Boot_A' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } @@ -25,20 +25,45 @@ repositories { } dependencies { - // --- Jakarta Validation --- - implementation 'jakarta.validation:jakarta.validation-api:3.0.2' - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' - // --- QueryDSL --- + /* --------------------------------------------------------------- + * Spring Boot Core + * --------------------------------------------------------------- */ + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + /* --------------------------------------------------------------- + * Validation (Boot starter로 교체) + * --------------------------------------------------------------- */ + implementation 'org.springframework.boot:spring-boot-starter-validation' + + /* --------------------------------------------------------------- + * MySQL + * --------------------------------------------------------------- */ + runtimeOnly 'com.mysql:mysql-connector-j' + + /* --------------------------------------------------------------- + * QueryDSL (Jakarta 버전) + * --------------------------------------------------------------- */ implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' - annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1' annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' + annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1' + + /* --------------------------------------------------------------- + * Lombok + * --------------------------------------------------------------- */ compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + + /* --------------------------------------------------------------- + * Swagger (SpringDoc) + * --------------------------------------------------------------- */ + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + + /* --------------------------------------------------------------- + * Test + * --------------------------------------------------------------- */ testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/QStore.java b/src/main/generated/com/example/spring_boot_a/domain/entity/QStore.java new file mode 100644 index 0000000..b6b48cf --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/QStore.java @@ -0,0 +1,62 @@ +package com.example.spring_boot_a.domain.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.example.spring_boot_a.domain.entity.mission.Mission; +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; + + +/** + * QStore is a Querydsl query type for Store + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QStore extends EntityPathBase { + + private static final long serialVersionUID = 1205412615L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QStore store = new QStore("store"); + + public final StringPath detailAddress = createString("detailAddress"); + + public final com.example.spring_boot_a.domain.entity.etc.QLocation location; + + public final NumberPath managerNumber = createNumber("managerNumber", Long.class); + + public final SetPath missions = this.createSet("missions", Mission.class, com.example.spring_boot_a.domain.entity.etc.QMission.class, PathInits.DIRECT2); + + public final StringPath name = createString("name"); + + public final SetPath reviews = this.createSet("reviews", com.example.spring_boot_a.domain.entity.review.Review.class, com.example.spring_boot_a.domain.entity.review.QReview.class, PathInits.DIRECT2); + + public final NumberPath storeId = createNumber("storeId", Long.class); + + public QStore(String variable) { + this(Store.class, forVariable(variable), INITS); + } + + public QStore(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QStore(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QStore(PathMetadata metadata, PathInits inits) { + this(Store.class, metadata, inits); + } + + public QStore(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.location = inits.isInitialized("location") ? new com.example.spring_boot_a.domain.entity.etc.QLocation(forProperty("location")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/QTerm.java b/src/main/generated/com/example/spring_boot_a/domain/entity/QTerm.java new file mode 100644 index 0000000..f59f2ed --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/QTerm.java @@ -0,0 +1,42 @@ +package com.example.spring_boot_a.domain.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; + + +/** + * QTerm is a Querydsl query type for Term + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QTerm extends EntityPathBase { + + private static final long serialVersionUID = -653836922L; + + public static final QTerm term = new QTerm("term"); + + public final EnumPath name = createEnum("name", com.example.spring_boot_a.domain.entity.enums.TermType.class); + + public final NumberPath termId = createNumber("termId", Long.class); + + public final SetPath userTerms = this.createSet("userTerms", com.example.spring_boot_a.domain.entity.user.UserTerm.class, com.example.spring_boot_a.domain.entity.user.QUserTerm.class, PathInits.DIRECT2); + + public QTerm(String variable) { + super(Term.class, forVariable(variable)); + } + + public QTerm(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QTerm(PathMetadata metadata) { + super(Term.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QFood.java b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QFood.java new file mode 100644 index 0000000..904dab6 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QFood.java @@ -0,0 +1,42 @@ +package com.example.spring_boot_a.domain.entity.etc; + +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; + + +/** + * QFood is a Querydsl query type for Food + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QFood extends EntityPathBase { + + private static final long serialVersionUID = 1124304018L; + + public static final QFood food = new QFood("food"); + + public final NumberPath foodId = createNumber("foodId", Long.class); + + public final EnumPath name = createEnum("name", com.example.spring_boot_a.domain.entity.enums.FoodType.class); + + public final SetPath userFoods = this.createSet("userFoods", com.example.spring_boot_a.domain.entity.user.UserFood.class, com.example.spring_boot_a.domain.entity.user.QUserFood.class, PathInits.DIRECT2); + + public QFood(String variable) { + super(Food.class, forVariable(variable)); + } + + public QFood(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QFood(PathMetadata metadata) { + super(Food.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QLocation.java b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QLocation.java new file mode 100644 index 0000000..1b2cde9 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QLocation.java @@ -0,0 +1,42 @@ +package com.example.spring_boot_a.domain.entity.etc; + +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; + + +/** + * QLocation is a Querydsl query type for Location + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QLocation extends EntityPathBase { + + private static final long serialVersionUID = -1333535255L; + + public static final QLocation location = new QLocation("location"); + + public final NumberPath locationId = createNumber("locationId", Long.class); + + public final StringPath name = createString("name"); + + public final SetPath stores = this.createSet("stores", com.example.spring_boot_a.domain.entity.Store.class, com.example.spring_boot_a.domain.entity.QStore.class, PathInits.DIRECT2); + + public QLocation(String variable) { + super(Location.class, forVariable(variable)); + } + + public QLocation(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QLocation(PathMetadata metadata) { + super(Location.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QMission.java b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QMission.java new file mode 100644 index 0000000..1962e64 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QMission.java @@ -0,0 +1,62 @@ +package com.example.spring_boot_a.domain.entity.etc; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.example.spring_boot_a.domain.entity.mission.Mission; +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; + + +/** + * QMission is a Querydsl query type for Mission + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMission extends EntityPathBase { + + private static final long serialVersionUID = -558912296L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QMission mission = new QMission("mission"); + + public final StringPath conditional = createString("conditional"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.Instant.class); + + public final DatePath deadline = createDate("deadline", java.time.LocalDate.class); + + public final NumberPath missionId = createNumber("missionId", Long.class); + + public final NumberPath point = createNumber("point", Integer.class); + + public final com.example.spring_boot_a.domain.entity.QStore store; + + public final SetPath userMissions = this.createSet("userMissions", com.example.spring_boot_a.domain.entity.user.UserMission.class, com.example.spring_boot_a.domain.entity.user.QUserMission.class, PathInits.DIRECT2); + + public QMission(String variable) { + this(Mission.class, forVariable(variable), INITS); + } + + public QMission(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QMission(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QMission(PathMetadata metadata, PathInits inits) { + this(Mission.class, metadata, inits); + } + + public QMission(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.store = inits.isInitialized("store") ? new com.example.spring_boot_a.domain.entity.QStore(forProperty("store"), inits.get("store")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReply.java b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReply.java new file mode 100644 index 0000000..1107083 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReply.java @@ -0,0 +1,53 @@ +package com.example.spring_boot_a.domain.entity.etc; + +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; + + +/** + * QReply is a Querydsl query type for Reply + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QReply extends EntityPathBase { + + private static final long serialVersionUID = 504471862L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QReply reply = new QReply("reply"); + + public final StringPath content = createString("content"); + + public final NumberPath replyId = createNumber("replyId", Long.class); + + public final com.example.spring_boot_a.domain.entity.review.QReview review; + + public QReply(String variable) { + this(Reply.class, forVariable(variable), INITS); + } + + public QReply(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QReply(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QReply(PathMetadata metadata, PathInits inits) { + this(Reply.class, metadata, inits); + } + + public QReply(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.review = inits.isInitialized("review") ? new com.example.spring_boot_a.domain.entity.review.QReview(forProperty("review"), inits.get("review")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReviewPhoto.java b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReviewPhoto.java new file mode 100644 index 0000000..af2d73f --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/etc/QReviewPhoto.java @@ -0,0 +1,53 @@ +package com.example.spring_boot_a.domain.entity.etc; + +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; + + +/** + * QReviewPhoto is a Querydsl query type for ReviewPhoto + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QReviewPhoto extends EntityPathBase { + + private static final long serialVersionUID = 1892209414L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QReviewPhoto reviewPhoto = new QReviewPhoto("reviewPhoto"); + + public final StringPath photoUrl = createString("photoUrl"); + + public final com.example.spring_boot_a.domain.entity.review.QReview review; + + public final NumberPath reviewPhotoId = createNumber("reviewPhotoId", Long.class); + + public QReviewPhoto(String variable) { + this(ReviewPhoto.class, forVariable(variable), INITS); + } + + public QReviewPhoto(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QReviewPhoto(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QReviewPhoto(PathMetadata metadata, PathInits inits) { + this(ReviewPhoto.class, metadata, inits); + } + + public QReviewPhoto(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.review = inits.isInitialized("review") ? new com.example.spring_boot_a.domain.entity.review.QReview(forProperty("review"), inits.get("review")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/review/QReview.java b/src/main/generated/com/example/spring_boot_a/domain/entity/review/QReview.java new file mode 100644 index 0000000..ff233cf --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/review/QReview.java @@ -0,0 +1,64 @@ +package com.example.spring_boot_a.domain.entity.review; + +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; + + +/** + * QReview is a Querydsl query type for Review + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QReview extends EntityPathBase { + + private static final long serialVersionUID = 247763732L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QReview review = new QReview("review"); + + public final StringPath content = createString("content"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.Instant.class); + + public final SetPath photos = this.createSet("photos", com.example.spring_boot_a.domain.entity.etc.ReviewPhoto.class, com.example.spring_boot_a.domain.entity.etc.QReviewPhoto.class, PathInits.DIRECT2); + + public final SetPath replies = this.createSet("replies", com.example.spring_boot_a.domain.entity.etc.Reply.class, com.example.spring_boot_a.domain.entity.etc.QReply.class, PathInits.DIRECT2); + + public final NumberPath reviewId = createNumber("reviewId", Long.class); + + public final NumberPath star = createNumber("star", Float.class); + + public final com.example.spring_boot_a.domain.entity.QStore store; + + public final com.example.spring_boot_a.domain.entity.user.QUser user; + + public QReview(String variable) { + this(Review.class, forVariable(variable), INITS); + } + + public QReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QReview(PathMetadata metadata, PathInits inits) { + this(Review.class, metadata, inits); + } + + public QReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.store = inits.isInitialized("store") ? new com.example.spring_boot_a.domain.entity.QStore(forProperty("store"), inits.get("store")) : null; + this.user = inits.isInitialized("user") ? new com.example.spring_boot_a.domain.entity.user.QUser(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUser.java b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUser.java new file mode 100644 index 0000000..afb9af2 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUser.java @@ -0,0 +1,70 @@ +package com.example.spring_boot_a.domain.entity.user; + +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; + + +/** + * QUser is a Querydsl query type for User + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUser extends EntityPathBase { + + private static final long serialVersionUID = -699737228L; + + public static final QUser user = new QUser("user"); + + public final EnumPath address = createEnum("address", com.example.spring_boot_a.domain.entity.enums.AddressGu.class); + + public final DatePath birth = createDate("birth", java.time.LocalDate.class); + + public final DateTimePath deletedAt = createDateTime("deletedAt", java.time.Instant.class); + + public final StringPath detailAddress = createString("detailAddress"); + + public final StringPath email = createString("email"); + + public final EnumPath gender = createEnum("gender", com.example.spring_boot_a.domain.entity.enums.Gender.class); + + public final StringPath name = createString("name"); + + public final StringPath phoneNumber = createString("phoneNumber"); + + public final NumberPath point = createNumber("point", Integer.class); + + public final SetPath reviews = this.createSet("reviews", com.example.spring_boot_a.domain.entity.review.Review.class, com.example.spring_boot_a.domain.entity.review.QReview.class, PathInits.DIRECT2); + + public final EnumPath socialType = createEnum("socialType", com.example.spring_boot_a.domain.entity.enums.SocialLoginType.class); + + public final StringPath socialUid = createString("socialUid"); + + public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.Instant.class); + + public final SetPath userFoods = this.createSet("userFoods", UserFood.class, QUserFood.class, PathInits.DIRECT2); + + public final NumberPath userId = createNumber("userId", Long.class); + + public final SetPath userMissions = this.createSet("userMissions", UserMission.class, QUserMission.class, PathInits.DIRECT2); + + public final SetPath userTerms = this.createSet("userTerms", UserTerm.class, QUserTerm.class, PathInits.DIRECT2); + + public QUser(String variable) { + super(User.class, forVariable(variable)); + } + + public QUser(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QUser(PathMetadata metadata) { + super(User.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserFood.java b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserFood.java new file mode 100644 index 0000000..60dc19f --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserFood.java @@ -0,0 +1,54 @@ +package com.example.spring_boot_a.domain.entity.user; + +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; + + +/** + * QUserFood is a Querydsl query type for UserFood + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserFood extends EntityPathBase { + + private static final long serialVersionUID = -1242988046L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUserFood userFood = new QUserFood("userFood"); + + public final com.example.spring_boot_a.domain.entity.etc.QFood food; + + public final QUser user; + + public final NumberPath userFoodId = createNumber("userFoodId", Long.class); + + public QUserFood(String variable) { + this(UserFood.class, forVariable(variable), INITS); + } + + public QUserFood(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUserFood(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUserFood(PathMetadata metadata, PathInits inits) { + this(UserFood.class, metadata, inits); + } + + public QUserFood(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.food = inits.isInitialized("food") ? new com.example.spring_boot_a.domain.entity.etc.QFood(forProperty("food")) : null; + this.user = inits.isInitialized("user") ? new QUser(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserMission.java b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserMission.java new file mode 100644 index 0000000..a9ade90 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserMission.java @@ -0,0 +1,56 @@ +package com.example.spring_boot_a.domain.entity.user; + +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; + + +/** + * QUserMission is a Querydsl query type for UserMission + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserMission extends EntityPathBase { + + private static final long serialVersionUID = -1193790600L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUserMission userMission = new QUserMission("userMission"); + + public final BooleanPath isComplete = createBoolean("isComplete"); + + public final com.example.spring_boot_a.domain.entity.etc.QMission mission; + + public final QUser user; + + public final NumberPath userMissionId = createNumber("userMissionId", Long.class); + + public QUserMission(String variable) { + this(UserMission.class, forVariable(variable), INITS); + } + + public QUserMission(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUserMission(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUserMission(PathMetadata metadata, PathInits inits) { + this(UserMission.class, metadata, inits); + } + + public QUserMission(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.mission = inits.isInitialized("mission") ? new com.example.spring_boot_a.domain.entity.etc.QMission(forProperty("mission"), inits.get("mission")) : null; + this.user = inits.isInitialized("user") ? new QUser(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserTerm.java b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserTerm.java new file mode 100644 index 0000000..9e58f54 --- /dev/null +++ b/src/main/generated/com/example/spring_boot_a/domain/entity/user/QUserTerm.java @@ -0,0 +1,54 @@ +package com.example.spring_boot_a.domain.entity.user; + +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; + + +/** + * QUserTerm is a Querydsl query type for UserTerm + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserTerm extends EntityPathBase { + + private static final long serialVersionUID = -1242580480L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUserTerm userTerm = new QUserTerm("userTerm"); + + public final com.example.spring_boot_a.domain.entity.QTerm term; + + public final QUser user; + + public final NumberPath userTermId = createNumber("userTermId", Long.class); + + public QUserTerm(String variable) { + this(UserTerm.class, forVariable(variable), INITS); + } + + public QUserTerm(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUserTerm(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUserTerm(PathMetadata metadata, PathInits inits) { + this(UserTerm.class, metadata, inits); + } + + public QUserTerm(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.term = inits.isInitialized("term") ? new com.example.spring_boot_a.domain.entity.QTerm(forProperty("term")) : null; + this.user = inits.isInitialized("user") ? new QUser(forProperty("user")) : null; + } + +} + diff --git a/src/main/java/com/example/spring_boot_a/config/SwaggerConfig.java b/src/main/java/com/example/spring_boot_a/config/SwaggerConfig.java new file mode 100644 index 0000000..7b8a032 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package com.example.spring_boot_a.config; + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI swagger() { + Info info = new Info().title("Project").description("Project Swagger").version("0.0.1"); + + // JWT 토큰 헤더 방식 + String securityScheme = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme); + + Components components = new Components() + .addSecuritySchemes(securityScheme, new SecurityScheme() + .name(securityScheme) + .type(SecurityScheme.Type.HTTP) + .scheme("Bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} diff --git a/src/main/java/com/example/spring_boot_a/controller/MissionController.java b/src/main/java/com/example/spring_boot_a/controller/MissionController.java new file mode 100644 index 0000000..0e9d425 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/controller/MissionController.java @@ -0,0 +1,91 @@ +package com.example.spring_boot_a.controller; + + +import com.example.spring_boot_a.domain.entity.mission.dto.MissionChallengeRequest; +import com.example.spring_boot_a.domain.entity.mission.dto.MissionChallengeResponse; +import com.example.spring_boot_a.domain.entity.mission.dto.MyInProgressMissionResponse; +import com.example.spring_boot_a.domain.entity.mission.dto.StoreMissionListResponse; +import com.example.spring_boot_a.domain.entity.user.UserMission; +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.global.paging.ValidPage; +import com.example.spring_boot_a.service.MissionChallengeService; +import com.example.spring_boot_a.service.MyInProgressMissionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "미션", description = "미션 조회/도전 API") +@RestController +@RequestMapping("/api/missions") +@RequiredArgsConstructor +public class MissionController { + + private final MissionChallengeService missionChallengeService; + private final MyInProgressMissionService myInProgressMissionService; + + + @Operation( + summary = "특정 가게의 미션 목록 조회", + description = "한 페이지에 10개씩 조회. page 는 1 이상의 정수 (0, 음수 전달 시 에러)." + ) + @GetMapping("/stores/{storeId}") + public ApiResponse getStoreMissions( + @PathVariable Long storeId, + @Parameter(description = "1 이상의 page 번호") + @RequestParam @Valid @ValidPage String page + ) { + int pageIndex = Integer.parseInt(page) - 1; // 0-base 로 변환 + StoreMissionListResponse response = + missionChallengeService.getStoreMissions(storeId, pageIndex); + + return ApiResponse.onSuccess(response); + } + + @Operation( + summary = "미션 도전하기", + description = "특정 가게의 특정 미션을 사용자(userId)가 도전 시작." + ) + @PostMapping("/stores/{storeId}/{missionId}/challenge") + public ApiResponse challengeMission( + @PathVariable Long storeId, + @PathVariable Long missionId, + @RequestBody MissionChallengeRequest request + ) { + UserMission userMission = missionChallengeService + .challengeMission(storeId, missionId, request.userId()); + + MissionChallengeResponse response = new MissionChallengeResponse( + userMission.getUserMissionId(), + userMission.getMission().getMissionId(), + userMission.getMission().getStore().getStoreId(), + userMission.getMission().getDeadline(), + userMission.getMission().getPoint(), + userMission.getStatus().name() + ); + + return ApiResponse.onSuccess(response); + } + + + @Operation( + summary = "내가 진행중인 미션 목록", + description = "UserMission status = IN_PROGRESS 인 미션을 10개씩 페이징 조회" + ) + @GetMapping("/me/in-progress") + public ApiResponse> getMyInProgress( + // TODO: 실제 구현에서는 로그인 유저에서 userId 가져오면 됨 + @Parameter(description = "유저 ID (과제용)") @RequestParam Long userId, + @Parameter(description = "1 이상 page 번호") + @RequestParam @Valid @ValidPage String page + ) { + int pageIndex = Integer.parseInt(page) - 1; + + ApiResponse.PageResponse result = + myInProgressMissionService.getMyInProgressMissions(userId, pageIndex); + + return ApiResponse.onSuccess(result); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/spring_boot_a/api/review/MyReviewController.java b/src/main/java/com/example/spring_boot_a/controller/MyReviewController.java similarity index 80% rename from src/main/java/com/example/spring_boot_a/api/review/MyReviewController.java rename to src/main/java/com/example/spring_boot_a/controller/MyReviewController.java index c7e9219..202ab63 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/MyReviewController.java +++ b/src/main/java/com/example/spring_boot_a/controller/MyReviewController.java @@ -1,7 +1,7 @@ -package com.example.spring_boot_a.api.review; +package com.example.spring_boot_a.controller; -import com.example.spring_boot_a.api.review.dto.MyReviewItemDto; -import com.example.spring_boot_a.domain.service.MyReviewQueryService; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewItemDto; +import com.example.spring_boot_a.service.MyReviewQueryService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/example/spring_boot_a/api/review/ReviewController.java b/src/main/java/com/example/spring_boot_a/controller/ReviewController.java similarity index 58% rename from src/main/java/com/example/spring_boot_a/api/review/ReviewController.java rename to src/main/java/com/example/spring_boot_a/controller/ReviewController.java index 769fcc5..fa8521d 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/ReviewController.java +++ b/src/main/java/com/example/spring_boot_a/controller/ReviewController.java @@ -1,6 +1,14 @@ -package com.example.spring_boot_a.api.review; +package com.example.spring_boot_a.controller; -import com.example.spring_boot_a.api.review.dto.*; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewResponse; +import com.example.spring_boot_a.domain.entity.review.dto.ReviewCreateRequest; +import com.example.spring_boot_a.domain.entity.review.dto.ReviewResponse; +import com.example.spring_boot_a.domain.entity.review.dto.StarSummaryResponse; +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.global.paging.ValidPage; +import com.example.spring_boot_a.service.ReviewService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; import org.springframework.data.domain.*; import org.springframework.web.bind.annotation.*; @@ -50,4 +58,19 @@ public Page getMyReviews( public StarSummaryResponse getStarSummary(@PathVariable Long storeId) { return reviewService.getStarSummary(storeId); } + + @Operation( + summary = "내가 작성한 리뷰 목록", + description = "한 페이지에 10개씩, page는 1 이상의 정수 (0, 음수 전달 시 에러)" + ) + @GetMapping("/me") + public ApiResponse> getMyReviews( + + @Parameter(description = "유저 ID (과제용)") @RequestParam Long userId, + @Parameter(description = "1 이상 page 번호") + @RequestParam @Valid @ValidPage String page + ) { + int pageIndex = Integer.parseInt(page) - 1; // 0-base로 변환 + return ApiResponse.onSuccess(reviewService.getMyReviews(userId, pageIndex)); + } } diff --git a/src/main/java/com/example/spring_boot_a/controller/UserController.java b/src/main/java/com/example/spring_boot_a/controller/UserController.java new file mode 100644 index 0000000..7f8b57d --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/controller/UserController.java @@ -0,0 +1,66 @@ +package com.example.spring_boot_a.controller; + +import com.example.spring_boot_a.domain.entity.user.converter.UserConverter; +import com.example.spring_boot_a.domain.entity.user.dto.UserRequestDto; +import com.example.spring_boot_a.domain.entity.user.dto.UserResponseDto; +import com.example.spring_boot_a.domain.entity.user.User; +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.global.apiPayload.code.GeneralSuccessCode; +import com.example.spring_boot_a.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/users") +public class UserController { + + private final UserService userService; + private final UserConverter userConverter; + + + @PostMapping + public ResponseEntity> createUser( + @Valid @RequestBody UserRequestDto.Create request + ) { + User user = userConverter.toUser(request); + + User saved = userService.createUser(user); + + UserResponseDto.CreateResult responseDTO = userConverter.toCreateResult(saved); + + return ResponseEntity + .status(GeneralSuccessCode.CREATED.getStatus()) + .body(ApiResponse.onSuccess(GeneralSuccessCode.CREATED, responseDTO)); + } + + + @GetMapping("/{userId}") + public ResponseEntity> getUser( + @PathVariable Long userId + ) { + User user = userService.getUser(userId); + + UserResponseDto.Detail dto = userConverter.toDetail(user); + + return ResponseEntity + .status(GeneralSuccessCode.OK.getStatus()) + .body(ApiResponse.onSuccess(GeneralSuccessCode.OK, dto)); + } + + + @GetMapping + public ResponseEntity>> getUsers() { + List users = userService.getUsers(); + + List dtos = userConverter.toSummaryList(users); + + return ResponseEntity + .status(GeneralSuccessCode.OK.getStatus()) + .body(ApiResponse.onSuccess(GeneralSuccessCode.OK, dtos)); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Store.java b/src/main/java/com/example/spring_boot_a/domain/entity/Store.java index 22eaeaa..92efb1d 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Store.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/Store.java @@ -1,5 +1,8 @@ package com.example.spring_boot_a.domain.entity; +import com.example.spring_boot_a.domain.entity.etc.Location; +import com.example.spring_boot_a.domain.entity.mission.Mission; +import com.example.spring_boot_a.domain.entity.review.Review; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Term.java b/src/main/java/com/example/spring_boot_a/domain/entity/Term.java index 67b0531..fb0b259 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Term.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/Term.java @@ -1,6 +1,7 @@ package com.example.spring_boot_a.domain.entity; import com.example.spring_boot_a.domain.entity.enums.TermType; +import com.example.spring_boot_a.domain.entity.user.UserTerm; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/UserMission.java b/src/main/java/com/example/spring_boot_a/domain/entity/UserMission.java deleted file mode 100644 index a59f1a5..0000000 --- a/src/main/java/com/example/spring_boot_a/domain/entity/UserMission.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.spring_boot_a.domain.entity; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; - -@Entity @Getter @Setter -@Table(uniqueConstraints = @UniqueConstraint(name = "uq_user_mission", columnNames = {"user_id","mission_id"})) -public class UserMission { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long userMissionId; - - @Column(nullable = false) - private Boolean isComplete = false; - - @ManyToOne(optional = false) @JoinColumn(name = "mission_id") - private Mission mission; - - @ManyToOne(optional = false) @JoinColumn(name = "user_id") - private User user; -} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/enums/UserMissionStatus.java b/src/main/java/com/example/spring_boot_a/domain/entity/enums/UserMissionStatus.java new file mode 100644 index 0000000..3e33d28 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/enums/UserMissionStatus.java @@ -0,0 +1,5 @@ +package com.example.spring_boot_a.domain.entity.enums; + +public enum UserMissionStatus { + IN_PROGRESS,COMPLETE,CANCELLED +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Food.java b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Food.java similarity index 82% rename from src/main/java/com/example/spring_boot_a/domain/entity/Food.java rename to src/main/java/com/example/spring_boot_a/domain/entity/etc/Food.java index 2d131bf..5d87461 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Food.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Food.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.etc; +import com.example.spring_boot_a.domain.entity.user.UserFood; import com.example.spring_boot_a.domain.entity.enums.FoodType; import jakarta.persistence.*; import lombok.Getter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Location.java b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Location.java similarity index 82% rename from src/main/java/com/example/spring_boot_a/domain/entity/Location.java rename to src/main/java/com/example/spring_boot_a/domain/entity/etc/Location.java index 115951e..3b14f94 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Location.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Location.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.etc; +import com.example.spring_boot_a.domain.entity.Store; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Reply.java b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Reply.java similarity index 76% rename from src/main/java/com/example/spring_boot_a/domain/entity/Reply.java rename to src/main/java/com/example/spring_boot_a/domain/entity/etc/Reply.java index cbed38e..293b19f 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Reply.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/etc/Reply.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.etc; +import com.example.spring_boot_a.domain.entity.review.Review; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/ReviewPhoto.java b/src/main/java/com/example/spring_boot_a/domain/entity/etc/ReviewPhoto.java similarity index 77% rename from src/main/java/com/example/spring_boot_a/domain/entity/ReviewPhoto.java rename to src/main/java/com/example/spring_boot_a/domain/entity/etc/ReviewPhoto.java index 41a9357..7b2ab0a 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/ReviewPhoto.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/etc/ReviewPhoto.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.etc; +import com.example.spring_boot_a.domain.entity.review.Review; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Mission.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/Mission.java similarity index 79% rename from src/main/java/com/example/spring_boot_a/domain/entity/Mission.java rename to src/main/java/com/example/spring_boot_a/domain/entity/mission/Mission.java index ac50cea..e79fcc6 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Mission.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/Mission.java @@ -1,5 +1,7 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.mission; +import com.example.spring_boot_a.domain.entity.Store; +import com.example.spring_boot_a.domain.entity.user.UserMission; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -14,6 +16,9 @@ public class Mission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long missionId; + @Column + private String missionName; + @Column(nullable = false) private LocalDate deadline; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeRequest.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeRequest.java new file mode 100644 index 0000000..3a54443 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeRequest.java @@ -0,0 +1,5 @@ +package com.example.spring_boot_a.domain.entity.mission.dto; + +public record MissionChallengeRequest(Long userId) { + +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeResponse.java new file mode 100644 index 0000000..6a904e4 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MissionChallengeResponse.java @@ -0,0 +1,14 @@ +package com.example.spring_boot_a.domain.entity.mission.dto; + +import lombok.Builder; + +import java.time.LocalDate; + +@Builder +public record MissionChallengeResponse(Long userMissionId, + Long missionId, + Long storeId, + LocalDate deadline, + Integer point, + String status) { +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MyInProgressMissionResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MyInProgressMissionResponse.java new file mode 100644 index 0000000..11df2d2 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/MyInProgressMissionResponse.java @@ -0,0 +1,20 @@ +package com.example.spring_boot_a.domain.entity.mission.dto; + +import com.example.spring_boot_a.domain.entity.enums.UserMissionStatus; +import lombok.Builder; + +import java.time.Instant; +import java.time.LocalDate; + +@Builder +public record MyInProgressMissionResponse( + Long userMissionId, + Long missionId, + String storeName, + String missionName, + String conditional, + Integer point, + LocalDate deadline, + UserMissionStatus status, + Instant startedAt +) {} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionListResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionListResponse.java new file mode 100644 index 0000000..75b74e8 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionListResponse.java @@ -0,0 +1,14 @@ +package com.example.spring_boot_a.domain.entity.mission.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record StoreMissionListResponse( + int page, + int size, + long totalElements, + int totalPages, + List missions +) {} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionSummaryResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionSummaryResponse.java new file mode 100644 index 0000000..b68cb8c --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/StoreMissionSummaryResponse.java @@ -0,0 +1,17 @@ +package com.example.spring_boot_a.domain.entity.mission.dto; + + +import lombok.Builder; + +import java.time.LocalDate; + +@Builder +public record StoreMissionSummaryResponse( + Long missionId, + Long storeId, + String storeName, + String missionName, + String conditional, + Integer point, + LocalDate deadline +) {} \ No newline at end of file diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/MyInProgressMissionConverter.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/MyInProgressMissionConverter.java new file mode 100644 index 0000000..7252e7f --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/MyInProgressMissionConverter.java @@ -0,0 +1,33 @@ +package com.example.spring_boot_a.domain.entity.mission.dto.converter; + + +import com.example.spring_boot_a.domain.entity.mission.dto.MyInProgressMissionResponse; +import com.example.spring_boot_a.domain.entity.user.UserMission; +import lombok.experimental.UtilityClass; + +import java.util.List; +import java.util.stream.Collectors; + +@UtilityClass +public class MyInProgressMissionConverter { + + public MyInProgressMissionResponse from(UserMission um) { + return MyInProgressMissionResponse.builder() + .userMissionId(um.getUserMissionId()) + .missionId(um.getMission().getMissionId()) + .storeName(um.getMission().getStore().getName()) + .missionName(um.getMission().getMissionName()) + .conditional(um.getMission().getConditional()) + .point(um.getMission().getPoint()) + .deadline(um.getMission().getDeadline()) + .status(um.getStatus()) + .startedAt(um.getCreatedAt()) + .build(); + } + + public List fromList(List userMissions) { + return userMissions.stream() + .map(MyInProgressMissionConverter::from) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/StoreMissionConverter.java b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/StoreMissionConverter.java new file mode 100644 index 0000000..6706b53 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/mission/dto/converter/StoreMissionConverter.java @@ -0,0 +1,30 @@ +package com.example.spring_boot_a.domain.entity.mission.dto.converter; + +import com.example.spring_boot_a.domain.entity.mission.Mission; +import com.example.spring_boot_a.domain.entity.mission.dto.StoreMissionSummaryResponse; +import lombok.experimental.UtilityClass; + +import java.util.List; +import java.util.stream.Collectors; + +@UtilityClass +public class StoreMissionConverter { + + public StoreMissionSummaryResponse toSummary(Mission mission) { + return StoreMissionSummaryResponse.builder() + .missionId(mission.getMissionId()) + .storeId(mission.getStore().getStoreId()) + .storeName(mission.getStore().getName()) + .missionName(mission.getMissionName()) + .conditional(mission.getConditional()) + .point(mission.getPoint()) + .deadline(mission.getDeadline()) + .build(); + } + + public List toSummaryList(List missions) { + return missions.stream() + .map(StoreMissionConverter::toSummary) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/Review.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/Review.java similarity index 69% rename from src/main/java/com/example/spring_boot_a/domain/entity/Review.java rename to src/main/java/com/example/spring_boot_a/domain/entity/review/Review.java index 1fdc96d..af69e36 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/Review.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/Review.java @@ -1,5 +1,9 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.review; +import com.example.spring_boot_a.domain.entity.etc.ReviewPhoto; +import com.example.spring_boot_a.domain.entity.Store; +import com.example.spring_boot_a.domain.entity.etc.Reply; +import com.example.spring_boot_a.domain.entity.user.User; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -25,7 +29,8 @@ public class Review { @ManyToOne(optional = false) @JoinColumn(name = "store_id") private Store store; - @ManyToOne(optional = false) @JoinColumn(name = "user_id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") private User user; @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/src/main/java/com/example/spring_boot_a/api/review/dto/MyReviewItemDto.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewItemDto.java similarity index 81% rename from src/main/java/com/example/spring_boot_a/api/review/dto/MyReviewItemDto.java rename to src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewItemDto.java index 67d94db..17e9297 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/dto/MyReviewItemDto.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewItemDto.java @@ -1,4 +1,4 @@ -package com.example.spring_boot_a.api.review.dto; +package com.example.spring_boot_a.domain.entity.review.dto; import java.time.Instant; import java.util.List; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewResponse.java new file mode 100644 index 0000000..b377d68 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/MyReviewResponse.java @@ -0,0 +1,19 @@ +package com.example.spring_boot_a.domain.entity.review.dto; + + +import lombok.Builder; + +import java.time.Instant; +import java.util.List; + +@Builder +public record MyReviewResponse( + Long reviewId, + String storeName, + Double rating, + String content, + Instant createdAt, + List photoUrls, + String ownerReply, + Instant ownerReplyCreatedAt +) {} diff --git a/src/main/java/com/example/spring_boot_a/api/review/dto/ReviewCreateRequest.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewCreateRequest.java similarity index 78% rename from src/main/java/com/example/spring_boot_a/api/review/dto/ReviewCreateRequest.java rename to src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewCreateRequest.java index bb6a044..79bbf9c 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/dto/ReviewCreateRequest.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewCreateRequest.java @@ -1,4 +1,4 @@ -package com.example.spring_boot_a.api.review.dto; +package com.example.spring_boot_a.domain.entity.review.dto; import jakarta.validation.constraints.*; import java.util.List; diff --git a/src/main/java/com/example/spring_boot_a/api/review/dto/ReviewResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewResponse.java similarity index 81% rename from src/main/java/com/example/spring_boot_a/api/review/dto/ReviewResponse.java rename to src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewResponse.java index e82f2ff..4ef4e3d 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/dto/ReviewResponse.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/ReviewResponse.java @@ -1,4 +1,4 @@ -package com.example.spring_boot_a.api.review.dto; +package com.example.spring_boot_a.domain.entity.review.dto; import java.time.Instant; import java.util.List; diff --git a/src/main/java/com/example/spring_boot_a/api/review/dto/StarSummaryResponse.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/StarSummaryResponse.java similarity index 71% rename from src/main/java/com/example/spring_boot_a/api/review/dto/StarSummaryResponse.java rename to src/main/java/com/example/spring_boot_a/domain/entity/review/dto/StarSummaryResponse.java index e587991..8ce6151 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/dto/StarSummaryResponse.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/StarSummaryResponse.java @@ -1,4 +1,4 @@ -package com.example.spring_boot_a.api.review.dto; +package com.example.spring_boot_a.domain.entity.review.dto; public record StarSummaryResponse( long s5, diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/MyReviewConverter.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/MyReviewConverter.java new file mode 100644 index 0000000..8883363 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/MyReviewConverter.java @@ -0,0 +1,39 @@ +package com.example.spring_boot_a.domain.entity.review.dto.converter; + +import com.example.spring_boot_a.domain.entity.review.Review; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewResponse; +import lombok.experimental.UtilityClass; + +import java.util.List; +import java.util.stream.Collectors; + +@UtilityClass +public class MyReviewConverter { + + public MyReviewResponse from(Review review) { + return MyReviewResponse.builder() + .reviewId(review.getReviewId()) + .storeName(review.getStore().getName()) + .rating(review.getRating()) + .content(review.getContent()) + .createdAt(review.getCreatedAt()) + .photoUrls( + review.getReviewPhotos().stream() + .map(photo -> photo.getImageUrl()) + .collect(Collectors.toList()) + ) + .ownerReply( + review.getReply() != null ? review.getReply().getContent() : null + ) + .ownerReplyCreatedAt( + review.getReply() != null ? review.getReply().getCreatedAt() : null + ) + .build(); + } + + public List fromList(List reviews) { + return reviews.stream() + .map(MyReviewConverter::from) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/ReviewResponseConverter.java b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/ReviewResponseConverter.java new file mode 100644 index 0000000..ca51700 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/review/dto/converter/ReviewResponseConverter.java @@ -0,0 +1,47 @@ +package com.example.spring_boot_a.domain.entity.review.dto.converter; + +import com.example.spring_boot_a.domain.entity.etc.Reply; +import com.example.spring_boot_a.domain.entity.etc.ReviewPhoto; +import com.example.spring_boot_a.domain.entity.review.Review; +import com.example.spring_boot_a.domain.entity.review.dto.ReviewResponse; +import lombok.experimental.UtilityClass; + +import java.util.List; +import java.util.stream.Collectors; + + +@UtilityClass +public class ReviewResponseConverter { + + public ReviewResponse toDto(Review review) { + return new ReviewResponse( + review.getReviewId(), + review.getUser().getName(), // 작성자 이름 + review.getStar(), // 별점 Float 그대로 + review.getContent(), // 리뷰 내용 + review.getCreatedAt(), // Instant 타입 + extractPhotos(review), // 사진 리스트 + extractReplies(review) // 댓글 리스트 + ); + } + + private List extractPhotos(Review review) { + return review.getPhotos() == null ? List.of() + : review.getPhotos().stream() + .map(ReviewPhoto::getPhotoUrl) // 사진 URL 문자열 반환 + .collect(Collectors.toList()); + } + + private List extractReplies(Review review) { + return review.getReplies() == null ? List.of() + : review.getReplies().stream() + .map(Reply::getContent) // 댓글 내용 문자열 반환 + .collect(Collectors.toList()); + } + + public List toDtoList(List reviews) { + return reviews.stream() + .map(ReviewResponseConverter::toDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/User.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/User.java similarity index 95% rename from src/main/java/com/example/spring_boot_a/domain/entity/User.java rename to src/main/java/com/example/spring_boot_a/domain/entity/user/User.java index dbdfce4..dca91ba 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/User.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/User.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.user; +import com.example.spring_boot_a.domain.entity.review.Review; import com.example.spring_boot_a.domain.entity.enums.AddressGu; import com.example.spring_boot_a.domain.entity.enums.Gender; import com.example.spring_boot_a.domain.entity.enums.SocialLoginType; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/UserFood.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserFood.java similarity index 81% rename from src/main/java/com/example/spring_boot_a/domain/entity/UserFood.java rename to src/main/java/com/example/spring_boot_a/domain/entity/user/UserFood.java index 2769156..c25ba2f 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/UserFood.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserFood.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.user; +import com.example.spring_boot_a.domain.entity.etc.Food; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/UserMission.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserMission.java new file mode 100644 index 0000000..7506f9b --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserMission.java @@ -0,0 +1,49 @@ +package com.example.spring_boot_a.domain.entity.user; + +import com.example.spring_boot_a.domain.entity.enums.UserMissionStatus; +import com.example.spring_boot_a.domain.entity.mission.Mission; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Instant; + +@Entity +@Getter @Setter +@NoArgsConstructor +public class UserMission { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long userMissionId; + + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(optional = false) + @JoinColumn(name = "mission_id") + private Mission mission; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private UserMissionStatus status; + + @Column(nullable = false) + private Instant createdAt = Instant.now(); + + private Instant completedAt; + + public static UserMission start(User user, Mission mission) { + UserMission um = new UserMission(); + um.user = user; + um.mission = mission; + um.status = UserMissionStatus.IN_PROGRESS; + return um; + } + + public void complete() { + this.status = UserMissionStatus.COMPLETE; + this.completedAt = Instant.now(); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/UserTerm.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserTerm.java similarity index 82% rename from src/main/java/com/example/spring_boot_a/domain/entity/UserTerm.java rename to src/main/java/com/example/spring_boot_a/domain/entity/user/UserTerm.java index 5872f66..7eb2fb8 100644 --- a/src/main/java/com/example/spring_boot_a/domain/entity/UserTerm.java +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/UserTerm.java @@ -1,5 +1,6 @@ -package com.example.spring_boot_a.domain.entity; +package com.example.spring_boot_a.domain.entity.user; +import com.example.spring_boot_a.domain.entity.Term; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/converter/UserConverter.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/converter/UserConverter.java new file mode 100644 index 0000000..e4668e4 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/converter/UserConverter.java @@ -0,0 +1,65 @@ +package com.example.spring_boot_a.domain.entity.user.converter; + +import com.example.spring_boot_a.domain.entity.user.dto.UserRequestDto; +import com.example.spring_boot_a.domain.entity.user.dto.UserResponseDto; +import com.example.spring_boot_a.domain.entity.user.User; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.List; + +@Component +public class UserConverter { + + public User toUser(UserRequestDto.Create request) { + User user = new User(); + user.setName(request.getName()); + user.setGender(request.getGender()); + user.setBirth(request.getBirth()); + user.setAddress(request.getAddress()); + user.setEmail(request.getEmail()); + user.setPhoneNumber(request.getPhoneNumber()); + user.setSocialType(request.getSocialType()); + user.setPoint(0); + user.setUpdatedAt(Instant.now()); + return user; + } + + public UserResponseDto.CreateResult toCreateResult(User user) { + return UserResponseDto.CreateResult.builder() + .userId(user.getUserId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public UserResponseDto.Detail toDetail(User user) { + return UserResponseDto.Detail.builder() + .userId(user.getUserId()) + .name(user.getName()) + .gender(user.getGender()) + .birth(user.getBirth()) + .address(user.getAddress()) + .email(user.getEmail()) + .phoneNumber(user.getPhoneNumber()) + .point(user.getPoint()) + .socialType(user.getSocialType()) + .updatedAt(user.getUpdatedAt()) + .build(); + } + + public UserResponseDto.Summary toSummary(User user) { + return UserResponseDto.Summary.builder() + .userId(user.getUserId()) + .name(user.getName()) + .address(user.getAddress()) + .point(user.getPoint()) + .build(); + } + + public List toSummaryList(List users) { + return users.stream() + .map(this::toSummary) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserRequestDto.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserRequestDto.java new file mode 100644 index 0000000..e364bd2 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserRequestDto.java @@ -0,0 +1,43 @@ +package com.example.spring_boot_a.domain.entity.user.dto; + +import com.example.spring_boot_a.domain.entity.enums.AddressGu; +import com.example.spring_boot_a.domain.entity.enums.Gender; +import com.example.spring_boot_a.domain.entity.enums.SocialLoginType; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +public class UserRequestDto { + + @Getter + @NoArgsConstructor + public static class Create { + + @NotBlank(message = "이름은 필수값입니다.") + private String name; + + @NotNull(message = "성별은 필수값입니다.") + private Gender gender; + + private LocalDate birth; + + @NotNull(message = "주소(구)는 필수값입니다.") + private AddressGu address; + + @NotBlank(message = "이메일은 필수값입니다.") + @Email(message = "이메일 형식이 올바르지 않습니다.") + private String email; + + @NotBlank(message = "전화번호는 필수값입니다.") + @Size(max = 20, message = "전화번호는 최대 20자입니다.") + private String phoneNumber; + + @NotNull(message = "소셜 타입은 필수값입니다.") + private SocialLoginType socialType; + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserResponseDto.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserResponseDto.java new file mode 100644 index 0000000..bf30f61 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/dto/UserResponseDto.java @@ -0,0 +1,45 @@ +package com.example.spring_boot_a.domain.entity.user.dto; + +import com.example.spring_boot_a.domain.entity.enums.AddressGu; +import com.example.spring_boot_a.domain.entity.enums.Gender; +import com.example.spring_boot_a.domain.entity.enums.SocialLoginType; +import lombok.Builder; +import lombok.Getter; + +import java.time.Instant; +import java.time.LocalDate; + +public class UserResponseDto { + + @Getter + @Builder + public static class CreateResult { + private Long userId; + private String name; + private String email; + } + + @Getter + @Builder + public static class Detail { + private Long userId; + private String name; + private Gender gender; + private LocalDate birth; + private AddressGu address; + private String email; + private String phoneNumber; + private Integer point; + private SocialLoginType socialType; + private Instant updatedAt; + } + + @Getter + @Builder + public static class Summary { + private Long userId; + private String name; + private AddressGu address; + private Integer point; + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserErrorCode.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserErrorCode.java new file mode 100644 index 0000000..54e6f8f --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserErrorCode.java @@ -0,0 +1,17 @@ +package com.example.spring_boot_a.domain.entity.user.exception; + +import com.example.spring_boot_a.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum UserErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND,"MEMBER404_1","해당 사용자를 찾지 못했습니다."); + + private HttpStatus status; + private String code; + private String message; +} diff --git a/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserException.java b/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserException.java new file mode 100644 index 0000000..61156a1 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/domain/entity/user/exception/UserException.java @@ -0,0 +1,10 @@ +package com.example.spring_boot_a.domain.entity.user.exception; + +import com.example.spring_boot_a.global.apiPayload.code.BaseErrorCode; +import com.example.spring_boot_a.global.exception.CustomException; + +public class UserException extends CustomException { + public UserException(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/UserMissionRepository.java b/src/main/java/com/example/spring_boot_a/domain/repository/UserMissionRepository.java deleted file mode 100644 index 73b7b6b..0000000 --- a/src/main/java/com/example/spring_boot_a/domain/repository/UserMissionRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.spring_boot_a.domain.repository; - -import com.example.spring_boot_a.domain.entity.UserMission; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -public interface UserMissionRepository extends JpaRepository { - @Query(""" - select um from UserMission um - where um.user.userId = :userId - and um.isComplete = true - and not exists ( - select 1 from Review r - where r.user.userId = um.user.userId - and r.store.storeId = um.mission.store.storeId - ) - order by um.userMissionId desc - """) - List findCompletesWithoutReview(@Param("userId") Long userId); -} diff --git a/src/main/java/com/example/spring_boot_a/global/apiPayload/code/ApiResponse.java b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/ApiResponse.java new file mode 100644 index 0000000..e0767ed --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/ApiResponse.java @@ -0,0 +1,62 @@ +package com.example.spring_boot_a.global.apiPayload.code; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + + public static ApiResponse onSuccess(BaseSuccessCode code) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), null); + } + + public static ApiResponse onSuccess(T result) { + return new ApiResponse<>(true, + GeneralSuccessCode.OK.getCode(), + GeneralSuccessCode.OK.getMessage(), + result + ); + } + + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } + + + public static ApiResponse onFailure(BaseErrorCode code) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), null); + } + + @Builder + public record PageResponse( + List content, + int page, + int size, + long totalElements, + int totalPages + ){} +} diff --git a/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseErrorCode.java b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..42ad0f1 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,10 @@ +package com.example.spring_boot_a.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseSuccessCode.java b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseSuccessCode.java new file mode 100644 index 0000000..8c0bc5c --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/BaseSuccessCode.java @@ -0,0 +1,10 @@ +package com.example.spring_boot_a.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralErrorCode.java b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralErrorCode.java new file mode 100644 index 0000000..4ce41a5 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralErrorCode.java @@ -0,0 +1,31 @@ +package com.example.spring_boot_a.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode { + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "AUTH401_1", + "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, + "AUTH403_1", + "요청이 거부되었습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "요청한 리소스를 찾을 수 없습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1" + ,"서버 에러입니다.") + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralSuccessCode.java b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralSuccessCode.java new file mode 100644 index 0000000..0116425 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/apiPayload/code/GeneralSuccessCode.java @@ -0,0 +1,24 @@ +package com.example.spring_boot_a.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode{ + + OK(HttpStatus.OK, + "COMMON200_1", + "요청이 성공적으로 처리되었습니다."), + CREATED(HttpStatus.CREATED, + "COMMON201_1", + "리소스가 성공적으로 생성되었습니다."), + NO_CONTENT(HttpStatus.NO_CONTENT, + "COMMON204_1", + "성공했지만 반환할 데이터는 없습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/spring_boot_a/global/exception/CustomException.java b/src/main/java/com/example/spring_boot_a/global/exception/CustomException.java new file mode 100644 index 0000000..fd865b3 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/exception/CustomException.java @@ -0,0 +1,15 @@ +package com.example.spring_boot_a.global.exception; + +import com.example.spring_boot_a.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException{ + + private final BaseErrorCode errorCode; + + public CustomException(BaseErrorCode errorCode){ + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/example/spring_boot_a/global/exception/GlobalExceptionHandler.java b/src/main/java/com/example/spring_boot_a/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..71d28ff --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,84 @@ +package com.example.spring_boot_a.global.exception; + +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.global.apiPayload.code.BaseErrorCode; +import com.example.spring_boot_a.global.apiPayload.code.GeneralErrorCode; +import jakarta.persistence.EntityNotFoundException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + + @ExceptionHandler(CustomException.class) + public ResponseEntity> handleCustomException(CustomException e) { + BaseErrorCode errorCode = e.getErrorCode(); + + ApiResponse body = ApiResponse.onFailure(errorCode); + + return ResponseEntity + .status(errorCode.getStatus()) + .body(body); + } + + + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity> handleEntityNotFound(EntityNotFoundException e) { + + ApiResponse body = ApiResponse.onFailure( + GeneralErrorCode.NOT_FOUND, + e.getMessage() // result에 상세 메시지를 실어 줄 수 있음 + ); + + return ResponseEntity + .status(GeneralErrorCode.NOT_FOUND.getStatus()) + .body(body); + } + + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidation(MethodArgumentNotValidException e) { + String msg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + + ApiResponse body = ApiResponse.onFailure( + GeneralErrorCode.BAD_REQUEST, + msg // result에 검증 실패 메시지 + ); + + return ResponseEntity + .status(GeneralErrorCode.BAD_REQUEST.getStatus()) + .body(body); + } + + + @ExceptionHandler(DataIntegrityViolationException.class) + public ResponseEntity> handleIntegrity(DataIntegrityViolationException e) { + + ApiResponse body = ApiResponse.onFailure( + GeneralErrorCode.BAD_REQUEST, + "데이터 제약 조건 위반(중복 등)" + ); + + return ResponseEntity + .status(GeneralErrorCode.BAD_REQUEST.getStatus()) + .body(body); + } + + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception e) { + + ApiResponse body = ApiResponse.onFailure( + GeneralErrorCode.INTERNAL_SERVER_ERROR, + e.getMessage() + ); + + return ResponseEntity + .status(GeneralErrorCode.INTERNAL_SERVER_ERROR.getStatus()) + .body(body); + } +} diff --git a/src/main/java/com/example/spring_boot_a/global/paging/ValidPage.java b/src/main/java/com/example/spring_boot_a/global/paging/ValidPage.java new file mode 100644 index 0000000..58a3bb8 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/paging/ValidPage.java @@ -0,0 +1,19 @@ +package com.example.spring_boot_a.global.paging; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = ValidPageValidator.class) +public @interface ValidPage { + + String message() default "page 값은 1 이상의 정수여야 합니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/example/spring_boot_a/global/paging/ValidPageValidator.java b/src/main/java/com/example/spring_boot_a/global/paging/ValidPageValidator.java new file mode 100644 index 0000000..7839b31 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/global/paging/ValidPageValidator.java @@ -0,0 +1,21 @@ +package com.example.spring_boot_a.global.paging; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class ValidPageValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null) return false; + + // 숫자인지 확인 + if (!value.matches("^[0-9]+$")) { + return false; + } + + int page = Integer.parseInt(value); + // 1 이상만 허용 (0, 음수는 에러) + return page >= 1; + } +} diff --git a/src/main/java/com/example/spring_boot_a/repository/MissionRepository.java b/src/main/java/com/example/spring_boot_a/repository/MissionRepository.java new file mode 100644 index 0000000..5c33952 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/repository/MissionRepository.java @@ -0,0 +1,11 @@ +package com.example.spring_boot_a.repository; + +import com.example.spring_boot_a.domain.entity.mission.Mission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MissionRepository extends JpaRepository { + + Page findByStore_StoreId(Long storeId, Pageable pageable); +} diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/ReplyRepository.java b/src/main/java/com/example/spring_boot_a/repository/ReplyRepository.java similarity index 70% rename from src/main/java/com/example/spring_boot_a/domain/repository/ReplyRepository.java rename to src/main/java/com/example/spring_boot_a/repository/ReplyRepository.java index 839e751..1649a2a 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/ReplyRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/ReplyRepository.java @@ -1,6 +1,6 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.domain.entity.Reply; +import com.example.spring_boot_a.domain.entity.etc.Reply; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Collection; diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewPhotoRepository.java b/src/main/java/com/example/spring_boot_a/repository/ReviewPhotoRepository.java similarity index 70% rename from src/main/java/com/example/spring_boot_a/domain/repository/ReviewPhotoRepository.java rename to src/main/java/com/example/spring_boot_a/repository/ReviewPhotoRepository.java index 269afb0..b6cd176 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewPhotoRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/ReviewPhotoRepository.java @@ -1,6 +1,6 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.domain.entity.ReviewPhoto; +import com.example.spring_boot_a.domain.entity.etc.ReviewPhoto; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Collection; diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepository.java b/src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepository.java similarity index 71% rename from src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepository.java rename to src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepository.java index 1e0975e..6d8a710 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepository.java @@ -1,6 +1,6 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.api.review.dto.MyReviewItemDto; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewItemDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepositoryImpl.java b/src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepositoryImpl.java similarity index 93% rename from src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepositoryImpl.java rename to src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepositoryImpl.java index a38e806..bff7c7d 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewQueryRepositoryImpl.java +++ b/src/main/java/com/example/spring_boot_a/repository/ReviewQueryRepositoryImpl.java @@ -1,9 +1,9 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.api.review.dto.MyReviewItemDto; -import com.example.spring_boot_a.domain.entity.QReply; -import com.example.spring_boot_a.domain.entity.QReview; -import com.example.spring_boot_a.domain.entity.QReviewPhoto; +import com.example.spring_boot_a.domain.entity.etc.QReply; +import com.example.spring_boot_a.domain.entity.etc.QReviewPhoto; +import com.example.spring_boot_a.domain.entity.review.QReview; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewItemDto; import com.example.spring_boot_a.domain.entity.QStore; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewRepository.java b/src/main/java/com/example/spring_boot_a/repository/ReviewRepository.java similarity index 88% rename from src/main/java/com/example/spring_boot_a/domain/repository/ReviewRepository.java rename to src/main/java/com/example/spring_boot_a/repository/ReviewRepository.java index df6a600..f576c69 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/ReviewRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/ReviewRepository.java @@ -1,12 +1,14 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.domain.entity.Review; +import com.example.spring_boot_a.domain.entity.review.Review; import org.springframework.data.domain.*; import org.springframework.data.jpa.repository.*; import org.springframework.data.repository.query.Param; public interface ReviewRepository extends JpaRepository { + Page findByUser_UserId(Long userId, Pageable pageable); + Page findByStore_StoreIdOrderByCreatedAtDesc(Long storeId, Pageable pageable); diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/StoreRepository.java b/src/main/java/com/example/spring_boot_a/repository/StoreRepository.java similarity index 78% rename from src/main/java/com/example/spring_boot_a/domain/repository/StoreRepository.java rename to src/main/java/com/example/spring_boot_a/repository/StoreRepository.java index 324bb86..5debcd8 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/StoreRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/StoreRepository.java @@ -1,4 +1,4 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; import com.example.spring_boot_a.domain.entity.Store; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/spring_boot_a/repository/UserMissionRepository.java b/src/main/java/com/example/spring_boot_a/repository/UserMissionRepository.java new file mode 100644 index 0000000..0350c8c --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/repository/UserMissionRepository.java @@ -0,0 +1,17 @@ +package com.example.spring_boot_a.repository; + +import com.example.spring_boot_a.domain.entity.enums.UserMissionStatus; +import com.example.spring_boot_a.domain.entity.user.UserMission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface UserMissionRepository extends JpaRepository { + Page findByUser_UserIdAndStatus(Long userId, + UserMissionStatus status, + Pageable pageable); +} diff --git a/src/main/java/com/example/spring_boot_a/domain/repository/UserRepository.java b/src/main/java/com/example/spring_boot_a/repository/UserRepository.java similarity index 55% rename from src/main/java/com/example/spring_boot_a/domain/repository/UserRepository.java rename to src/main/java/com/example/spring_boot_a/repository/UserRepository.java index 83328ca..acfc985 100644 --- a/src/main/java/com/example/spring_boot_a/domain/repository/UserRepository.java +++ b/src/main/java/com/example/spring_boot_a/repository/UserRepository.java @@ -1,6 +1,6 @@ -package com.example.spring_boot_a.domain.repository; +package com.example.spring_boot_a.repository; -import com.example.spring_boot_a.domain.entity.User; +import com.example.spring_boot_a.domain.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { diff --git a/src/main/java/com/example/spring_boot_a/service/MissionChallengeService.java b/src/main/java/com/example/spring_boot_a/service/MissionChallengeService.java new file mode 100644 index 0000000..17ed0af --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/service/MissionChallengeService.java @@ -0,0 +1,81 @@ +package com.example.spring_boot_a.service; + +import com.example.spring_boot_a.domain.entity.Store; +import com.example.spring_boot_a.domain.entity.mission.Mission; +import com.example.spring_boot_a.domain.entity.mission.dto.StoreMissionListResponse; +import com.example.spring_boot_a.domain.entity.mission.dto.converter.StoreMissionConverter; +import com.example.spring_boot_a.domain.entity.user.User; +import com.example.spring_boot_a.domain.entity.user.UserMission; + +import com.example.spring_boot_a.repository.MissionRepository; +import com.example.spring_boot_a.repository.StoreRepository; +import com.example.spring_boot_a.repository.UserMissionRepository; +import com.example.spring_boot_a.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + + +@Service +@RequiredArgsConstructor +public class MissionChallengeService { + + private final StoreRepository storeRepository; + private final MissionRepository missionRepository; + private final UserRepository userRepository; + private final UserMissionRepository userMissionRepository; + + private static final int PAGE_SIZE = 10; + + @Transactional + public UserMission challengeMission(Long storeId, Long missionId, Long userId) { + + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 유저입니다. userId=" + userId)); + + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 가게입니다. storeId=" + storeId)); + + Mission mission = missionRepository.findById(missionId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 미션입니다. missionId=" + missionId)); + + if (!mission.getStore().getStoreId().equals(store.getStoreId())) { + throw new IllegalArgumentException("해당 가게의 미션이 아닙니다."); + } + + if (mission.getDeadline().isBefore(LocalDate.now())) { + throw new IllegalStateException("이미 마감된 미션입니다."); + } + + UserMission userMission = UserMission.start(user, mission); + return userMissionRepository.save(userMission); + } + + @Transactional(readOnly = true) + public StoreMissionListResponse getStoreMissions(Long storeId, int pageIndex) { + + // 가게 존재 여부 체크 (없으면 400) + storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 가게입니다. storeId=" + storeId)); + + var pageable = PageRequest.of( + pageIndex, + PAGE_SIZE, + Sort.by(Sort.Direction.DESC, "createdAt") + ); + + var missionPage = missionRepository.findByStore_StoreId(storeId, pageable); + + return StoreMissionListResponse.builder() + .page(pageIndex + 1) // 다시 1-base 로 + .size(PAGE_SIZE) + .totalElements(missionPage.getTotalElements()) + .totalPages(missionPage.getTotalPages()) + .missions(StoreMissionConverter.toSummaryList(missionPage.getContent())) + .build(); + } +} diff --git a/src/main/java/com/example/spring_boot_a/service/MyInProgressMissionService.java b/src/main/java/com/example/spring_boot_a/service/MyInProgressMissionService.java new file mode 100644 index 0000000..8d874f6 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/service/MyInProgressMissionService.java @@ -0,0 +1,40 @@ +package com.example.spring_boot_a.service; + +import com.example.spring_boot_a.domain.entity.enums.UserMissionStatus; +import com.example.spring_boot_a.domain.entity.mission.dto.MyInProgressMissionResponse; +import com.example.spring_boot_a.domain.entity.mission.dto.converter.MyInProgressMissionConverter; +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.repository.UserMissionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MyInProgressMissionService { + + private static final int PAGE_SIZE = 10; + + private final UserMissionRepository userMissionRepository; + + public ApiResponse.PageResponse getMyInProgressMissions(Long userId, int pageIndex) { + + var pageable = PageRequest.of( + pageIndex, + PAGE_SIZE, + Sort.by(Sort.Direction.DESC, "createdAt") + ); + + var page = userMissionRepository + .findByUser_UserIdAndStatus(userId, UserMissionStatus.IN_PROGRESS, pageable); + + return ApiResponse.PageResponse.builder() + .content(MyInProgressMissionConverter.fromList(page.getContent())) + .page(pageIndex + 1) + .size(PAGE_SIZE) + .totalElements(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .build(); + } +} diff --git a/src/main/java/com/example/spring_boot_a/domain/service/MyReviewQueryService.java b/src/main/java/com/example/spring_boot_a/service/MyReviewQueryService.java similarity index 75% rename from src/main/java/com/example/spring_boot_a/domain/service/MyReviewQueryService.java rename to src/main/java/com/example/spring_boot_a/service/MyReviewQueryService.java index b1d5aa0..7e9a6a8 100644 --- a/src/main/java/com/example/spring_boot_a/domain/service/MyReviewQueryService.java +++ b/src/main/java/com/example/spring_boot_a/service/MyReviewQueryService.java @@ -1,7 +1,7 @@ -package com.example.spring_boot_a.domain.service; +package com.example.spring_boot_a.service; -import com.example.spring_boot_a.api.review.dto.MyReviewItemDto; -import com.example.spring_boot_a.domain.repository.ReviewQueryRepository; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewItemDto; +import com.example.spring_boot_a.repository.ReviewQueryRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.*; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/example/spring_boot_a/api/review/ReviewService.java b/src/main/java/com/example/spring_boot_a/service/ReviewService.java similarity index 75% rename from src/main/java/com/example/spring_boot_a/api/review/ReviewService.java rename to src/main/java/com/example/spring_boot_a/service/ReviewService.java index fb4cb04..49e1188 100644 --- a/src/main/java/com/example/spring_boot_a/api/review/ReviewService.java +++ b/src/main/java/com/example/spring_boot_a/service/ReviewService.java @@ -1,8 +1,17 @@ -package com.example.spring_boot_a.api.review; +package com.example.spring_boot_a.service; -import com.example.spring_boot_a.api.review.dto.*; import com.example.spring_boot_a.domain.entity.*; -import com.example.spring_boot_a.domain.repository.*; +import com.example.spring_boot_a.domain.entity.etc.Reply; +import com.example.spring_boot_a.domain.entity.etc.ReviewPhoto; +import com.example.spring_boot_a.domain.entity.review.Review; +import com.example.spring_boot_a.domain.entity.review.dto.MyReviewResponse; +import com.example.spring_boot_a.domain.entity.review.dto.ReviewCreateRequest; +import com.example.spring_boot_a.domain.entity.review.dto.ReviewResponse; +import com.example.spring_boot_a.domain.entity.review.dto.StarSummaryResponse; +import com.example.spring_boot_a.domain.entity.review.dto.converter.MyReviewConverter; +import com.example.spring_boot_a.domain.entity.user.User; +import com.example.spring_boot_a.global.apiPayload.code.ApiResponse; +import com.example.spring_boot_a.repository.*; import jakarta.persistence.EntityNotFoundException; import org.springframework.data.domain.*; import org.springframework.stereotype.Service; @@ -14,6 +23,9 @@ @Service @Transactional(readOnly = true) public class ReviewService { + + private static final int PAGE_SIZE = 10; + private final ReviewRepository reviewRepository; private final ReviewPhotoRepository photoRepository; private final ReplyRepository replyRepository; @@ -40,7 +52,6 @@ public Long create(Long userId, Long storeId, ReviewCreateRequest req) { .orElseThrow(() -> new EntityNotFoundException("store not found")); Review r = new Review(); - r.setUser(user); r.setStore(store); r.setStar(req.star()); r.setContent(req.content()); @@ -58,6 +69,25 @@ public Long create(Long userId, Long storeId, ReviewCreateRequest req) { return saved.getReviewId(); } + public ApiResponse.PageResponse getMyReviews(Long userId, int pageIndex) { + + var pageable = PageRequest.of( + pageIndex, + PAGE_SIZE, + Sort.by(Sort.Direction.DESC, "createdAt") + ); + + var reviewPage = reviewRepository.findByUser_UserId(userId, pageable); + + return ApiResponse.PageResponse.builder() + .content(MyReviewConverter.fromList(reviewPage.getContent())) + .page(pageIndex + 1) + .size(PAGE_SIZE) + .totalElements(reviewPage.getTotalElements()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + public Page getStoreReviews(Long storeId, Integer starBucket, Pageable pageable) { Page page; if (starBucket == null) { diff --git a/src/main/java/com/example/spring_boot_a/service/UserService.java b/src/main/java/com/example/spring_boot_a/service/UserService.java new file mode 100644 index 0000000..95e2342 --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/service/UserService.java @@ -0,0 +1,14 @@ +package com.example.spring_boot_a.service; + +import com.example.spring_boot_a.domain.entity.user.User; + +import java.util.List; + +public interface UserService { + + User createUser(User user); + + User getUser(Long userId); + + List getUsers(); +} diff --git a/src/main/java/com/example/spring_boot_a/service/UserServiceImpl.java b/src/main/java/com/example/spring_boot_a/service/UserServiceImpl.java new file mode 100644 index 0000000..ff0545c --- /dev/null +++ b/src/main/java/com/example/spring_boot_a/service/UserServiceImpl.java @@ -0,0 +1,39 @@ +package com.example.spring_boot_a.service; + +import com.example.spring_boot_a.domain.entity.user.User; +import com.example.spring_boot_a.repository.UserRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + + @Override + @Transactional + public User createUser(User user) { + return userRepository.save(user); + } + + + @Override + public User getUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> + new EntityNotFoundException("User not found. id=" + userId)); + } + + + @Override + public List getUsers() { + return userRepository.findAll(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 698e672..98ce774 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,8 @@ spring.application.name=Spring_Boot_A +spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_a?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 +spring.datasource.username=root +spring.datasource.password=z1227530! + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true