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

[Node.js] 게시판[3/4] 회원가입, 로그인 추가하기

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

기존 게시판 프로젝트에 회원가입, 로그인 등 추가하겠습니다.

 

먼저 server.js 파일에 회원가입 코드를 추가하기위해 필요한 모듈을 설치하겠습니다.

npm install cookieParser
npm install bcrypt

 

설치 완료 후 server.js에 모듈을 가져옵니다.

const cookieParser = require('cookie-parser');
const bcrypt = require('bcrypt');

 

모듈을 추가 하였으면 먼저 user 정보를 담을 데이터베이스를 만들어줍니다.

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

 

server.js에 회원가입 처리를 위한 코드를 추가합니다.

// 회원가입 처리를 위한 라우트입니다.
app.post('/register', jsonParser, (req, res) => {
    const { username, password } = req.body;

    // 사용자명이 이미 사용 중인지 확인합니다.
    const checkUsernameQuery = 'SELECT * FROM users WHERE username = ?';
    connection.query(checkUsernameQuery, [username], (checkErr, checkResults) => {
        if (checkErr) {
            console.error('사용자명 가용성 확인 중 오류:', checkErr);
            return res.status(500).json({ success: false, message: '서버 오류' });
        }

        if (checkResults.length > 0) {
            return res.status(400).json({ success: false, message: '이미 사용 중인 사용자명입니다.' });
        }

        // 비밀번호를 해시하여 데이터베이스에 저장합니다.
        bcrypt.hash(password, 10, (hashErr, hashedPassword) => {
            if (hashErr) {
                console.error('비밀번호 해싱 중 오류:', hashErr);
                return res.status(500).json({ success: false, message: '서버 오류' });
            }

            // 사용자 정보를 데이터베이스에 저장합니다.
            const insertUserQuery = 'INSERT INTO users (username, password) VALUES (?, ?)';
            connection.query(insertUserQuery, [username, hashedPassword], (insertErr, insertResults) => {
                if (insertErr) {
                    console.error('데이터베이스에 사용자 삽입 중 오류:', insertErr);
                    return res.status(500).json({ success: false, message: '서버 오류' });
                }

                // 회원가입 성공
                res.json({ success: true, message: '회원가입이 완료되었습니다.' });
            });
        });
    });
});

 

그 다음 로그인기능을 추가하기 위해 로그인 코드도 추가해줍니다.

// 로그인 처리를 위한 라우트입니다.
app.post('/login', jsonParser, (req, res) => {
    const { username, password } = req.body;

    // 'users' 테이블에서 'username' 및 'password' 열이 있는 것으로 가정합니다.
    const query = 'SELECT * FROM users WHERE username = ?';

    connection.query(query, [username], (err, results) => {
        if (err) {
            console.error('데이터베이스 쿼리 중 오류:', err);
            return res.status(500).json({ success: false, message: '서버 오류' });
        }

        if (results.length === 0) {
            return res.status(401).json({ success: false, message: '사용자명이나 비밀번호가 올바르지 않습니다.' });
        }

        const user = results[0];

        // 제공된 비밀번호를 데이터베이스에 저장된 해시된 비밀번호와 비교합니다.
        bcrypt.compare(password, user.password, (bcryptErr, passwordMatch) => {
            if (bcryptErr) {
                console.error('비밀번호 비교 중 오류:', bcryptErr);
                return res.status(500).json({ success: false, message: '서버 오류' });
            }

            if (passwordMatch) {
                // 비밀번호가 일치하면 쿠키를 설정하고 성공 응답을 보냅니다.
                res.cookie('user', username).json({ success: true, message: '로그인 성공' });
                // 콘솔에 사용자 정보 기록
                console.log('로그인한 사용자:', user);
            } else {
                // 비밀번호가 일치하지 않으면 실패 응답을 보냅니다.
                res.status(401).json({ success: false, message: '사용자명이나 비밀번호가 올바르지 않습니다.' });
            }
        });
    });
});

 

 

