본문 바로가기
Node.js/Node 프로젝트 예제

[Node.js] 게시판[4/4] 댓글 기능, 이미지 업로드

by P_eli 2023. 12. 27.
728x90
반응형

server.js 에 필요한 모듈을 가져옵니다.

const multer = require('multer');
const path = require('path');

 

없다면 설치해 줍니다.

npm install multer
npm install path

 

이미지를 저장할 디렉터리를 하나 생성합니다.

public 디렉터리 안에 uploads 디렉터리를 생성해줍니다.

 

이미지 주소와 업로드 기능을 참고하기 위해 server.js에 아래 코드를 추가해 이미지 주소 연동해줍니다.

// 이미지 주소 연동
app.use('/public/uploads', express.static(path.join(__dirname, 'public/uploads')));

// 이미지를 저장할 디렉토리 및 파일명 지정
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, 'public/uploads/');
    },
    filename: function (req, file, cb) {
      cb(null, Date.now() + '-' + file.originalname);
    }
});
const upload = multer({ storage: storage });

 

MySQL에 가서 board 테이블에 Column 을 추가해줍니다

Column : image_url

Datatype : VARCHAR(255)

 

게시글 작성 기능에 이미지 업로드 기능을 추가해줍니다.

server.js

// 새로운 게시글을 작성하는 라우트입니다. (이미지 업로드 기능 추가)
app.post('/article', upload.single('image'), jsonParser, (req, res) => {
    // 'board' 테이블에 새 레코드를 삽입하기 위한 SQL 쿼리입니다.
    const sql = 'INSERT INTO board (title, user_id, content, image_url) VALUES (?,?,?,?)';
    const title = req.body.title;
    const user_id = req.body.user_id;
    const content = req.body.content;

   // 변경된 부분: 이미지 파일이 업로드된 경우, 이미지 URL을 요청 데이터에 추가합니다.
   const imageUrl = req.file ? `/uploads/${req.file.filename}` : null;

    // 변경된 부분: params 변수를 정의하여 데이터를 담습니다.
    const params = [title, user_id, content, imageUrl];

    // 주어진 매개변수로 쿼리를 실행합니다.
    connection.query(sql, params, (err, rows, fields) => {
        if (err) {
            console.error('새 글 삽입 중 오류 발생:', err);
            return res.status(500).json({ status: 500, message: '내부 서버 오류' });
        }

        console.log(rows);

        // 쿼리 결과를 응답으로 전송합니다 (클라이언트에게 무언가를 전송하려고 가정합니다).
        res.status(201).json({ status: 201, message: '게시글이 성공적으로 작성되었습니다.', data: rows });
    });
});

 

이제 public 디렉터리 안에 create.html 에 가서 이미지를 받아올 태그와 스크립트 코드를 수정해줍니다.

<!-- create.html -->

<style>
    /* create.html에 해당하는 CSS 스타일 */

    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #f4f4f4;
    }

    form {
        max-width: 600px;
        margin: 20px auto;
        padding: 20px;
        background-color: #fff;
        border: 1px solid #ddd;
    }

    label {
        display: block;
        margin-bottom: 5px;
        font-weight: bold;
    }

    input[type="text"],
    textarea {
        width: 100%;
        padding: 8px;
        margin-bottom: 10px;
        box-sizing: border-box;
    }

    button {
        background-color: #333;
        color: #fff;
        padding: 10px;
        border: none;
        cursor: pointer;
        border-radius: 5px;
        margin-right: 10px; /* 추가된 부분: 뒤로가기 버튼과의 간격을 조절합니다. */
    }

    button:hover {
        background-color: #555;
    }

</style>

<!-- 페이지 상단에 표시되는 제목입니다. -->
<h1>Create Article</h1>

