Skip to content

Commit

Permalink
Merge pull request #20 from Leets-Official/feat/#16/게시글-작성게시글-삭제-기능-구현
Browse files Browse the repository at this point in the history
[feat] 게시글 작성 / 게시글 삭제 기능 구현
  • Loading branch information
dyk-im authored Nov 7, 2024
2 parents 0a4ce1c + fb0cfd3 commit 238ac7d
Show file tree
Hide file tree
Showing 35 changed files with 900 additions and 148 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.leets.xcellentbe.domain.article.controller;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.leets.xcellentbe.domain.article.dto.ArticleCreateRequestDto;
import com.leets.xcellentbe.domain.article.dto.ArticleCreateResponseDto;
import com.leets.xcellentbe.domain.article.dto.ArticleResponseDto;
import com.leets.xcellentbe.domain.article.service.ArticleService;
import com.leets.xcellentbe.global.response.GlobalResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/article")
@RequiredArgsConstructor
public class ArticleController {
private final ArticleService articleService;

//게시글 작성
@PostMapping
@Operation(summary = "게시글 작성", description = "새 게시글을 작성합니다.")
public ResponseEntity<GlobalResponseDto<ArticleCreateResponseDto>> createArticle(
HttpServletRequest request,
@RequestBody ArticleCreateRequestDto articleCreateRequestDto,
@RequestParam(value = "mediaFiles", required = false) List<MultipartFile> mediaFiles){
if (mediaFiles == null) {
mediaFiles = Collections.emptyList();
}
ArticleCreateResponseDto responseDto = articleService.createArticle(request, articleCreateRequestDto, mediaFiles);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success(responseDto));
}

//게시글 삭제(소프트)
@PatchMapping("/{articleId}")
@Operation(summary = "게시글 삭제", description = "게시글을 삭제(상태 변경)합니다.")
public ResponseEntity<GlobalResponseDto<Void>> deleteArticle(
HttpServletRequest request,
@PathVariable UUID articleId){
articleService.deleteArticle(request, articleId);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success());
}

//게시글 조회
@GetMapping("/{articleId}")
@Operation(summary = "게시글 조회", description = "해당 ID의 게시글을 조회합니다.")
public ResponseEntity<GlobalResponseDto<ArticleResponseDto>> getArticle(
HttpServletRequest request,
@PathVariable UUID articleId){
ArticleResponseDto articleResponseDto = articleService.getArticle(request, articleId);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success(articleResponseDto));
}

//메인 페이지 게시글 조회
@GetMapping
@Operation(summary = "게시글 목록 조회(스크롤)", description = "페이징을 적용하여 게시글 목록을 조회합니다.")
public ResponseEntity<GlobalResponseDto<List<ArticleResponseDto>>> getArticles(
HttpServletRequest request,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime cursor,
@RequestParam(defaultValue = "10") int size) {
List<ArticleResponseDto> articles = articleService.getArticles(request, cursor, size);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success(articles));
}

//리포스트 작성
@PostMapping("/{articleId}/repost")
@Operation(summary = "게시글 리포스트", description = "게시글을 리포스트합니다.")
public ResponseEntity<GlobalResponseDto<ArticleCreateResponseDto>> rePostArticle(
HttpServletRequest request,
@PathVariable UUID articleId){
ArticleCreateResponseDto responseDto = articleService.rePostArticle(request, articleId);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success(responseDto));
}

//리포스트 삭제
@PatchMapping("/{articleId}/unRepost")
@Operation(summary = "리포스트 삭제", description = "리포스트를 삭제합니다.")
public ResponseEntity<GlobalResponseDto<Void>> deleteRepost(
HttpServletRequest request,
@PathVariable UUID articleId){
articleService.deleteRepost(request, articleId);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success());
}
}
129 changes: 129 additions & 0 deletions src/main/java/com/leets/xcellentbe/domain/article/domain/Article.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.leets.xcellentbe.domain.article.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import com.leets.xcellentbe.domain.articleMedia.domain.ArticleMedia;
import com.leets.xcellentbe.domain.hashtag.domain.Hashtag;
import com.leets.xcellentbe.domain.shared.BaseTimeEntity;
import com.leets.xcellentbe.domain.shared.DeletedStatus;
import com.leets.xcellentbe.domain.user.domain.User;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID articleId;

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "writer_id")
private User writer;

@NotNull
@Column
private String content;

@NotNull
@Column(columnDefinition = "VARCHAR(30)")
@Enumerated(EnumType.STRING)
private DeletedStatus deletedStatus;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "repost_id")
private Article rePost;

@OneToMany(mappedBy = "article")
private List<Hashtag> hashtags;

@OneToMany(mappedBy = "article")
private List<ArticleMedia> mediaList;

private int viewCnt, repostCnt, likeCnt, commentCnt;


@Builder
private Article(User writer, String content, DeletedStatus deletedStatus) {
this.writer = writer;
this.content = content;
this.deletedStatus = DeletedStatus.NOT_DELETED;
}