그 다음 public 디렉터리 에 기존에 구현했던 js 파일은 모두 지워줍니다.

지운 후 public 디렉터리 에 회원가입 페이지와 로그인 페이지를 만들어줍니다.

 

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>회원가입</title>
</head>
<style>
    body {
        font-family: 'Arial', sans-serif;
        margin: 0;
        padding: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 100vh;
        background-color: #f4f4f4;
    }

    h1 {
        color: #333;
    }

    form {
        background-color: #fff;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        width: 300px;
    }

    label {
        display: block;
        margin-bottom: 8px;
        color: #333;
    }

    input {
        width: 100%;
        padding: 8px;
        margin-bottom: 16px;
        border: 1px solid #ccc;
        border-radius: 4px;
    }

    button {
        background-color: #4caf50;
        color: #fff;
        padding: 10px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    }

    button:hover {
        background-color: #45a049;
    }

    #resultMessage {
        margin-top: 16px;
        color: #333;
    }
</style>
<body>
    <h1>회원가입</h1>
    <form id="registerForm">
        <label for="username">사용자명:</label>
        <input type="text" id="username" name="username" required>
        <br>
        <label for="password">비밀번호:</label>
        <input type="password" id="password" name="password" required>
        <br>
        <button type="submit">가입하기</button>
    </form>
    <div id="registrationMessage"></div>
</body>
<script>
    document.getElementById('registerForm').addEventListener('submit', function (event) {
        event.preventDefault();

        // 사용자명과 비밀번호 가져오기
        const username = document.getElementById('username').value;
        const password = document.getElementById('password').value;

        // 서버에 가입 정보 전송
        fetch('/register', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ username, password }),
        })
        .then(response => response.json())
        .then(data => {
            const registrationMessage = document.getElementById('registrationMessage');
            if (data.success) {
                // 가입 성공 메시지 표시
                registrationMessage.innerHTML = '회원가입이 완료되었습니다. <a href="/login">로그인하러 가기</a>';
            } else {
                // 가입 실패 메시지 표시
                registrationMessage.innerHTML = '회원가입 실패: ' + data.message;
            }
        })
        .catch(error => {
            console.error('가입 중 오류:', error);
        });
    });
</script>
</html>

 

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로그인</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            background-color: #f4f4f4;
        }

        h1 {
            color: #333;
        }

        form {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 300px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            color: #333;
        }

        input {
            width: 100%;
            padding: 8px;
            margin-bottom: 16px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }

        button {
            background-color: #4caf50;
            color: #fff;
            padding: 10px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        button:hover {
            background-color: #45a049;
        }

        #resultMessage {
            margin-top: 16px;
            color: #333;
        }

    </style>
</head>
<body>
    <h1>로그인</h1>
    
    <form id="loginForm">
        <label for="username">사용자명:</label>
        <input type="text" id="username" name="username" required minlength="3">
        
        <label for="password">비밀번호:</label>
        <input type="password" id="password" name="password" required minlength="6">

        <button type="button" onclick="submitForm()">로그인</button>
    </form>

    <div id="resultMessage"></div>

    <script>
        function submitForm() {
            // 입력된 사용자명과 비밀번호 가져오기
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;

            // 사용자명과 비밀번호를 JSON 객체로 만들기
            const data = { username, password };

            // 서버에 인증을 위한 AJAX 요청
            fetch('/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data),
            })
            .then(response => response.json())
            .then(result => {
                // 결과 메시지 표시
                document.getElementById('resultMessage').textContent = result.message;

                // 로그인이 성공하면 메인 페이지로 리디렉션
                if (result.success) {
                    window.location.href = '/';
                }
            })
            .catch(error => {
                console.error('Error:', error);
                // 오류 메시지 표시
                document.getElementById('resultMessage').textContent = '서버 오류';
            });
        }
    </script>