<!-- 게시글 작성 폼입니다. -->
<form id="create-form" enctype="multipart/form-data">
    <label for="title">제목:</label>
    <input type="text" id="title" name="title" required><br>

    <label for="user_id">작성자:</label>
    <input type="text" id="user_id" name="user_id" value="<%= user.username %>" readonly required><br>
    <label for="image">이미지 업로드:</label>
    <input type="file" id="image" name="image">
    <label for="content">내용:</label>
    <textarea id="content" name="content" required></textarea><br>

    <!-- 게시글 작성 버튼 -->
    <button type="submit">게시글 작성</button>

    <!-- 뒤로가기 버튼 -->
    <button onclick="goBack()">뒤로 가기</button>
</form>


<!-- 뒤로가기 버튼을 처리하는 스크립트입니다. -->
<script>
    
    // 뒤로가기 버튼을 눌렀을 때 이전 페이지로 이동하는 함수
    function goBack() {
        window.history.back();
    }

    // 변경된 부분: 사용자 정보 가져와서 작성자 필드에 설정
    fetch('/checkSession')
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json();
        })
        .then(user => {
            const writerField = document.getElementById('user_id');
            if (user) {
                // 세션에서 가져온 사용자 이름을 작성자 필드에 설정
                writerField.value = user.username;
            } else {
                // 사용자가 로그인하지 않은 경우에 대한 처리
                console.log('User not logged in.');
            }
        })
        .catch(error => {
            console.error('Error checking user session:', error);
        });

        document.addEventListener('DOMContentLoaded', function () {
    const createForm = document.getElementById('create-form');

    // Declare formData outside the event listener
    const formData = new FormData();

    createForm.addEventListener('submit', function (event) {
        event.preventDefault();

        // Retrieve values from form fields
        const title = document.getElementById('title').value;
        const user_id = document.getElementById('user_id').value;
        const content = document.getElementById('content').value;

        // Check if any of the required fields are empty
        if (!title || !user_id || !content) {
            console.error('Incomplete data for article creation');
            return;  // Stop further execution if data is incomplete
        }

        // Retrieve the user ID from the cookie
        const userId = getCookie('userID');

        // Clear existing form data before appending new data
        formData.delete('title');
        formData.delete('user_id');
        formData.delete('content');
        formData.delete('userId');
        formData.delete('image');

        // Append form data
        formData.append('title', title);
        formData.append('user_id', user_id);
        formData.append('content', content);
        formData.append('userId', userId);

        // Get the selected file input
        const imageInput = document.getElementById('image');
        // Check if a file is selected
        if (imageInput.files.length > 0) {
            formData.append('image', imageInput.files[0]);
        }

        // AJAX or Fetch API to send the article creation request to the server
        fetch('/article', {
            method: 'POST',
            headers: {
                'Authorization': 'Bearer ' + userId
            },
            body: formData  // Use the FormData object for file uploads
        })
        .then(response => response.json())
        .then(data => {
            // Server response handling and displaying result message
            console.log(data.message);
            if (data.status === 201) {
                // Article creation successful, redirect to the desired page
                alert("게시글이 생성되었습니다!");
                window.location.href = 'http://localhost:3000/';  // Update the URL as needed
            }
        })
        .catch(error => {
            console.error('Error creating article:', error);
        });
    });

    // Function to retrieve a cookie by name
    function getCookie(name) {
        const cookies = document.cookie.split(';');
        for (const cookie of cookies) {
            const [cookieName, cookieValue] = cookie.trim().split('=');
            if (cookieName === name) {
                return cookieValue;
            }
        }
        return null;
    }
});
</script>

 

업데이트 기능도 이미지 수정할 수 있게 수정해줍니다.

server.js

