Web Project/booking

댓글 작성/조회 (Backend / Front)

hmmmmmmmmmmmm 2024. 12. 29. 19:39

 

Comment.java

package com.booking.booking.model;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Comment {

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

    @Column(columnDefinition = "TEXT")
    private String content;

    private LocalDateTime createdAt;



    // 댓글이 속한 게시글
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id")
    private Board board;


    // 댓글 작성자
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

 

 

 

CommentRequest.java

package com.booking.booking.request;

import com.booking.booking.model.Board;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class CommentRequest {

    @NotBlank
    private String content;
    @NotNull
    private Board board;
}

 

 

CommentResponse.java

package com.booking.booking.response;

import com.booking.booking.model.Comment;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentResponse {

    private Long id;
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
    private Long boardId;
    // 작성자 정보 (Comment 응답 반환 시 해당 게시글의 작성자 정보도 함께 반환)
    private UserResponse user;

    // 엔티티 -> DTO 변환 생성자
    public CommentResponse(Comment comment) {
        this.id = comment.getId();
        this.content = comment.getContent();
        this.createdAt = comment.getCreatedAt();
        this.boardId = comment.getBoard().getId();
        // User 엔티티도 DTO로 변환
        this.user = new UserResponse(
            comment.getUser().getId(),
            comment.getUser().getEmail(),
            comment.getUser().getFirstName(),
            comment.getUser().getLastName()
        );
    }

}

 

 

 

 

 

CommentController.java

// 댓글 작성
@PostMapping("/add/new-comment")
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<CommentResponse> createComment(@Valid @RequestBody CommentRequest request) {

    // 1. 현재 Security Context에서 인증 정보(로그인된 사용자의 인증 객체)를 가져옴
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication != null && authentication.getPrincipal() instanceof HotelUserDetails) {

            // 2. 인증 객체에서 principal(사용자 정보)를 가져와 HotelUserDetails로 형변환
            HotelUserDetails userDetails = (HotelUserDetails) authentication.getPrincipal();
            // 3. HotelUserDetails에서 User 정보 (작성자 정보) 가져옴
            User user = userDetails.getUser();

            Comment comment = new Comment();
            comment.setContent(request.getContent());
            comment.setUser(user);
            comment.setBoard(request.getBoard());
            comment.setCreatedAt(LocalDateTime.now());

            Comment savedComment = commentService.createComment(comment);

        return ResponseEntity.ok(new CommentResponse(savedComment));
    }

    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}


// 댓글 조회 (게시글별)
@GetMapping("/board/{boardId}")
public ResponseEntity<List<CommentResponse>> getCommentsByBoardId(@PathVariable Long boardId) {

    List<Comment> comments = commentService.getCommentsByBoardId(boardId);

    List<CommentResponse> commentResponses = comments.stream()
                                                     .map(CommentResponse::new)
                                                     .collect(Collectors.toList());

    return ResponseEntity.ok(commentResponses);
}

 

 

 

CommentServiceImpl.java

package com.booking.booking.service;

import com.booking.booking.model.Comment;
import com.booking.booking.repository.CommentRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class CommentServiceImpl implements CommentService {

    private final CommentRepository commentRepository;

    // 댓글 생성
    @Override
    public Comment createComment(Comment comment) {
        return commentRepository.save(comment);
    }

    // 댓글 조회 (게시글별)
    @Override
    public List<Comment> getCommentsByBoardId(Long boardId) {
        return commentRepository.findByBoardIdOrderByCreatedAtDesc(boardId);
    }


}

 

 

 

CommentRepository.java

public interface CommentRepository extends JpaRepository<Comment, Long> {

    List<Comment> findByBoardIdOrderByCreatedAtDesc(Long boardId);
}

 

 

 

 

 

프론트

 

 

 

어드민 외 댓글 작성 불가

 

 

 

 

어드민 댓글 작성

 

 

 

ApiFunctions.js

// 댓글 작성
export async function CreateComment(commentData) {
    try {
        const response = await api.post("/comments/add/new-comment", commentData, {
            headers: getHeader()
        });
        return response.data;
    }
    catch(error) {
        if(error.response && error.response.data) {
            throw new Error(error.response.data)
        }
        else {
            throw new Error(`Error creating a comment: ${error.message}`)
        }
    }
}



