Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,11 @@ public ResponseEntity<Void> selectAnswer(

// λ‹΅λ³€ μ‚­μ œ (κ΄€λ¦¬μžλ§Œ κ°€λŠ₯)
@DeleteMapping("/{answerId}")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public ResponseEntity<Void> deleteAnswer(
@PathVariable Long postId,
@PathVariable Long answerId,
@AuthenticationPrincipal CustomUserDetails userDetails) {

Long userId = userDetails.getId();
boolean isAdmin = "ROLE_ADMIN".equals(userDetails.getRole());

answerService.deleteAnswer(postId, answerId, userId, isAdmin);
answerService.deleteAnswer(postId, answerId, userDetails.getId(), userDetails.getRole());
return ResponseEntity.noContent().build();
}
}
Expand Down
37 changes: 21 additions & 16 deletions src/main/java/com/hyetaekon/hyetaekon/answer/entity/Answer.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
package com.hyetaekon.hyetaekon.answer.entity;

import com.hyetaekon.hyetaekon.common.util.BaseEntity;
import com.hyetaekon.hyetaekon.post.entity.Post;
import com.hyetaekon.hyetaekon.user.entity.User;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import lombok.*;


@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "answer")
public class Answer {
public class Answer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // λ‹΅λ³€ ID

@Column(name = "post_id", nullable = false)
private Long postId; // κ²Œμ‹œκΈ€ ID
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;

@Column(name = "user_id", nullable = false)
private Long userId; // νšŒμ› ID
@ManyToOne
@JoinColumn(name = "user_id")
private User user;

@Column(name = "content", columnDefinition = "TEXT", nullable = false)
private String content; // λ‹΅λ³€ λ‚΄μš©

@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; // 생성일

@Column(name = "selected", nullable = false)
private boolean selected; // 채택 μ—¬λΆ€

// ⭐ 생성 μ‹œμ μ— createdAt μžλ™ μ„€μ •
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
public String getDisplayContent() {
if (getDeletedAt() != null) {
return "μ‚­μ œλœ λ‹΅λ³€μž…λ‹ˆλ‹€.";
} else if (getSuspendAt() != null) {
return "κ΄€λ¦¬μžμ— μ˜ν•΄ μ‚­μ œλœ λ‹΅λ³€μž…λ‹ˆλ‹€.";
}
return content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@
import com.hyetaekon.hyetaekon.answer.entity.Answer;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import org.mapstruct.ReportingPolicy;

@Mapper(componentModel = "spring")
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AnswerMapper {
AnswerMapper INSTANCE = Mappers.getMapper(AnswerMapper.class);

@Mapping(target = "content", expression = "java(answer.getDisplayContent())")
AnswerDto toDto(Answer answer);

@Mapping(target = "id", ignore = true) // idλŠ” μžλ™ μƒμ„±λ˜λ―€λ‘œ λ¬΄μ‹œ
@Mapping(target = "selected", ignore = true) // κΈ°λ³Έκ°’ false 처리
@Mapping(target = "createdAt", ignore = true) // createdAt μžλ™ μ„€μ •
Answer toEntity(AnswerDto answerDto);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hyetaekon.hyetaekon.answer.repository;

import com.hyetaekon.hyetaekon.answer.entity.Answer;
import com.hyetaekon.hyetaekon.post.entity.Post;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -10,9 +11,20 @@

@Repository
public interface AnswerRepository extends JpaRepository<Answer, Long> {
// νŽ˜μ΄μ§€λ„€μ΄μ…˜ μ μš©ν•œ λ‹΅λ³€ λͺ©λ‘ 쑰회
Page<Answer> findByPostId(Long postId, Pageable pageable);
// νŽ˜μ΄μ§€λ„€μ΄μ…˜ μ μš©ν•œ λ‹΅λ³€ λͺ©λ‘ 쑰회 (post 객체 μ‚¬μš©)
Page<Answer> findByPost(Post post, Pageable pageable);

// 채택 μ—¬λΆ€ 및 등둝일 κΈ°μ€€ μ •λ ¬λœ νŽ˜μ΄μ§• 처리된 λ‹΅λ³€ λͺ©λ‘ 쑰회
Page<Answer> findByPostIdOrderBySelectedDescCreatedAtDesc(Long postId, Pageable pageable);
Page<Answer> findByPostOrderBySelectedDescCreatedAtDesc(Post post, Pageable pageable);

// μ‚­μ œλœ λ‹΅λ³€λ§Œ 쑰회 (κ΄€λ¦¬μžμš©)
Page<Answer> findByPostAndDeletedAtIsNotNull(Post post, Pageable pageable);

// μ •μ§€λœ λ‹΅λ³€λ§Œ 쑰회 (κ΄€λ¦¬μžμš©)
Page<Answer> findByPostAndSuspendAtIsNotNull(Post post, Pageable pageable);

// ν†΅κ³„μš© μΉ΄μš΄νŒ… λ©”μ„œλ“œλ“€
long countByPost(Post post);
long countByPostAndDeletedAtIsNotNull(Post post);
long countByPostAndSuspendAtIsNotNull(Post post);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.hyetaekon.hyetaekon.post.entity.Post;
import com.hyetaekon.hyetaekon.post.repository.PostRepository;
import com.hyetaekon.hyetaekon.user.entity.PointActionType;
import com.hyetaekon.hyetaekon.user.entity.User;
import com.hyetaekon.hyetaekon.user.repository.UserRepository;
import com.hyetaekon.hyetaekon.user.service.UserPointService;
import jakarta.persistence.EntityNotFoundException;
import jakarta.transaction.Transactional;
Expand All @@ -28,29 +30,31 @@ public class AnswerService {
private final AnswerRepository answerRepository;
private final AnswerMapper answerMapper;
private final PostRepository postRepository;
private final UserRepository userRepository;
private final UserPointService userPointService;

// κ²Œμ‹œκΈ€μ— λ‹΅λ³€ λͺ©λ‘ 쑰회
public Page<AnswerDto> getAnswersByPostId(Long postId, Pageable pageable) {
// κ²Œμ‹œκΈ€ 쑴재 μ—¬λΆ€ 확인
postRepository.findByIdAndDeletedAtIsNull(postId)
Post post = postRepository.findByIdAndDeletedAtIsNull(postId)
.orElseThrow(() -> new GlobalException(ErrorCode.POST_NOT_FOUND_BY_ID));

// μ±„νƒλœ 닡변이 λ¨Όμ € λ‚˜μ˜€κ³ , κ·Έ λ‹€μŒ μ΅œμ‹ μˆœμœΌλ‘œ μ •λ ¬
Page<Answer> answersPage = answerRepository.findByPostIdOrderBySelectedDescCreatedAtDesc(postId, pageable);
Page<Answer> answersPage = answerRepository.findByPostOrderBySelectedDescCreatedAtDesc(post, pageable);

return answersPage.map(answerMapper::toDto);
}

public AnswerDto createAnswer(Long postId, AnswerDto answerDto, Long userId) {
// κ²Œμ‹œκΈ€ 쑴재 μ—¬λΆ€ 확인
postRepository.findByIdAndDeletedAtIsNull(postId)
// κ²Œμ‹œκΈ€κ³Ό μ‚¬μš©μž 객체 쑰회
Post post = postRepository.findByIdAndDeletedAtIsNull(postId)
.orElseThrow(() -> new GlobalException(ErrorCode.POST_NOT_FOUND_BY_ID));
User user = userRepository.findById(userId)
.orElseThrow(() -> new GlobalException(ErrorCode.USER_NOT_FOUND_BY_ID));

Answer answer = answerMapper.toEntity(answerDto);
answer.setPostId(postId);
answer.setUserId(userId);
answer.setCreatedAt(LocalDateTime.now());
answer.setPost(post);
answer.setUser(user);
answer = answerRepository.save(answer);

userPointService.addPointForAction(userId, PointActionType.ANSWER_CREATION);
Expand All @@ -73,7 +77,7 @@ public void selectAnswer(Long postId, Long answerId, Long userId) {
.orElseThrow(() -> new GlobalException(ErrorCode.ANSWER_NOT_FOUND));

// 닡변이 ν•΄λ‹Ή κ²Œμ‹œκΈ€μ— μ†ν•˜λŠ”μ§€ 확인
if (!answer.getPostId().equals(postId)) {
if (!answer.getPost().getId().equals(postId)) { // post 객체 μ‚¬μš©
throw new GlobalException(ErrorCode.ANSWER_NOT_MATCHED_POST);
}

Expand All @@ -82,23 +86,28 @@ public void selectAnswer(Long postId, Long answerId, Long userId) {
answerRepository.save(answer);

// λ‹΅λ³€ μž‘μ„±μžμ—κ²Œ 포인트 λΆ€μ—¬
userPointService.addPointForAction(answer.getUserId(), PointActionType.ANSWER_ACCEPTED);
userPointService.addPointForAction(answer.getUser().getId(), PointActionType.ANSWER_ACCEPTED); // user 객체 μ‚¬μš©
}

@Transactional
public void deleteAnswer(Long postId, Long answerId, Long userId, boolean isAdmin) {
public void deleteAnswer(Long postId, Long answerId, Long userId, String role) {
Answer answer = answerRepository.findById(answerId)
.orElseThrow(() -> new GlobalException(ErrorCode.ANSWER_NOT_FOUND));
.orElseThrow(() -> new GlobalException(ErrorCode.ANSWER_NOT_FOUND));

if (!answer.getPostId().equals(postId)) {
if (!answer.getPost().getId().equals(postId)) { // post 객체 μ‚¬μš©
throw new GlobalException(ErrorCode.ANSWER_NOT_MATCHED_POST);
}

if (!answer.getUserId().equals(userId) && !isAdmin) {
throw new AccessDeniedException("λ‹΅λ³€ μ‚­μ œ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.");
// μž‘μ„±μž λ˜λŠ” κ΄€λ¦¬μž 확인
boolean isOwner = answer.getUser().getId().equals(userId); // user 객체 μ‚¬μš©
boolean isAdmin = "ROLE_ADMIN".equals(role);

if (!isOwner && !isAdmin) {
throw new AccessDeniedException("λ‹΅λ³€ μ‚­μ œ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€");
}

answerRepository.delete(answer);
answer.delete(); // soft delete μ‚¬μš©
answerRepository.save(answer);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.hyetaekon.hyetaekon.comment.controller;

import com.hyetaekon.hyetaekon.comment.dto.CommentCreateRequestDto;
import com.hyetaekon.hyetaekon.comment.dto.CommentDto;
import com.hyetaekon.hyetaekon.comment.dto.CommentListResponseDto;
import com.hyetaekon.hyetaekon.comment.service.CommentService;
import com.hyetaekon.hyetaekon.common.jwt.CustomUserDetails;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,54 +22,58 @@ public class CommentController {

// κ²Œμ‹œκΈ€ λŒ“κΈ€ λͺ©λ‘ 쑰회 (νŽ˜μ΄μ§• 지원)
@GetMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Page<CommentDto>> getComments(@PathVariable Long postId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<CommentDto> comments = commentService.getComments(postId, page, size);
public ResponseEntity<Page<CommentListResponseDto>> getComments(
@PathVariable("postId") Long postId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<CommentListResponseDto> comments = commentService.getComments(postId, page, size);
return ResponseEntity.ok(comments);
}

// κ²Œμ‹œκΈ€μ— λŒ“κΈ€ μž‘μ„±
@PostMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<CommentDto> createComment(@PathVariable Long postId, @RequestBody CommentDto commentDto) {
CommentDto createdComment = commentService.createComment(postId, commentDto);
public ResponseEntity<CommentListResponseDto> createComment(
@PathVariable("postId") Long postId,
@RequestBody CommentCreateRequestDto commentDto,
@AuthenticationPrincipal CustomUserDetails userDetails) {

CommentListResponseDto createdComment = commentService.createComment(postId, userDetails.getId(), commentDto);
return ResponseEntity.status(HttpStatus.CREATED).body(createdComment);
}

// λŒ€λŒ“κΈ€ λͺ©λ‘ 쑰회
@GetMapping("/{commentId}/replies")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Page<CommentDto>> getReplies(@PathVariable Long postId,
@PathVariable Long commentId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size) {
Page<CommentDto> replies = commentService.getReplies(postId, commentId, page, size);
public ResponseEntity<Page<CommentListResponseDto>> getReplies(
@PathVariable("postId") Long postId,
@PathVariable("commentId") Long commentId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size) {
Page<CommentListResponseDto> replies = commentService.getReplies(postId, commentId, page, size);
return ResponseEntity.ok(replies);
}

// λŒ€λŒ“κΈ€ μž‘μ„±
//λŒ€λŒ“κΈ€ μž‘μ„±
@PostMapping("/{commentId}/replies")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<CommentDto> createReply(@PathVariable Long postId,
@PathVariable Long commentId,
@RequestBody CommentDto commentDto) {
CommentDto createdReply = commentService.createReply(postId, commentId, commentDto);
public ResponseEntity<CommentListResponseDto> createReply(
@PathVariable("postId") Long postId,
@PathVariable("commentId") Long commentId,
@RequestBody CommentCreateRequestDto commentDto,
@AuthenticationPrincipal CustomUserDetails userDetails) {

// λŒ€λŒ“κΈ€μ˜ λΆ€λͺ¨ λŒ“κΈ€ ID μ„€μ •
commentDto.setParentId(commentId);

CommentListResponseDto createdReply = commentService.createComment(postId, userDetails.getId(), commentDto);
return ResponseEntity.status(HttpStatus.CREATED).body(createdReply);
}

// λŒ“κΈ€ μ‚­μ œ (κ΄€λ¦¬μžλ§Œ κ°€λŠ₯)
@DeleteMapping("/admin/comments/{commentId}")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
// λŒ“κΈ€ μ‚­μ œ (κ΄€λ¦¬μžλ‚˜ λŒ“κΈ€ μž‘μ„±μžλ§Œ κ°€λŠ₯)
@DeleteMapping("/{commentId}")
public ResponseEntity<Void> deleteComment(
@PathVariable Long commentId,
@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable String postId) {

Long currentUserId = userDetails.getId();
boolean isAdmin = "ROLE_ADMIN".equals(userDetails.getRole());
@PathVariable("commentId") Long commentId,
@AuthenticationPrincipal CustomUserDetails userDetails) {

commentService.deleteComment(commentId, currentUserId, isAdmin);
commentService.deleteComment(commentId, userDetails.getId(), userDetails.getRole());
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.hyetaekon.hyetaekon.comment.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommentCreateRequestDto {
private Long parentId;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.hyetaekon.hyetaekon.comment.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommentListResponseDto {
private Long id;
private Long postId;
private Long parentId; // λŒ€λŒ“κΈ€μΌ 경우 λΆ€λͺ¨ λŒ“κΈ€ ID
private String content;
private String nickname;
private LocalDateTime createdAt;
}
Loading