// 게시글 수정
app.put('/article/:id', upload.single('image'), jsonParser, (req, res) => {
    // 요청에서 필요한 정보 추출
    const articleId = req.params.id;
    const { title, content } = req.body;

    // 주어진 ID로 기존의 글이 존재하는지 확인
    connection.query('SELECT * FROM board WHERE idx = ?', [articleId], (err, rows) => {
        if (err) {
            console.error('글 존재 여부 확인 중 오류 발생:', err);
            return res.status(500).json({ status: 500, message: '내부 서버 오류' });
        }

        // 글이 존재하지 않으면 404 에러 반환
        if (rows.length === 0) {
            return res.status(404).json({ status: 404, message: '글을 찾을 수 없습니다.' });
        }

        // 변경된 부분: 이미지 파일이 업로드된 경우, 이미지 URL을 요청 데이터에 추가합니다.
        const imageUrl = req.file ? `/uploads/${req.file.filename}` : null;

        // 새로운 데이터로 글 업데이트
        let updateQuery = 'UPDATE board SET title = ?, content = ?, update_time = CURRENT_TIMESTAMP';
        
        // 이미지 URL이 있는 경우에만 추가
        if (imageUrl) {
            updateQuery += ', image_url = ?';
        }

        updateQuery += ' WHERE idx = ?';

        const updateParams = [title, content];

        // 이미지 URL이 있는 경우에만 매개변수에 추가
        if (imageUrl) {
            updateParams.push(imageUrl);
        }

        updateParams.push(articleId);

        connection.query(updateQuery, updateParams, (err, rows) => {
            if (err) {
                console.error('글 업데이트 중 오류 발생:', err);
                return res.status(500).json({ status: 500, message: '내부 서버 오류' });
            }

            // 업데이트가 성공하면 성공 메시지와 데이터 반환
            res.json({ status: 200, message: '수정이 완료되었습니다.', data: { title, content, imageUrl } });
        });
    });
});

 

상세보기 기능도 수정해줍니다.

// 기존 게시글을 ID를 기반으로 가져오는 라우트입니다.
app.get('/articles/:id', (req, res) => {
    const articleId = req.params.id;
    console.log(`ID가 ${articleId}인 게시글을 가져오는 중`);

    // 디버깅 정보와 함께 데이터베이스에 쿼리합니다.
    connection.query('SELECT * FROM board WHERE idx = ?', [articleId], (err, rows) => {
        if (err) {
            console.error('게시글을 가져오는 중 오류 발생:', err);
            // 더 자세한 오류 응답을 반환합니다.
            return res.status(500).json({ error: '내부 서버 오류', details: err.message });
        }

        if (!rows.length) {
            console.log(`ID가 ${articleId}인 게시글을 찾을 수 없음`);
            return res.status(404).json({ error: '게시글을 찾을 수 없음' });
        }

        const article = rows[0];

        // Increase the view count in the database
        connection.query('UPDATE board SET view_cnt = view_cnt + 1 WHERE idx = ?', [articleId], (updateErr, updateResult) => {
            if (updateErr) {
                console.error('조회수를 증가하는 중 오류 발생:', updateErr);
                // 오류 응답을 반환합니다.
                return res.status(500).json({ error: '내부 서버 오류', details: updateErr.message });
            }

            console.log(`ID가 ${articleId}인 게시글의 조회수를 증가함`);
            res.json(article);
        });
    });
});

 

public 디렉터리 안에 article-details.html 도 수정해 줍니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- 페이지의 메타 정보를 설정합니다. -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- 페이지의 제목을 설정합니다. -->
    <title>게시글 상세 정보</title>

    <!-- 페이지의 스타일을 지정하는 CSS 코드입니다. -->
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }

        h1 {
            background-color: #333;
            color: #fff;
            padding: 10px;
            margin: 0;
        }

        h1 a {
            text-decoration: none;
            color: #fff;
            transition: color 0.3s ease;
        }

        h1 a:hover {
            color: blue;
        }

        #article-details {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ddd;
        }

        #article-details strong {
            font-weight: bold;
        }

        #article-details hr {
            margin: 10px 0;
            border: 0;
            border-top: 1px solid #ddd;
        }

        #article-button {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #f4f4f4;
        }

        .action-button {
            display: inline-block;
            padding: 10px;
            margin: 10px 5px 20px;
            background-color: #333;
            color: #fff;
            text-decoration: none;
            border: none;
            cursor: pointer;
            border-radius: 5px;
        }

        .action-button:hover {
            background-color: #555;
        }

        /* 댓글 스타일링 */
        #comments {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ddd;
        }

        .comment {
            margin-bottom: 10px;
            display: flex; /* Added to enable flexbox layout */
            align-items: baseline; /* Align items along the baseline */
        }

        .comment p {
            margin: 0; /* Remove default margin for paragraphs */
        }

        .comment strong {
            margin-right: 10px; /* Adjust the margin as needed */
        }

        .comment-text {
            margin-top: 5px;
            flex-grow: 1; /* Added to allow text to take remaining space */
        }

        /* Style for the delete button */
        .comment .delete-button {
            margin-left: 10px; /* Adjust the margin as needed */
        }
    </style>