public static Article createArticle(User writer, String content){
return Article.builder()
.writer(writer)
.content(content)
.deletedStatus(DeletedStatus.NOT_DELETED)
.build();
}

public void addRepost(Article rePost) {
this.rePost = rePost;
}

public void addHashtag(List<Hashtag> hashtags) {
if(this.hashtags == null){
this.hashtags = new ArrayList<>();
}
this.hashtags.addAll(hashtags);
}

public void deleteArticle() {
this.deletedStatus = DeletedStatus.DELETED;
}

public void addMedia(List<ArticleMedia> mediaList) {
if (this.mediaList == null) {
this.mediaList = new ArrayList<>();
}
this.mediaList.addAll(mediaList);
}

public void updateViewCount() {
this.viewCnt++;
}

public void plusRepostCount() {
this.repostCnt++;
}

public void minusRepostCount() {
this.repostCnt--;
}

public void plusLikeCount() {
this.likeCnt++;
}

public void minusLikeCount() {
this.likeCnt--;
}
public void plusCommentCount() {
this.commentCnt++;
}

public void minusCommentCount() {
this.commentCnt--;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.leets.xcellentbe.domain.article.domain.repository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import com.leets.xcellentbe.domain.article.domain.Article;

import io.lettuce.core.dynamic.annotation.Param;

@Repository
public interface ArticleRepository extends JpaRepository<Article, UUID> {

@Query("SELECT a FROM Article a ORDER BY a.createdAt DESC")
List<Article> findRecentArticles(Pageable pageable);

@Query("SELECT a FROM Article a WHERE a.createdAt < :cursor ORDER BY a.createdAt DESC")
List<Article> findRecentArticles(@Param("cursor") LocalDateTime cursor, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.leets.xcellentbe.domain.article.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ArticleCreateRequestDto {
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.leets.xcellentbe.domain.article.dto;

import java.util.UUID;

import com.leets.xcellentbe.domain.article.domain.Article;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ArticleCreateResponseDto {

private UUID articleId;

@Builder
private ArticleCreateResponseDto(UUID articleId, String message) {
this.articleId = articleId;
}

public static ArticleCreateResponseDto from(Article article) {
return ArticleCreateResponseDto.builder()
.articleId(article.getArticleId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.leets.xcellentbe.domain.article.dto;

import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import com.leets.xcellentbe.domain.article.domain.Article;
import com.leets.xcellentbe.domain.articleMedia.domain.ArticleMedia;
import com.leets.xcellentbe.domain.hashtag.domain.Hashtag;
import com.leets.xcellentbe.domain.shared.DeletedStatus;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ArticleResponseDto {
private UUID articleId;
private Long writerId;
private String content;
private DeletedStatus deletedStatus;
private List<String> hashtags;
private UUID rePostId;
private List<String> mediaUrls;
private int viewCnt;
private int rePostCnt;
private int likeCnt;
private int commentCnt;
private boolean owner;

@Builder
private ArticleResponseDto(UUID articleId, Long writerId, String content, DeletedStatus deletedStatus,
List<String> hashtags, UUID rePostId, List<String> mediaUrls, int viewCnt,
int rePostCnt, int likeCnt, int commentCnt, boolean owner) {
this.articleId = articleId;
this.writerId = writerId;
this.content = content;
this.deletedStatus = deletedStatus;
this.hashtags = hashtags;
this.rePostId = rePostId;
this.mediaUrls = mediaUrls;
this.viewCnt = viewCnt;
this.rePostCnt = rePostCnt;
this.likeCnt = likeCnt;
this.commentCnt = commentCnt;
this.owner = owner;
}

public static ArticleResponseDto from(Article article, boolean isOwner) {
return ArticleResponseDto.builder()
.articleId(article.getArticleId())
.content(article.getContent())
.deletedStatus(article.getDeletedStatus())
.writerId(article.getWriter().getUserId())
.hashtags(article.getHashtags() != null ? article.getHashtags()
.stream()
.map(Hashtag::getContent)
.collect(Collectors.toList()) : null)
.rePostId(article.getRePost() != null ? article.getRePost().getArticleId() : null)
.mediaUrls(article.getMediaList() != null ? article.getMediaList()
.stream()
.map(ArticleMedia::getFilePath) // 이미지 URL로 매핑
.collect(Collectors.toList()) : null)
.viewCnt(article.getViewCnt())
.rePostCnt(article.getRepostCnt())
.likeCnt(article.getLikeCnt())
.commentCnt(article.getCommentCnt())
.owner(isOwner)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.leets.xcellentbe.domain.post.exception;
package com.leets.xcellentbe.domain.article.exception;

import com.leets.xcellentbe.global.error.ErrorCode;
import com.leets.xcellentbe.global.error.exception.CommonException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.leets.xcellentbe.domain.article.exception;

import com.leets.xcellentbe.global.error.ErrorCode;
import com.leets.xcellentbe.global.error.exception.CommonException;

public class DeleteForbiddenException extends CommonException {
public DeleteForbiddenException() {

super(ErrorCode.DELETE_FORBIDDEN);
}
}
Loading

0 comments on commit 238ac7d

Please sign in to comment.