Skip to content

Commit

Permalink
[Feature] 게시물 업로드, 상세 조회을 구현한다 (#34)
Browse files Browse the repository at this point in the history
* feature: Profile ID를 통한 조회 추가

* feature: Post에 검증 로직 추가

* feature: 게시물 업로드 구현

* feature: 게시물 단건 조회 구현

* chore: JPA 전략을 update로 변경
  • Loading branch information
vectorch9 authored Oct 26, 2023
1 parent 4a295df commit 9609cad
Show file tree
Hide file tree
Showing 17 changed files with 371 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
29 changes: 29 additions & 0 deletions src/main/java/daybyquest/post/application/GetPostService.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
56 changes: 56 additions & 0 deletions src/main/java/daybyquest/post/application/SavePostService.java
Original file line number Diff line number Diff line change
@@ -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<MultipartFile> files) {
users.validateExistentById(loginId);
final Post post = toEntity(loginId, request, toImageList(files));
return posts.save(post);
}

private List<Image> toImageList(final List<MultipartFile> 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<Image> images) {
return new Post(loginId, request.getQuestId(), request.getContent(), images);
}
}
36 changes: 30 additions & 6 deletions src/main/java/daybyquest/post/domain/Post.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,13 +32,16 @@
@EntityListeners(AuditingEntityListener.class)
public class Post {

private static final int MAX_IMAGE_SIZE = 5;

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

@Column(nullable = false)
private Long userId;

@Column
private Long questId;

@LastModifiedDate
Expand All @@ -51,15 +58,32 @@ public class Post {
@OrderColumn(name = "order")
private List<Image> images;

private boolean deleted;


public Post(Long userId, Long questId, String content, List<Image> images, boolean deleted) {
public Post(Long userId, Long questId, String content, List<Image> 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;
}
}
2 changes: 1 addition & 1 deletion src/main/java/daybyquest/post/domain/PostRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.Optional;
import org.springframework.data.repository.Repository;

public interface PostRepository extends Repository<Post, Long> {
interface PostRepository extends Repository<Post, Long> {

Post save(Post post);

Expand Down
4 changes: 3 additions & 1 deletion src/main/java/daybyquest/post/domain/PostState.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

public enum PostState {

PREPARING,
SUCCESS,
NEED_CHECK,
FAIL,
NOT_DECIDED;
NOT_DECIDED,
DELETED;
}
22 changes: 22 additions & 0 deletions src/main/java/daybyquest/post/domain/Posts.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions src/main/java/daybyquest/post/dto/request/SavePostRequest.java
Original file line number Diff line number Diff line change
@@ -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;
}
45 changes: 45 additions & 0 deletions src/main/java/daybyquest/post/dto/response/PostResponse.java
Original file line number Diff line number Diff line change
@@ -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<String> images;

public PostResponse(final ProfileResponse author, final Long id, final String content,
final LocalDateTime updatedAt, final boolean liked,
final List<String> 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()
);
}
}
30 changes: 30 additions & 0 deletions src/main/java/daybyquest/post/presentation/PostCommandApi.java
Original file line number Diff line number Diff line change
@@ -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<Long> savePost(@UserId final Long loginId, @RequestPart SavePostRequest request,
@RequestPart List<MultipartFile> files) {
final Long postId = savePostService.invoke(loginId, request, files);
return ResponseEntity.ok(postId);
}
}
27 changes: 27 additions & 0 deletions src/main/java/daybyquest/post/presentation/PostQueryApi.java
Original file line number Diff line number Diff line change
@@ -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<PostResponse> getPost(@UserId final Long loginId, @PathVariable final Long postId) {
final PostResponse response = getPostService.invoke(loginId, postId);
return ResponseEntity.ok(response);
}
}
6 changes: 6 additions & 0 deletions src/main/java/daybyquest/post/query/PostDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package daybyquest.post.query;

public interface PostDao {

PostData getByPostId(final Long userId, final Long postId);
}
48 changes: 48 additions & 0 deletions src/main/java/daybyquest/post/query/PostDaoQuerydslImpl.java
Original file line number Diff line number Diff line change
@@ -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<Image> images = factory.select(image)
.from(post)
.leftJoin(post.images, image)
.where(post.id.eq(postId))
.fetch();
postData.setImages(images);
return postData;
}
}
Loading

0 comments on commit 9609cad

Please sign in to comment.