</head>
<body>

    <!-- 페이지 상단에 위치하는 제목 부분입니다. -->
    <h1><a href="http://localhost:3000/">게시글 상세 정보</a></h1>

    <!-- 게시글의 상세 정보를 표시하는 부분입니다. -->
    <div id="article-details"></div>

    <!-- 업데이트 및 삭제 버튼이 있는 부분입니다. -->
    <div id="article-button">  
        <!-- 업데이트 버튼 -->
        <button id="update-button" class="action-button">게시글 수정</button>
        
        <!-- 삭제 버튼 -->
        <button id="delete-button" class="action-button">게시글 삭제</button>

        <!-- 뒤로가기 버튼 -->
        <button id="goBack" class="action-button" onclick="goBack()">목록</button>
    </div>
    <!-- JavaScript 코드입니다. -->
<script>
    // URL에서 게시글 ID 가져오기
    const urlParams = new URLSearchParams(window.location.search);
    const articleId = urlParams.get('id');

    // 게시글 상세 정보 가져오기
    fetch(`http://localhost:3000/articles/${articleId}`)
        .then(response => response.json())
        .then(article => {
            const articleDetails = document.getElementById('article-details');

            if (article) {
                // 게시글이 존재하는 경우 상세 정보를 표시합니다.
                const imageUrlTag = article.image_url ? `<img src="/public${article.image_url}"><br>` : 'No Image<br>';
                articleDetails.innerHTML = `
                    <strong>Title:</strong> ${article.title}<br>
                    <strong>Writer:</strong> ${article.user_id}<br>
                    <strong>Image:</strong> ${imageUrlTag}<br>
                    <strong>Content:</strong> ${article.content}<br>
                    <strong>Views:</strong> ${article.view_cnt+1}<br>
                    <strong>Insert Time:</strong> ${formatCommentTime(article.insert_time)}<br>
                    <strong>Update Time:</strong> ${formatCommentTime(article.update_time)}<br>
                    <hr>
                `;
            } else {
                // 게시글이 존재하지 않는 경우 메시지를 표시합니다.
                articleDetails.innerHTML = '<p>게시글을 찾을 수 없습니다.</p>';
            }

            // 사용자가 작성자인 경우에만 업데이트 및 삭제 버튼을 활성화합니다.
            const updateButton = document.getElementById('update-button');
            const deleteButton = document.getElementById('delete-button');

            fetch('/checkSession')
                .then(response => response.json())
                .then(user => {
                    if (user && user.username === article.user_id) {
                        updateButton.style.display = 'inline-block';
                        deleteButton.style.display = 'inline-block';

                        // 업데이트 버튼에 이벤트 리스너 추가
                        updateButton.addEventListener('click', () => {
                            // 업데이트 페이지로 리다이렉트
                            window.location.href = `/update.html?id=${articleId}`;
                        });
                    } else {
                        updateButton.style.display = 'none';
                        deleteButton.style.display = 'none';
                    }

                    // 삭제 버튼에 이벤트 리스너 추가
                    deleteButton.addEventListener('click', () => {
                        // 사용자가 정말로 게시글을 삭제하고 싶어 하는지 확인합니다.
                        const confirmDelete = confirm('정말로 게시글을 삭제하시겠습니까??');

                        if (confirmDelete) {
                            // 게시글을 삭제하기 위한 DELETE 요청 수행
                            fetch(`http://localhost:3000/article/${articleId}`, {
                                method: 'DELETE'
                            })
                            .then(response => response.json())
                            .then(data => {
                                alert("삭제 하였습니다!");
                                // 선택적으로 게시글 목록 페이지로 리다이렉트할 수 있습니다.
                                window.location.href = 'http://localhost:3000/';
                            })
                            .catch(error => {
                                console.error('게시글 삭제 오류:', error);
                                alert('게시글을 삭제하는 동안 오류가 발생했습니다. 다시 시도하세요.');
                            });
                        }
                    });
                })
                .catch(error => {
                    console.error('사용자 세션 확인 오류:', error);
                });
        })
        .catch(error => {
            console.error(`게시글 가져오기 오류: ${error.message}`);
            // ... (기존 코드 유지)
        });

    // 이전 페이지로 이동하는 함수
    function goBack() {
        window.location.href = 'http://localhost:3000/';
    }
        // 로그인 상태 확인 함수
    function checkLoginStatus() {
        fetch('/checkSession')
            .then(response => response.json())
            .then(user => {
                const submitCommentButton = document.getElementById('submit-comment');

                if (user) {
                    // 로그인 상태인 경우
                    submitCommentButton.style.display = 'inline-block';
                } else {
                    // 로그인 상태가 아닌 경우
                    submitCommentButton.style.display = 'none';
                }
            })
            .catch(error => {
                console.error('로그인 상태 확인 오류:', error);
            });
    }
        //작성 시간을 한국 시간으로 형식화하는 함수
    function formatCommentTime(timeString) {
        if (!timeString) {
            return 'N/A';
        }

        const options = {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
            hour: 'numeric',
            minute: 'numeric',
        };
        const commentTime = new Date(timeString).toLocaleDateString('ko-KR', options);
        return commentTime;
    }
    </script>

