From 631e3727880c46329152cdcf803f73296bea2f7f Mon Sep 17 00:00:00 2001 From: DH CHOI Date: Wed, 25 Oct 2023 23:55:17 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feature:=20Profile=20ID=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/daybyquest/user/query/ProfileDao.java | 2 ++ .../user/query/ProfileDaoQuerydslImpl.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/daybyquest/user/query/ProfileDao.java b/src/main/java/daybyquest/user/query/ProfileDao.java index 00da6a8..22e4a40 100644 --- a/src/main/java/daybyquest/user/query/ProfileDao.java +++ b/src/main/java/daybyquest/user/query/ProfileDao.java @@ -7,6 +7,8 @@ public interface ProfileDao { Profile getByUsername(final Long userId, final String username); + Profile getById(final Long userId, final Long targetId); + Profile getMine(final Long userId); List findAllByUserIdIn(final Long userId, final Collection targetIds); diff --git a/src/main/java/daybyquest/user/query/ProfileDaoQuerydslImpl.java b/src/main/java/daybyquest/user/query/ProfileDaoQuerydslImpl.java index a3e9407..4bb2f4d 100644 --- a/src/main/java/daybyquest/user/query/ProfileDaoQuerydslImpl.java +++ b/src/main/java/daybyquest/user/query/ProfileDaoQuerydslImpl.java @@ -54,6 +54,19 @@ private ConstructorExpression profileProjection(final Long userId) { ); } + @Override + public Profile getById(final Long userId, final Long targetId) { + final Profile profile = factory + .select(profileProjection(userId)) + .from(user) + .where(user.id.eq(targetId)) + .fetchOne(); + if (profile == null) { + throw new NotExistUserException(); + } + return profile; + } + @Override public Profile getMine(final Long userId) { final Profile profile = factory From 425adbebbd062c52a2404966c20829824725a435 Mon Sep 17 00:00:00 2001 From: DH CHOI Date: Wed, 25 Oct 2023 23:55:43 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feature:=20Post=EC=97=90=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/NotExistPostException.java | 10 ++++++ .../java/daybyquest/post/domain/Post.java | 36 +++++++++++++++---- .../post/domain/PostRepository.java | 2 +- .../daybyquest/post/domain/PostState.java | 4 ++- .../java/daybyquest/post/domain/Posts.java | 22 ++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 src/main/java/daybyquest/global/error/exception/NotExistPostException.java create mode 100644 src/main/java/daybyquest/post/domain/Posts.java diff --git a/src/main/java/daybyquest/global/error/exception/NotExistPostException.java b/src/main/java/daybyquest/global/error/exception/NotExistPostException.java new file mode 100644 index 0000000..faaad67 --- /dev/null +++ b/src/main/java/daybyquest/global/error/exception/NotExistPostException.java @@ -0,0 +1,10 @@ +package daybyquest.global.error.exception; + +import daybyquest.global.error.ExceptionCode; + +public class NotExistPostException extends CustomException { + + public NotExistPostException() { + super(ExceptionCode.NOT_EXIST_POST); + } +} diff --git a/src/main/java/daybyquest/post/domain/Post.java b/src/main/java/daybyquest/post/domain/Post.java index 2f47c26..1aeea39 100644 --- a/src/main/java/daybyquest/post/domain/Post.java +++ b/src/main/java/daybyquest/post/domain/Post.java @@ -1,7 +1,11 @@ package daybyquest.post.domain; +import static daybyquest.post.domain.PostState.NOT_DECIDED; +import static daybyquest.post.domain.PostState.PREPARING; +import static daybyquest.post.domain.PostState.SUCCESS; import static jakarta.persistence.GenerationType.IDENTITY; +import daybyquest.global.error.exception.InvalidDomainException; import daybyquest.image.vo.Image; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; @@ -28,6 +32,8 @@ @EntityListeners(AuditingEntityListener.class) public class Post { + private static final int MAX_IMAGE_SIZE = 5; + @Id @GeneratedValue(strategy = IDENTITY) private Long id; @@ -35,6 +41,7 @@ public class Post { @Column(nullable = false) private Long userId; + @Column private Long questId; @LastModifiedDate @@ -51,15 +58,32 @@ public class Post { @OrderColumn(name = "order") private List images; - private boolean deleted; - - - public Post(Long userId, Long questId, String content, List images, boolean deleted) { + public Post(Long userId, Long questId, String content, List images) { this.userId = userId; this.questId = questId; this.content = content; this.images = images; - this.deleted = deleted; - this.state = PostState.NOT_DECIDED; + this.state = PREPARING; + validateImages(); + } + + private void validateImages() { + if (this.images.isEmpty() || this.images.size() > MAX_IMAGE_SIZE) { + throw new InvalidDomainException(); + } + } + + public void afterRequestDeciding() { + if (this.state != PREPARING || this.images == null) { + throw new InvalidDomainException(); + } + this.state = NOT_DECIDED; + } + + public void success() { + if (this.state != PREPARING && this.state != NOT_DECIDED) { + throw new InvalidDomainException(); + } + this.state = SUCCESS; } } diff --git a/src/main/java/daybyquest/post/domain/PostRepository.java b/src/main/java/daybyquest/post/domain/PostRepository.java index f1b79e7..ab44fb1 100644 --- a/src/main/java/daybyquest/post/domain/PostRepository.java +++ b/src/main/java/daybyquest/post/domain/PostRepository.java @@ -3,7 +3,7 @@ import java.util.Optional; import org.springframework.data.repository.Repository; -public interface PostRepository extends Repository { +interface PostRepository extends Repository { Post save(Post post); diff --git a/src/main/java/daybyquest/post/domain/PostState.java b/src/main/java/daybyquest/post/domain/PostState.java index b422da9..d8e6871 100644 --- a/src/main/java/daybyquest/post/domain/PostState.java +++ b/src/main/java/daybyquest/post/domain/PostState.java @@ -2,8 +2,10 @@ public enum PostState { + PREPARING, SUCCESS, NEED_CHECK, FAIL, - NOT_DECIDED; + NOT_DECIDED, + DELETED; } diff --git a/src/main/java/daybyquest/post/domain/Posts.java b/src/main/java/daybyquest/post/domain/Posts.java new file mode 100644 index 0000000..8131f41 --- /dev/null +++ b/src/main/java/daybyquest/post/domain/Posts.java @@ -0,0 +1,22 @@ +package daybyquest.post.domain; + +import daybyquest.global.error.exception.NotExistPostException; +import org.springframework.stereotype.Component; + +@Component +public class Posts { + + private final PostRepository postRepository; + + public Posts(final PostRepository postRepository) { + this.postRepository = postRepository; + } + + public Long save(final Post post) { + return postRepository.save(post).getId(); + } + + public Post getById(final Long id) { + return postRepository.findById(id).orElseThrow(NotExistPostException::new); + } +} From 331c2d5264029128f8b0592e63631d101131ea21 Mon Sep 17 00:00:00 2001 From: DH CHOI Date: Wed, 25 Oct 2023 23:56:07 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feature:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/application/SavePostService.java | 56 +++++++++++++++++++ .../post/dto/request/SavePostRequest.java | 13 +++++ .../post/presentation/PostCommandApi.java | 30 ++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/main/java/daybyquest/post/application/SavePostService.java create mode 100644 src/main/java/daybyquest/post/dto/request/SavePostRequest.java create mode 100644 src/main/java/daybyquest/post/presentation/PostCommandApi.java diff --git a/src/main/java/daybyquest/post/application/SavePostService.java b/src/main/java/daybyquest/post/application/SavePostService.java new file mode 100644 index 0000000..c6d4bad --- /dev/null +++ b/src/main/java/daybyquest/post/application/SavePostService.java @@ -0,0 +1,56 @@ +package daybyquest.post.application; + +import daybyquest.global.utils.MultipartFileUtils; +import daybyquest.image.vo.Image; +import daybyquest.image.vo.ImageIdentifierGenerator; +import daybyquest.image.vo.Images; +import daybyquest.post.domain.Post; +import daybyquest.post.domain.Posts; +import daybyquest.post.dto.request.SavePostRequest; +import daybyquest.user.domain.Users; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class SavePostService { + + private static final String CATEGORY = "POST"; + + private final Users users; + + private final Posts posts; + + private final Images images; + + private final ImageIdentifierGenerator generator; + + public SavePostService(final Users users, final Posts posts, final Images images, + final ImageIdentifierGenerator generator) { + this.users = users; + this.posts = posts; + this.images = images; + this.generator = generator; + } + + @Transactional + public Long invoke(final Long loginId, final SavePostRequest request, + final List files) { + users.validateExistentById(loginId); + final Post post = toEntity(loginId, request, toImageList(files)); + return posts.save(post); + } + + private List toImageList(final List files) { + return files.stream().map((file) -> { + final String identifier = generator.generateIdentifier(CATEGORY, file.getOriginalFilename()); + images.upload(identifier, MultipartFileUtils.getInputStream(file)); + return new Image(identifier); + }).toList(); + } + + private Post toEntity(final Long loginId, final SavePostRequest request, final List images) { + return new Post(loginId, request.getQuestId(), request.getContent(), images); + } +} diff --git a/src/main/java/daybyquest/post/dto/request/SavePostRequest.java b/src/main/java/daybyquest/post/dto/request/SavePostRequest.java new file mode 100644 index 0000000..d056755 --- /dev/null +++ b/src/main/java/daybyquest/post/dto/request/SavePostRequest.java @@ -0,0 +1,13 @@ +package daybyquest.post.dto.request; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class SavePostRequest { + + private String content; + + private Long questId; +} diff --git a/src/main/java/daybyquest/post/presentation/PostCommandApi.java b/src/main/java/daybyquest/post/presentation/PostCommandApi.java new file mode 100644 index 0000000..0d21bb5 --- /dev/null +++ b/src/main/java/daybyquest/post/presentation/PostCommandApi.java @@ -0,0 +1,30 @@ +package daybyquest.post.presentation; + +import daybyquest.auth.Authorization; +import daybyquest.auth.UserId; +import daybyquest.post.application.SavePostService; +import daybyquest.post.dto.request.SavePostRequest; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +public class PostCommandApi { + + private final SavePostService savePostService; + + public PostCommandApi(final SavePostService savePostService) { + this.savePostService = savePostService; + } + + @PostMapping("/post") + @Authorization + public ResponseEntity savePost(@UserId final Long loginId, @RequestPart SavePostRequest request, + @RequestPart List files) { + final Long postId = savePostService.invoke(loginId, request, files); + return ResponseEntity.ok(postId); + } +} From 1bdc179b7de3af4aa8fbe0cd30450e9cfa762074 Mon Sep 17 00:00:00 2001 From: DH CHOI Date: Wed, 25 Oct 2023 23:56:16 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feature:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EB=8B=A8=EA=B1=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/application/GetPostService.java | 29 +++++++++++ .../post/dto/response/PostResponse.java | 45 +++++++++++++++++ .../post/presentation/PostQueryApi.java | 27 +++++++++++ .../java/daybyquest/post/query/PostDao.java | 6 +++ .../post/query/PostDaoQuerydslImpl.java | 48 +++++++++++++++++++ .../java/daybyquest/post/query/PostData.java | 35 ++++++++++++++ 6 files changed, 190 insertions(+) create mode 100644 src/main/java/daybyquest/post/application/GetPostService.java create mode 100644 src/main/java/daybyquest/post/dto/response/PostResponse.java create mode 100644 src/main/java/daybyquest/post/presentation/PostQueryApi.java create mode 100644 src/main/java/daybyquest/post/query/PostDao.java create mode 100644 src/main/java/daybyquest/post/query/PostDaoQuerydslImpl.java create mode 100644 src/main/java/daybyquest/post/query/PostData.java diff --git a/src/main/java/daybyquest/post/application/GetPostService.java b/src/main/java/daybyquest/post/application/GetPostService.java new file mode 100644 index 0000000..a6654f3 --- /dev/null +++ b/src/main/java/daybyquest/post/application/GetPostService.java @@ -0,0 +1,29 @@ +package daybyquest.post.application; + +import daybyquest.post.dto.response.PostResponse; +import daybyquest.post.query.PostDao; +import daybyquest.post.query.PostData; +import daybyquest.user.query.Profile; +import daybyquest.user.query.ProfileDao; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class GetPostService { + + private final PostDao postDao; + + private final ProfileDao profileDao; + + public GetPostService(final PostDao postDao, final ProfileDao profileDao) { + this.postDao = postDao; + this.profileDao = profileDao; + } + + @Transactional(readOnly = true) + public PostResponse invoke(final Long loginId, final Long postId) { + final PostData postData = postDao.getByPostId(loginId, postId); + final Profile profile = profileDao.getById(loginId, postData.getUserId()); + return PostResponse.of(postData, profile); + } +} diff --git a/src/main/java/daybyquest/post/dto/response/PostResponse.java b/src/main/java/daybyquest/post/dto/response/PostResponse.java new file mode 100644 index 0000000..3eb0042 --- /dev/null +++ b/src/main/java/daybyquest/post/dto/response/PostResponse.java @@ -0,0 +1,45 @@ +package daybyquest.post.dto.response; + +import daybyquest.image.vo.Image; +import daybyquest.post.query.PostData; +import daybyquest.user.dto.response.ProfileResponse; +import daybyquest.user.query.Profile; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PostResponse { + + private ProfileResponse author; + + private Long id; + + private String content; + + private LocalDateTime updatedAt; + + private boolean liked; + + private List images; + + public PostResponse(final ProfileResponse author, final Long id, final String content, + final LocalDateTime updatedAt, final boolean liked, + final List images) { + this.author = author; + this.id = id; + this.content = content; + this.updatedAt = updatedAt; + this.liked = liked; + this.images = images; + } + + public static PostResponse of(final PostData postData, final Profile profile) { + return new PostResponse(ProfileResponse.of(profile), postData.getId(), postData.getContent(), + postData.getUpdatedAt(), postData.isLiked(), + postData.getImages().stream().map(Image::getImageIdentifier).toList() + ); + } +} diff --git a/src/main/java/daybyquest/post/presentation/PostQueryApi.java b/src/main/java/daybyquest/post/presentation/PostQueryApi.java new file mode 100644 index 0000000..7b8e203 --- /dev/null +++ b/src/main/java/daybyquest/post/presentation/PostQueryApi.java @@ -0,0 +1,27 @@ +package daybyquest.post.presentation; + +import daybyquest.auth.Authorization; +import daybyquest.auth.UserId; +import daybyquest.post.application.GetPostService; +import daybyquest.post.dto.response.PostResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PostQueryApi { + + private final GetPostService getPostService; + + public PostQueryApi(final GetPostService getPostService) { + this.getPostService = getPostService; + } + + @GetMapping("/post/{postId}") + @Authorization + public ResponseEntity getPost(@UserId final Long loginId, @PathVariable final Long postId) { + final PostResponse response = getPostService.invoke(loginId, postId); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/daybyquest/post/query/PostDao.java b/src/main/java/daybyquest/post/query/PostDao.java new file mode 100644 index 0000000..8113ea8 --- /dev/null +++ b/src/main/java/daybyquest/post/query/PostDao.java @@ -0,0 +1,6 @@ +package daybyquest.post.query; + +public interface PostDao { + + PostData getByPostId(final Long userId, final Long postId); +} diff --git a/src/main/java/daybyquest/post/query/PostDaoQuerydslImpl.java b/src/main/java/daybyquest/post/query/PostDaoQuerydslImpl.java new file mode 100644 index 0000000..101a561 --- /dev/null +++ b/src/main/java/daybyquest/post/query/PostDaoQuerydslImpl.java @@ -0,0 +1,48 @@ +package daybyquest.post.query; + +import static daybyquest.image.vo.QImage.image; +import static daybyquest.like.domain.QPostLike.postLike; +import static daybyquest.post.domain.QPost.post; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import daybyquest.global.error.exception.NotExistPostException; +import daybyquest.image.vo.Image; +import java.util.List; +import org.springframework.stereotype.Repository; + +@Repository +public class PostDaoQuerydslImpl implements PostDao { + + private final JPAQueryFactory factory; + + public PostDaoQuerydslImpl(final JPAQueryFactory factory) { + this.factory = factory; + } + + @Override + public PostData getByPostId(final Long userId, final Long postId) { + final PostData postData = factory.select(Projections.constructor(PostData.class, + post.userId, + post.id, + post.content, + post.updatedAt, + JPAExpressions.selectFrom(postLike) + .where(postLike.userId.eq(userId).and(postLike.postId.eq(postId))) + .exists())) + .from(post) + .where(post.id.eq(postId)) + .fetchOne(); + if (postData == null) { + throw new NotExistPostException(); + } + final List images = factory.select(image) + .from(post) + .leftJoin(post.images, image) + .where(post.id.eq(postId)) + .fetch(); + postData.setImages(images); + return postData; + } +} diff --git a/src/main/java/daybyquest/post/query/PostData.java b/src/main/java/daybyquest/post/query/PostData.java new file mode 100644 index 0000000..8418b59 --- /dev/null +++ b/src/main/java/daybyquest/post/query/PostData.java @@ -0,0 +1,35 @@ +package daybyquest.post.query; + +import daybyquest.image.vo.Image; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Getter; + +@Getter +public class PostData { + + private final Long userId; + + private final Long id; + + private final String content; + + private final LocalDateTime updatedAt; + + private final boolean liked; + + private List images; + + public PostData(final Long userId, final Long id, final String content, final LocalDateTime updatedAt, + final boolean liked) { + this.userId = userId; + this.id = id; + this.content = content; + this.updatedAt = updatedAt; + this.liked = liked; + } + + public void setImages(final List images) { + this.images = images; + } +} From dfbb8429abcb80e521b4328ad97c05104898667e Mon Sep 17 00:00:00 2001 From: DH CHOI Date: Thu, 26 Oct 2023 12:41:42 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20JPA=20=EC=A0=84=EB=9E=B5=EC=9D=84?= =?UTF-8?q?=20update=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-data.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-data.yml b/src/main/resources/application-data.yml index e4d26d2..2996add 100644 --- a/src/main/resources/application-data.yml +++ b/src/main/resources/application-data.yml @@ -15,4 +15,4 @@ spring: default_batch_fetch_size: 1000 dialect: org.hibernate.dialect.MariaDB103Dialect hibernate: - ddl-auto: create \ No newline at end of file + ddl-auto: update \ No newline at end of file