Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ dependencies {

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

// JWT
implementation 'com.auth0:java-jwt:4.4.0'
}

def querydslDir = "src/main/generated/querydsl"
Expand All @@ -53,3 +56,4 @@ tasks.withType(JavaCompile).configureEach {
clean {
delete file(querydslDir)
}

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class QMember extends EntityPathBase<Member> {

public final StringPath name = createString("name");

public final StringPath password = createString("password");

public QMember(String variable) {
super(Member.class, forVariable(variable));
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/org/sopt/domain/article/dto/res/ArticleRes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@

import org.sopt.domain.article.entity.Article;
import org.sopt.domain.article.entity.ArticleTag;
import org.sopt.domain.comment.dto.res.CommentResponse;

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

public record ArticleRes(
Long id,
String title,
String content,
ArticleTag articleTag,
LocalDateTime createdAt,
Long memberId
Long memberId,
List<CommentResponse> comments
) {
public static ArticleRes from(Article article) {
public static ArticleRes from(Article article, List<CommentResponse> comments) {
return new ArticleRes(
article.getId(),
article.getTitle(),
article.getContent(),
article.getArticleTag(),
article.getCreatedAt(),
article.getMember().getId()
article.getMember().getId(),
comments
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import org.sopt.domain.article.dto.res.ArticleRes;
import org.sopt.domain.article.entity.Article;
import org.sopt.domain.article.repository.ArticleRepository;
import org.sopt.domain.comment.dto.res.CommentResponse;
import org.sopt.domain.comment.entity.Comment;
import org.sopt.domain.comment.repository.CommentRepository;
import org.sopt.domain.member.entity.Member;
import org.sopt.domain.member.repository.MemberRepository;
import org.sopt.global.api.ErrorCode;
Expand All @@ -23,6 +26,8 @@
public class ArticleServiceImpl implements ArticleService {
private final ArticleRepository articleRepository;
private final MemberRepository memberRepository;
private final CommentRepository commentRepository;


@Override
@Transactional
Expand All @@ -49,9 +54,24 @@ public void createArticle(Long userId, CreateArticleReq req) {

@Override
public ArticleRes getArticle(Long articleId) {
Article article = articleRepository.findById(articleId).orElseThrow(() -> new ArticleException(ErrorCode.ARTICLE_NOT_FOUND));

return ArticleRes.from(article);
Article article = articleRepository.findById(articleId)
.orElseThrow(() -> new ArticleException(ErrorCode.ARTICLE_NOT_FOUND));

List<Comment> comments = commentRepository
.findByArticleIdAndIsDeletedFalseOrderByCreatedAtAsc(articleId);

List<CommentResponse> commentResponses = comments.stream()
.map(c -> new CommentResponse(
c.getId(),
c.getMember().getId(),
c.getMember().getName(),
c.getContent(),
c.getCreatedAt()
))
.toList();

return ArticleRes.from(article, commentResponses);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.sopt.domain.comment.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sopt.domain.comment.dto.req.CommentCreateRequest;
import org.sopt.domain.comment.dto.req.CommentUpdateRequest;
import org.sopt.domain.comment.dto.res.CommentResponse;
import org.sopt.domain.comment.service.CommentServiceImpl;
import org.sopt.domain.member.repository.JwtService;
import org.sopt.global.api.ApiResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Tag(name = "Comment", description = "댓글 관련 API")
@RestController
@RequestMapping("/api/v1/comments")
@RequiredArgsConstructor
public class CommentController {

private final CommentServiceImpl commentService;
private final JwtService jwtService;

@Operation(summary = "댓글 생성")
@PostMapping
public ResponseEntity<ApiResponse<Void>> create(
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
@Valid @RequestBody CommentCreateRequest request
) {
Long memberId = extractMemberId(authorization);
commentService.create(memberId, request);
return ResponseEntity.ok(ApiResponse.ok(null));
}

@Operation(summary = "댓글 목록 조회")
@GetMapping
public ResponseEntity<ApiResponse<List<CommentResponse>>> list(
@RequestParam Long articleId
) {
return ResponseEntity.ok(
ApiResponse.ok(commentService.findByArticle(articleId))
);
}

@Operation(summary = "댓글 수정")
@PatchMapping("/{commentId}")
public ResponseEntity<ApiResponse<Void>> update(
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
@PathVariable Long commentId,
@Valid @RequestBody CommentUpdateRequest request
) {
Long memberId = extractMemberId(authorization);
commentService.update(memberId, commentId, request);
return ResponseEntity.ok(ApiResponse.ok(null));
}

@Operation(summary = "댓글 삭제")
@DeleteMapping("/{commentId}")
public ResponseEntity<ApiResponse<Void>> delete(
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
@PathVariable Long commentId
) {
Long memberId = extractMemberId(authorization);
commentService.delete(memberId, commentId);
return ResponseEntity.ok(ApiResponse.ok(null));
}

private Long extractMemberId(String authorization) {
String token = authorization.replace("Bearer ", "").trim();
return jwtService.verifyAndGetMemberId(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.sopt.domain.comment.dto.req;


import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record CommentCreateRequest(

@NotNull
Long articleId,

@NotBlank
@Size(max = 300, message = "댓글 내용은 최대 300자까지 입력할 수 있습니다.")
String content
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.domain.comment.dto.req;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;


public record CommentUpdateRequest(

@NotBlank
@Size(max = 300, message = "댓글 내용은 최대 300자까지 입력할 수 있습니다.")
String content
) {}
11 changes: 11 additions & 0 deletions src/main/java/org/sopt/domain/comment/dto/res/CommentResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.domain.comment.dto.res;

import java.time.LocalDateTime;

public record CommentResponse(
Long commentId,
Long memberId,
String memberName,
String content,
LocalDateTime createdAt
) {}
59 changes: 59 additions & 0 deletions src/main/java/org/sopt/domain/comment/entity/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.sopt.domain.comment.entity;

import jakarta.persistence.*;
import lombok.*;
import org.sopt.domain.article.entity.Article;
import org.sopt.domain.member.entity.Member;
import org.sopt.global.entity.BaseEntity;

import java.time.LocalDateTime;

@Entity
@Table(name = "comments")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Comment extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id", nullable = false)
private Article article;

@Column(nullable = false, length = 1000)
private String content;

@Column(nullable = false)
private Boolean isDeleted;

public static Comment create(
Member member,
Article article,
String content
) {
return Comment.builder()
.member(member)
.article(article)
.content(content)
.isDeleted(false)
.build();
}

public void updateContent(String content) {
this.content = content;
this.updatedAt = LocalDateTime.now();
}

public void delete() {
this.isDeleted = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.domain.comment.repository;

import org.sopt.domain.comment.entity.Comment;

import java.util.List;

public interface CommentCustomRepository {
List<Comment> findAllWithMember();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sopt.domain.comment.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.sopt.domain.comment.entity.Comment;
import org.springframework.stereotype.Repository;

import java.util.List;

import static org.sopt.domain.article.entity.QArticle.article;
import static org.sopt.domain.member.entity.QMember.member;


@Repository
@RequiredArgsConstructor
public class CommentCustomRepositoryImpl implements CommentCustomRepository {
private final JPAQueryFactory queryFactory;

@Override
public List<Comment> findAllWithMember() {
return queryFactory
.selectFrom(article)
.join(article.member, member).fetchJoin()
.fetch();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.domain.comment.repository;

import org.sopt.domain.comment.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByArticleIdAndIsDeletedFalseOrderByCreatedAtAsc(Long articleId);
}
17 changes: 17 additions & 0 deletions src/main/java/org/sopt/domain/comment/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.domain.comment.service;

import org.sopt.domain.comment.dto.req.CommentCreateRequest;
import org.sopt.domain.comment.dto.req.CommentUpdateRequest;
import org.sopt.domain.comment.dto.res.CommentResponse;

import java.util.List;

public interface CommentService {
void create(Long memberId, CommentCreateRequest request);

List<CommentResponse> findByArticle(Long articleId);

void update(Long memberId, Long commentId, CommentUpdateRequest request);

void delete(Long memberId, Long commentId);
}
Loading