</body>
</html>

 

이제 댓글 기능을 만들어봅시다.

MySQL에 댓글 테이블을 생성해줍니다.

CREATE TABLE comments (
    comment_id INT AUTO_INCREMENT PRIMARY KEY,
    article_id INT,
    user_id INT,
    comment_text TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (article_id) REFERENCES board(idx),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

 

server.js에 가서 댓글 기능 코드를 추가합니다.

// 댓글
app.post('/comment', jsonParser, (req, res) => {
    const { article_id, user_id, comment_text } = req.body;
    const insertCommentQuery = 'INSERT INTO comments (article_id, user_id, comment_text) VALUES (?, ?, ?)';
    connection.query(insertCommentQuery, [article_id, user_id, comment_text], (err, result) => {
        if (err) {
            console.error('댓글 추가 중 오류 발생:', err);
            return res.status(500).json({ success: false, message: '서버 오류' });
        }
        res.status(201).json({ success: true, message: '댓글이 성공적으로 작성되었습니다.', data: result });
    });
});

// 댓글 가져오기
app.get('/comments/:article_id', (req, res) => {
    const articleId = req.params.article_id;
    const getCommentsQuery = 'SELECT * FROM comments WHERE article_id = ? ORDER BY created_at DESC';
    connection.query(getCommentsQuery, [articleId], (err, comments) => {
        if (err) {
            console.error('댓글 가져오기 중 오류 발생:', err);
            return res.status(500).json({ success: false, message: '서버 오류' });
        }
        res.json(comments);
    });
});
// 댓글 삭제
app.delete('/comment/:id', (req, res) => {
    const commentId = req.params.id;

    // 'comments' 테이블에서 주어진 ID의 댓글을 삭제하는 SQL 쿼리입니다.
    const deleteCommentQuery = 'DELETE FROM comments WHERE comment_id = ?';

    // 주어진 ID로 댓글을 찾고 삭제합니다.
    connection.query(deleteCommentQuery, [commentId], (err, result) => {
        if (err) {
            console.error('댓글 삭제 중 오류 발생:', err);
            return res.status(500).json({ success: false, message: '서버 오류' });
        }

        // 삭제 성공 시 응답을 반환합니다.
        res.json({ success: true, message: '댓글이 성공적으로 삭제되었습니다.', data: result });
    });
});

 

public 디렉터리 안에 article-details.html 파일도 댓글기능을 추가하기 위해 수정해줍니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- 페이지의 메타 정보를 설정합니다. -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- 페이지의 제목을 설정합니다. -->
    <title>게시글 상세 정보</title>

    <!-- 페이지의 스타일을 지정하는 CSS 코드입니다. -->
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }

        h1 {
            background-color: #333;
            color: #fff;
            padding: 10px;
            margin: 0;
        }

        h1 a {
            text-decoration: none;
            color: #fff;
            transition: color 0.3s ease;
        }

        h1 a:hover {
            color: blue;
        }

        #article-details {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ddd;
        }

        #article-details strong {
            font-weight: bold;
        }

        #article-details hr {
            margin: 10px 0;
            border: 0;
            border-top: 1px solid #ddd;
        }

        #article-button {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #f4f4f4;
        }

        .action-button {
            display: inline-block;
            padding: 10px;
            margin: 10px 5px 20px;
            background-color: #333;
            color: #fff;
            text-decoration: none;
            border: none;
            cursor: pointer;
            border-radius: 5px;
        }

        .action-button:hover {
            background-color: #555;
        }

        /* 댓글 스타일링 */
        #comments {
            max-width: 600px;
            margin: 20px auto;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ddd;
        }

        .comment {
            margin-bottom: 10px;
            display: flex; /* Added to enable flexbox layout */
            align-items: baseline; /* Align items along the baseline */
        }

        .comment p {
            margin: 0; /* Remove default margin for paragraphs */
        }

        .comment strong {
            margin-right: 10px; /* Adjust the margin as needed */
        }

        .comment-text {
            margin-top: 5px;
            flex-grow: 1; /* Added to allow text to take remaining space */
        }

        /* Style for the delete button */
        .comment .delete-button {
            margin-left: 10px; /* Adjust the margin as needed */
        }
    </style>