</body>
</html>

 

다시 server.js에 가서 로그인 페이지를 제공하는 코드를 추가합니다.

// 로그인 페이지를 제공하는 라우트입니다.
app.get('/login', (req, res) => {
    res.sendFile(__dirname + '/public/login.html');
});

 

로그아웃 코드도 추가해줍니다.

// 로그아웃을 처리하는 라우트를 추가합니다.
app.get('/logout', (req, res) => {
    // 사용자가 인증되었는지 확인합니다.
    if (req.cookies.user) {
        // 사용자 쿠키를 지워 로그아웃합니다.
        res.clearCookie('user').json({ success: true, message: '로그아웃 성공' });
    } else {
        // 사용자가 인증되지 않았으면 오류 응답을 보냅니다.
        res.status(401).json({ success: false, message: '인증되지 않음' });
    }
});

 

메인 HTML 파일을 제공하는 코드를 로그인하였을때 쿠키정보를 보내기 위해 코드를 수정해줍니다.

// 메인 HTML 파일을 제공하는 라우트입니다.
app.get('/', (req, res) => {
    const { user } = req.cookies;
    if (user) {
        res.sendFile(__dirname + '/public/index.html');
    } else {
        res.redirect('/login');
    }
});

 

이제 글쓰기, 글수정, 글삭제 코드를 변경하기 위해서 server.js 에 로그인 하였을때 세션정보를 확인하기 위한 코드를 추가합니다.

// 사용자 세션을 확인하는 라우트를 추가합니다.
app.get('/checkSession', (req, res) => {
    // req.cookies가 정의되어 있는지 확인합니다.
    if (req.cookies && req.cookies.user) {
        // 정의되어 있으면 사용자 정보를 보냅니다.
        res.json({ username: req.cookies.user });
    } else {
        // 정의되어 있지 않거나 사용자가 없으면 null을 보냅니다.
        res.json(null);
    }
});

 

MySQL에 가서 board 테이블을 수정합니다.

writer 를 user_id 로 수정해줍니다.

 