// 댓글 조회 (게시글별)
export async function GetCommentsByBoardId(boardId) {
    try {
        const result = await api.get(`/comments/board/${boardId}`, {
            headers: getHeader()
        })
        return result.data
    }
    catch(error) {
        if(error.response && error.response.data) {
            throw new Error(error.response.data)
        }
        else {
            throw new Error(`Error fetching all comments: ${error.message}`)
        }
    }
}

 

 

 

CommentSection.jsx

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { CreateComment, GetCommentsByBoardId } from '../utils/ApiFunctions';

export default function CommentSection() {
  const [comments, setComments] = useState([]);
  const [newComment, setNewComment] = useState('');
  const { boardId } = useParams();

  const userRole = localStorage.getItem("userRole")

  
  // 댓글 조회
  useEffect(() => {
    const fetchComments = async () => {
        try {
            const commentList = await GetCommentsByBoardId(boardId)
            setComments(commentList.map((comment) => ({
                id: comment.id,
                content: comment.content,
                author: comment.user.firstName + ' ' + comment.user.lastName,
                createdAt: comment.createdAt,
                boardId: comment.boardId,
                userEmail: comment.user.email,
            })));
        }
        catch (error) {
            console.error('댓글 조회 중 오류 발생:', error);
            alert(error.message);
        }
    };

    if (boardId) {
        fetchComments();
    }
  }, [boardId]);


  
  const canCreateComment = () => {
    return userRole === 'ROLE_ADMIN';
  }


  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!newComment.trim()) {
        alert('댓글 내용을 입력해주세요.')
        return;
    }

    try {
        const commentData = {
            content: newComment,
            board: {
                id: boardId,
            }
        }

        // 댓글 생성 API
        await CreateComment(commentData);

        // 댓글 생성 후 댓글 목록 다시 조회
        const updatedComments = await GetCommentsByBoardId(boardId);
        setComments(updatedComments.map((comment) => ({
            id: comment.id,
            content: comment.content,
            author: comment.user.firstName + ' ' + comment.user.lastName,
            createdAt: comment.createdAt,
            boardId: comment.boardId,
        })));
        setNewComment('');
    }

    catch (error) {
      console.error('댓글 작성 중 오류 발생:', error);
      alert(error.message);
    }

    
  };

  return (
    <div className="mt-12">
      <h2 className="text-xl font-bold mb-4">댓글</h2>
      
      {/* 댓글 입력 폼 */}
      <form onSubmit={handleSubmit} className="mb-8">
        <div className="flex flex-col space-y-2">
          <textarea
            value={newComment}
            onChange={(e) => setNewComment(e.target.value)}
            rows="3"
            placeholder={canCreateComment() 
              ? "댓글을 입력하세요..." 
              : "어드민 권한만 작성 가능합니다."
            }
            className={`w-full p-3 border rounded-lg resize-none focus:outline-none focus:ring-2 
                ${canCreateComment() 
                  ? 'focus:ring-blue-500' 
                  : 'bg-gray-100 cursor-not-allowed'
                }`}
            disabled={!canCreateComment()}
          />
          {canCreateComment() && (
            <div className="flex justify-end">
                <button
                    type="submit"
                    className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
                >
                    댓글 작성
                </button>
                </div>
            )}
            </div>
        </form>

      {/* 댓글 목록 */}
      {comments.length === 0 ? (
        <p className="text-center text-gray-500">아직 작성된 댓글이 없습니다.</p>
      ) : (
        <div className="space-y-4">
            {comments.map((comment) => (
            <div key={comment.id} className="p-4 bg-gray-50 rounded-lg">
                <div className="flex justify-between items-center mb-2">
                <span className="font-semibold">{comment.author}</span>
                <span className="text-sm text-gray-500">
                    {new Date(comment.createdAt).toLocaleString()}
                </span>
                </div>
                <p className="text-gray-700 text-left">{comment.content}</p>
            </div>
            ))}
        </div>
      )}
    </div>
  );
};