</head>
<body>

    <!-- 페이지 상단에 위치하는 제목 부분입니다. -->
    <h1><a href="http://localhost:3000/">게시글 상세 정보</a></h1>

    <!-- 게시글의 상세 정보를 표시하는 부분입니다. -->
    <div id="article-details"></div>

    <!-- 업데이트 및 삭제 버튼이 있는 부분입니다. -->
    <div id="article-button">  
        <!-- 업데이트 버튼 -->
        <button id="update-button" class="action-button">게시글 수정</button>
        
        <!-- 삭제 버튼 -->
        <button id="delete-button" class="action-button">게시글 삭제</button>

        <!-- 뒤로가기 버튼 -->
        <button id="goBack" class="action-button" onclick="goBack()">목록</button>
    </div>

<!-- 댓글 부분 -->
<div id="comments">
    <h2>댓글</h2>
    <div id="comment-list"></div>
    <textarea id="comment-text" placeholder="댓글을 입력하세요"></textarea>
    
    <!-- 로그인 상태인 경우에만 댓글 작성 버튼을 표시합니다. -->
    <button id="submit-comment" class="action-button" onclick="submitComment()">댓글 작성</button>
</div>

 <!-- JavaScript 코드입니다. -->
<script>
    // URL에서 게시글 ID 가져오기
    const urlParams = new URLSearchParams(window.location.search);
    const articleId = urlParams.get('id');

    // 게시글 상세 정보 가져오기
    fetch(`http://localhost:3000/articles/${articleId}`)
        .then(response => response.json())
        .then(article => {
            const articleDetails = document.getElementById('article-details');

            if (article) {
                // 게시글이 존재하는 경우 상세 정보를 표시합니다.
                const imageUrlTag = article.image_url ? `<img src="/public${article.image_url}"><br>` : 'No Image<br>';
                articleDetails.innerHTML = `
                    <strong>Title:</strong> ${article.title}<br>
                    <strong>Writer:</strong> ${article.user_id}<br>
                    <strong>Image:</strong> ${imageUrlTag}<br>
                    <strong>Content:</strong> ${article.content}<br>
                    <strong>Views:</strong> ${article.view_cnt+1}<br>
                    <strong>Insert Time:</strong> ${formatCommentTime(article.insert_time)}<br>
                    <strong>Update Time:</strong> ${formatCommentTime(article.update_time)}<br>
                    <hr>
                `;
            } else {
                // 게시글이 존재하지 않는 경우 메시지를 표시합니다.
                articleDetails.innerHTML = '<p>게시글을 찾을 수 없습니다.</p>';
            }

            // 사용자가 작성자인 경우에만 업데이트 및 삭제 버튼을 활성화합니다.
            const updateButton = document.getElementById('update-button');
            const deleteButton = document.getElementById('delete-button');

            fetch('/checkSession')
                .then(response => response.json())
                .then(user => {
                    if (user && user.username === article.user_id) {
                        updateButton.style.display = 'inline-block';
                        deleteButton.style.display = 'inline-block';

                        // 업데이트 버튼에 이벤트 리스너 추가
                        updateButton.addEventListener('click', () => {
                            // 업데이트 페이지로 리다이렉트
                            window.location.href = `/update.html?id=${articleId}`;
                        });
                    } else {
                        updateButton.style.display = 'none';
                        deleteButton.style.display = 'none';
                    }

                    // 삭제 버튼에 이벤트 리스너 추가
                    deleteButton.addEventListener('click', () => {
                        // 사용자가 정말로 게시글을 삭제하고 싶어 하는지 확인합니다.
                        const confirmDelete = confirm('정말로 게시글을 삭제하시겠습니까??');

                        if (confirmDelete) {
                            // 게시글을 삭제하기 위한 DELETE 요청 수행
                            fetch(`http://localhost:3000/article/${articleId}`, {
                                method: 'DELETE'
                            })
                            .then(response => response.json())
                            .then(data => {
                                alert("삭제 하였습니다!");
                                // 선택적으로 게시글 목록 페이지로 리다이렉트할 수 있습니다.
                                window.location.href = 'http://localhost:3000/';
                            })
                            .catch(error => {
                                console.error('게시글 삭제 오류:', error);
                                alert('게시글을 삭제하는 동안 오류가 발생했습니다. 다시 시도하세요.');
                            });
                        }
                    });
                })
                .catch(error => {
                    console.error('사용자 세션 확인 오류:', error);
                });
        })
        .catch(error => {
            console.error(`게시글 가져오기 오류: ${error.message}`);
            // ... (기존 코드 유지)
        });

    // 이전 페이지로 이동하는 함수
    function goBack() {
        window.location.href = 'http://localhost:3000/';
    }
        // 로그인 상태 확인 함수
    function checkLoginStatus() {
        fetch('/checkSession')
            .then(response => response.json())
            .then(user => {
                const submitCommentButton = document.getElementById('submit-comment');

                if (user) {
                    // 로그인 상태인 경우
                    submitCommentButton.style.display = 'inline-block';
                } else {
                    // 로그인 상태가 아닌 경우
                    submitCommentButton.style.display = 'none';
                }
            })
            .catch(error => {
                console.error('로그인 상태 확인 오류:', error);
            });
    }
   // 댓글 목록을 가져와서 표시하는 함수