게시글을 작성하는 코드를 변경해줍니다.

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

    // 변경된 부분: user_id 정보를 req.body.user_id에서 가져옵니다.
    const user_id = req.body.user_id;

    const content = req.body.content;
    const params = [title, user_id, content];

    // 주어진 매개변수로 쿼리를 실행합니다.
    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 디렉터리에 update.html 코드도 수정해줍니다.

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

    <style>
        /* 페이지의 스타일을 지정하는 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;
        }

        h1 a {
            text-decoration: none;
            color: #333;
        }

        h1 a:hover {
            color: #555;
        }
    </style>
</head>

<body>
    <!-- 페이지 상단에 표시되는 제목입니다. -->
    <h1>게시글 수정</h1>

    <!-- 게시글 수정 폼입니다. -->
    <form id="update-form">
        <!-- 게시글 ID를 감추어서 전송합니다. -->
        <input hidden id="id" name="id">
        
        <!-- 제목 입력란 -->
        <label for="title">제목:</label>
        <input type="text" id="title" name="title" required><br>
        
        <!-- 내용 입력란 -->
        <label for="content">내용:</label>
        <textarea id="content" name="content" required></textarea><br>

        <!-- 게시글 수정 버튼 -->
        <button onclick="updateArticle()">게시글 수정</button>

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

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

        // articleId가 null이 아닌 경우에만 fetch 요청을 보냅니다.
        if (articleId !== null) {
            // 게시글 상세 정보를 가져와서 폼에 미리 채웁니다.
            fetch(`http://localhost:3000/articles/${articleId}`)
                .then(response => response.json())
                .then(article => {
                    // 폼 필드에 기존 데이터로 채웁니다.
                    document.getElementById('id').value = article.idx;
                    document.getElementById('title').value = article.title;
                    document.getElementById('content').value = article.content;
                })
                .catch(error => {
                    console.error('게시글 정보를 가져오는 중 오류 발생:', error);
                });
        } else {
            console.error('게시글 ID를 찾을 수 없습니다.');
        }

        // 게시글 수정 버튼을 눌렀을 때 해당 게시글 상세보기 페이지로 이동하는 함수
        function updateArticle() {
            const title = document.getElementById('title').value;
            const content = document.getElementById('content').value;

            // PUT 요청을 사용하여 게시글 업데이트
            fetch(`http://localhost:3000/article/${articleId}`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ title, content }),
            })
            .then(response => response.json())
            .then(result => {
                console.log('게시글이 성공적으로 업데이트되었습니다.', result);
                
                // 게시글 상세보기 페이지로 이동
                const articleDetailsURL = `http://localhost:3000/article-details.html?id=${articleId}`;
                alert("게시글이 수정되었습니다!")
                window.location.replace(articleDetailsURL);
            })
            .catch(error => {
                console.error('게시글 업데이트 중 오류 발생:', error);
            });
        }

        // 뒤로가기 버튼을 눌렀을 때 이전 페이지로 이동하는 함수
        function goBack() {
            window.history.back();
        }
    </script>

</body>
</html>

public 디렉터리에 index.html 에서 비로그인 일때 Login, SignUp 버튼을 보여주고 로그인 하면 로그인 아이디, Logout 버튼이 나오게

수정합니다.

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- 페이지의 메타 정보를 설정합니다. -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

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

        h1 {
            background-color: #343a40;
            color: #ffffff;
            padding: 20px;
            margin: 0;
        }

        nav ul {
            list-style: none;
            padding: 0;
            margin: 0;
            background-color: #343a40;
            overflow: hidden;
        }

        nav ul li {
            float: left;
            display: inline;
            padding: 10px;
        }

        nav ul li a {
            text-decoration: none;
            color: #ffffff;
            font-weight: bold;
            display: inline-block;
            padding: 10px 20px;
            background-color: #343a40;
            transition: background-color 0.3s ease;
        }

        nav ul li a:hover {
            background-color: #495057;
        }

        #articles-list {
            list-style: none;
            padding: 0;
            margin: 20px;
        }

        #articles-list li {
            background-color: #ffffff;
            border: 1px solid #ced4da;
            padding: 15px;
            margin-bottom: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        #articles-list li a {
            text-decoration: none;
            color: #343a40;
            font-weight: bold;
        }

        #articles-list li a:hover {
            color: #007bff;
        }

         /* 페이징 컨트롤의 스타일을 설정합니다. */
        .pagination {
            display: inline-block;
            margin-top: 20px;
        }

        .pagination li {
            display: inline;
            margin-right: 5px;
        }

        .pagination a {
            color: #007bff;
            padding: 5px 10px;
            text-decoration: none;
            border: 1px solid #007bff;
            border-radius: 3px;
        }

        .pagination .active a {
            background-color: #007bff;
            color: #fff;
        }

        .pagination a:hover {
            background-color: #0056b3;
            color: #fff;
        }

        .pagination .disabled a {
            pointer-events: none;
            cursor: not-allowed;
            background-color: #ccc;
            color: #777;
        }

        #userInfo {
            margin-top: 0px;
            padding: 15px; /* Increase the padding to make the background color larger */
            background-color: #495057;
            color: #fff;
            text-align: right;
        }

        #userInfo a {
            color: #fff;
            text-decoration: underline;
            cursor: pointer;
            margin-left: 10px;
        }

    </style>