function loadComments() {
    fetch(`http://localhost:3000/comments/${articleId}`)
        .then(response => response.json())
        .then(comments => {
            const commentList = document.getElementById('comment-list');
            commentList.innerHTML = '';

            fetch('/checkSession')
                .then(response => response.json())
                .then(user => {
                    if (comments.length > 0) {
                        comments.forEach(comment => {
                            const commentDiv = document.createElement('div');
                            commentDiv.classList.add('comment');

                            const userParagraph = document.createElement('p');
                            userParagraph.innerHTML = `<strong>${comment.user_id}</strong>`;
                            commentDiv.appendChild(userParagraph);

                            const textParagraph = document.createElement('p');
                            textParagraph.classList.add('comment-text');
                            textParagraph.textContent = comment.comment_text;
                            commentDiv.appendChild(textParagraph);

                            const timeParagraph = document.createElement('p');
                            timeParagraph.innerHTML = `<em>${formatCommentTime(comment.created_at)}</em>`;
                            commentDiv.appendChild(timeParagraph);

                            // Display delete button if the logged-in user is the author of the comment
                            if (user && user.username === comment.user_id) {
                                const deleteButton = document.createElement('button');
                                deleteButton.textContent = '삭제';
                                deleteButton.classList.add('action-button', 'delete-button'); // Add the 'delete-button' class
                                deleteButton.addEventListener('click', () => deleteComment(comment.comment_id));
                                commentDiv.appendChild(deleteButton);
                            }

                            commentList.appendChild(commentDiv);
                        });
                    } else {
                        commentList.innerHTML = '<p>댓글이 없습니다.</p>';
                    }
                })
                .catch(error => {
                    console.error('로그인 상태 확인 오류:', error);
                });
        })
        .catch(error => {
            console.error(`댓글 가져오기 오류: ${error.message}`);
        });
}

    //작성 시간을 한국 시간으로 형식화하는 함수
    function formatCommentTime(timeString) {
        if (!timeString) {
            return 'N/A';
        }

        const options = {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
            hour: 'numeric',
            minute: 'numeric',
        };
        const commentTime = new Date(timeString).toLocaleDateString('ko-KR', options);
        return commentTime;
    }