</head>
<body>
    <!-- 페이지의 제목을 설정합니다. -->
    <title>게시글 목록</title>
    <!-- 페이지 상단에 표시되는 제목입니다. -->
    <h1>게시글 목록</h1> 
    <div id="userInfo"></div>
    <!-- 네비게이션 링크 -->
    <nav>
        <ul>
            <!-- 게시글 작성 페이지로 이동하는 링크 -->
            <li><a href="#" onclick="handleCreateClick()">게시글 작성</a></li>
        </ul>
    </nav>

    <!-- 게시글 목록을 표시하는 부분입니다. -->
    <ul id="articles-list"></ul>

    <!-- Pagination controls -->
    <div id="pagination" class="col-8">
        <ul class="pagination pagination-sm justify-content-center align-items-center h-100 mb-0" id="pageList"></ul>
    </div>

    <!-- JavaScript 코드입니다. -->
    <script>
        // 변수 초기화
        const articlesList = document.getElementById('articles-list'); // HTML에서 게시글 목록을 표시할 엘리먼트
        const pageListElement = document.getElementById('pageList'); // HTML에서 페이지 목록을 표시할 엘리먼트
        let currentPage = 1; // 현재 페이지 변수 초기화
        let maxPages = 1; // 최대 페이지 수를 저장하는 변수 초기화

        // 함수: 게시글 목록을 가져오는 함수
        function fetchArticles(page) {
            const pageSize = 5; // 페이지당 표시할 게시글 수 (원하는 값으로 조절)
            const startIndex = (page - 1) * pageSize; // 가져올 게시글의 시작 인덱스

            // 서버로부터 게시글 데이터 가져오기
            fetch(`http://localhost:3000/articles?page=${page}`)
                .then(response => response.json())
                .then(data => {
                    const articles = data.rows; // 서버에서 받아온 게시글 데이터
                    articlesList.innerHTML = ''; // 이전에 표시된 게시글 초기화

                    // 각 게시글을 리스트에 추가
                    articles.forEach(article => {
                        const li = document.createElement('li');
                        li.innerHTML = `
                            <strong>Title:</strong> ${article.title}<br>
                            <strong>Writer:</strong> ${article.user_id}<br>
                            <strong>Content:</strong> ${article.content}<br>
                            <strong>Views:</strong> ${article.view_cnt}<br>
                            <strong>Insert Time:</strong> ${article.insert_time}<br>
                            <strong>Update Time:</strong> ${article.update_time ? article.update_time : 'N/A'}<br>
                            <strong>Delete Time:</strong> ${article.delete_time ? article.delete_time : 'N/A'}<br>
                            <a href="article-details.html?id=${article.idx}">상세 정보 보기</a><br>
                            <hr>
                        `;
                        articlesList.appendChild(li);
                    });

                    // 전체 페이지 수 업데이트
                    maxPages = data.totalPages;

                    // 페이지 목록 업데이트
                    renderPaginationControls(data.page_current, data.page_max);
                });
        }

        // 사용자 세션 확인 및 사용자 정보 표시
        fetch('/checkSession')
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                return response.json();
            })
            .then(user => {
                const userInfoElement = document.getElementById('userInfo');
                if (user) {
                    userInfoElement.innerHTML = `Logged in as: ${user.username} | <a href="#" onclick="logout()">Logout</a>`;
                } else {
                    userInfoElement.innerHTML = '<a href="/login">Login</a>';
                    userInfoElement.innerHTML += '<a href="/register.html">Sign Up</a>';
                }
            })
            .catch(error => {
                console.error('Error checking user session:', error);
            });

        // 게시글 작성 버튼 클릭 처리
        function handleCreateClick() {
            fetch('/checkSession')
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(user => {
                    if (user) {
                        // 사용자가 로그인한 경우, create.html 페이지로 이동
                        window.location.href = 'create.html';
                    } else {
                        // 사용자가 로그인하지 않은 경우, 경고 메시지 표시 후 로그인 페이지로 이동
                        alert('로그인이 필요합니다.');
                        window.location.href = '/login';
                    }
                })
                .catch(error => {
                    console.error('Error checking user session:', error);
                });
        }

        // 함수: 페이지 변경 이벤트 처리
        function changePage(page) {
            // 페이지가 1보다 작아지지 않도록 보장
            currentPage = Math.max(page, 1);
            // 페이지가 최대 페이지 수를 초과하지 않도록 보장
            currentPage = Math.min(currentPage, maxPages);

            // 페이지를 업데이트하고 게시글을 가져옵니다.
            fetchArticles(currentPage);
        }

        // 함수: 페이지 목록 렌더링
        function renderPaginationControls(page_current, page_max) {
            // 페이지 목록 엘리먼트를 찾지 못한 경우 에러를 방지하기 위해 확인
            if (!pageListElement) {
                console.error("Error in renderPaginationControls: Unable to find the 'pageList' element on the page.");
                return;
            }

            // 이전 페이지 목록 초기화
            pageListElement.innerHTML = '';

            const offset = 2;
            const previousBtnEnabled = page_current > 1; // 이전 버튼 활성화 여부
            const nextBtnEnabled = page_current < page_max; // 다음 버튼 활성화 여부

            // "이전" 버튼 추가
            pageListElement.innerHTML += `
                <li class="page-item ${previousBtnEnabled ? '' : 'disabled'}">
                    <a class="page-link" href="#" onclick="changePage(${page_current - 1})" ${previousBtnEnabled ? '' : 'tabindex=-1'}>«</a>
                </li>
            `;

            // 페이지 번호 추가
            for (let i = 1; i <= page_max; i++) {
                if (i == 1 || i == page_max || (i >= page_current - offset && i <= page_current + offset)) {
                    pageListElement.innerHTML += `
                        <li class="page-item ${page_current == i ? 'active' : ''}">
                            <a class="page-link" href="#" onclick="changePage(${i})">${i}</a>
                        </li>
                    `;
                } else if (i == 2 || i == page_max - 1) {
                    pageListElement.innerHTML += `
                        <li><a class="page-link">...</a></li>
                    `;
                }
            }

            // "다음" 버튼 추가
            pageListElement.innerHTML += `
                <li class="page-item ${nextBtnEnabled ? '' : 'disabled'}">
                    <a class="page-link" href="#" onclick="changePage(${page_current + 1})" ${nextBtnEnabled ? '' : 'tabindex=-1'}>»</a>
                </li>
            `;
        }

        // 초기 페이지 로딩 시 첫 번째 페이지의 게시글 가져오기
        fetchArticles(1);

        // 함수: 로그아웃 처리
        function logout() {
            fetch('/logout')
                .then(response => response.json())
                .then(result => {
                    if (result.success) {
                        // 성공적인 로그아웃 후 홈 페이지로 리다이렉트
                        window.location.href = '/';
                    } else {
                        console.error('Logout failed:', result.message);
                    }
                })
                .catch(error => {
                    console.error('Error during logout:', error);
                });
        }
    </script>
</body>
</html>

 

 

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;
        }
    </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 update_time = article.update_time ? article.update_time : 'N/A';

                articleDetails.innerHTML = `
                    <strong>Title:</strong> ${article.title}<br>
                    <strong>Writer:</strong> ${article.user_id}<br>
                    <strong>Content:</strong> ${article.content}<br>
                    <strong>Views:</strong> ${article.view_cnt}<br>
                    <strong>Insert Time:</strong> ${article.insert_time}<br>
                    <strong>Update Time:</strong> ${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/';
    }
</script>

</body>
</html>

 

 

실행 화면

비로그인 시 index.html 화면

 

로그인 시 index.html 화면

 

게시글 작성자가 로그인한 유저 라면 article-details.html 화면

 

게시글 작성자가 로그인한 유저가 아닐때 article-details.html 화면

 

회원가입 화면

 

회원가입 했을때 화면

중복 아이디 로 가입 했을때 화면

로그인 화면

 

전체코드는 깃허브에 올려두겠습니다.

https://github.com/phs1579/NodeBoard 를 참조해주세요.

728x90
반응형