// 댓글 삭제 함수
function deleteComment(commentId) {
    const confirmDelete = confirm('정말로 댓글을 삭제하시겠습니까?');

    if (confirmDelete) {
        fetch(`http://localhost:3000/comment/${commentId}`, {
            method: 'DELETE'
        })
        .then(response => response.json())
        .then(data => {
            alert("댓글이 삭제되었습니다!");
            // 댓글 목록 다시 로드
            loadComments();
        })
        .catch(error => {
            console.error('댓글 삭제 오류:', error);
            alert('댓글을 삭제하는 동안 오류가 발생했습니다. 다시 시도하세요.');
        });
    }
}

     // 댓글 작성 함수
    function submitComment() {
        const commentText = document.getElementById('comment-text').value;

        if (commentText) {
            // 로그인 상태 확인
            fetch('/checkSession')
                .then(response => response.json())
                .then(user => {
                    if (user) {
                        // 로그인 상태인 경우 댓글 작성을 위한 POST 요청 수행
                        const userId = user.username;

                        fetch('http://localhost:3000/comment', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            },
                            body: JSON.stringify({
                                article_id: articleId,
                                user_id: userId,
                                comment_text: commentText,
                            }),
                        })
                        .then(response => response.json())
                        .then(data => {
                            alert('댓글이 성공적으로 작성되었습니다.');
                            // 댓글 목록 다시 로드
                            loadComments();
                        })
                        .catch(error => {
                            console.error('댓글 작성 오류:', error);
                            alert('댓글을 작성하는 동안 오류가 발생했습니다. 다시 시도하세요.');
                        });
                    } else {
                        // 로그인 상태가 아닌 경우
                        alert('로그인이 필요합니다.');
                    }
                })
                .catch(error => {
                    console.error('로그인 상태 확인 오류:', error);
                });
        } else {
            alert('댓글 내용을 입력하세요.');
        }
    }

    // 초기화 시 로그인 상태 확인 및 게시글, 댓글 목록 로드
    checkLoginStatus();
    loadComments();

</script>

</body>
</html>

 

실행화면

게시글 작성

 

상세보기

 

게시글 이미지 수정

 

댓글 작성

 

 

댓글 삭제

 

 

전체 코드는 github 에 올려두겠습니다.

 

https://github.com/phs1579/NodeBoard

728x90
반응형