본문 바로가기

Projects

[AWS Final Project] S3와 DynamoDB를 활용하여 Serverless 게시판 만들기 - 1편 (개발환경 세팅, 소스코드 이해)

 

위와 같은 흐름을 따라 게시판 기능을 하는 웹페이지를 만들어볼 것이다. 1편에서는 개발환경 세팅과 소스코드를 이해하는 데 초점을 맞췄다. 엄밀히 말하면 프론트엔드를 구현하는 파트라고 볼 수 있다. AWS 콘솔환경에서 다룰 백엔드 파트는 2편에서 다뤄보겠다.

 

 

요구사항 분석

우리는 아래와 같은 게시판 페이지를 만들고 싶은 상황이다.

 

게시물 등록 (article_add.html)

  • 사용자가 게시물 제목, 내용, 사진을 입력
  • 사진이 S3에 업로드되고, 게시물 정보는 DynamoDB에 저장
  • 게시물 작성이 완료되면 게시물 목록 페이지로 리다이렉트

게시물 목록 조회 (article_view.html)

  • DynamoDB에서 모든 게시물을 불러와 목록 형태로 화면에 출력
  • 게시물 제목을 클릭하면 해당 게시물의 상세 페이지로 이동

게시물 상세 조회 및 삭제 (article_detail.html)

  • 특정 게시물을 조회하여 제목, 내용, 첨부된 이미지를 출력
  • 사용자가 직접 작성한 게시물은 삭제할 수 있음

 

소스코드

<!--article_add.html-->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>글 등록 페이지</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>
    <script src="./js/s3_photoExample.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <div style="text-align: center;"><h1>글 등록 페이지</h1></div>
        <form name="article_add_form" method="POST">
            <table>
                <tr>
                    <th>Title</th>
                    <td><input type="text" id="title" required></td>
                </tr>
                <tr>
                    <th>Content</th>
                    <td><input type="text" id="content" required></td>
                </tr>
                <tr>
                    <th>사진</th>
                    <td><input type="file" id="article_image" name="filename"></td>
                </tr>
                <tr>
                    <td colspan="3" style="text-align: center;">
                        <button type="button" onclick="submitToAPI(event)">등록</button>
                        <button type="button" onclick="cancelAndGoBack()">취소</button>
                    </td>
                </tr>
            </table>
        </form>
    </div>

    <script>
        function submitToAPI(e) {
            e.preventDefault();
            add_article_with_photo('images', function() {
                window.location.href = 'article_view.html';  // 등록 후 게시물 목록 페이지로 이동
            });
        }

        function cancelAndGoBack() {
            window.location.href = 'article_view.html';  // 게시물 보기 페이지로 이동
        }
    </script>
</body>
</html>

 

 

<!--article_detail.html-->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>게시물 상세 보기</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>
    <script src="./js/s3_photoExample.js"></script>
</head>
<body onload="getArticleDetail()">
    <div class="container">
        <h1>게시물 상세 보기</h1>
        <div id="article-detail">
            <div class="article-box article-title-box">
                <h2 id="article-title"></h2>
            </div>
            <div class="article-box article-content-box">
                <img id="article-image" src="" alt="게시물 이미지">
                <p id="article-content"></p>
            </div>
        </div>
        <button onclick="goToArticleList()">목록으로</button>
        <button id="delete-button" onclick="deleteArticle()">삭제</button>

    </div>

    <script>
        const URL = "https://8uetkgzthk.execute-api.ap-northeast-2.amazonaws.com/2024-08-26/article_resource";

        function getArticleDetail() {
            const params = new URLSearchParams(window.location.search);
            const article_id = params.get('id');
            
            if (article_id) {
                fetch(URL + '?article_id=' + article_id, {
                    method: "GET",
                    headers: {
                        'Accept': 'application/json'
                    }
                })
                .then(resp => resp.json())
                .then(function(data) {
                    let article = data.Item;
                    if (!article) {
                        alert('게시글을 찾을 수 없습니다.');
                        return;
                    }
        
                    document.getElementById('article-title').textContent = article.title;
                    document.getElementById('article-content').textContent = article.content;
                    document.getElementById('article-image').src = article.img_source;
                })
                .catch(err => console.log(err));
            } else {
                alert('Invalid article ID');
            }
        }

        function goToArticleList() {
            window.location.href = 'article_view.html';  // 게시물 목록 페이지로 이동
        }

        function deleteArticle() {
    const params = new URLSearchParams(window.location.search);
    const article_id = params.get('id');
    
    if (!article_id) return alert("게시물 ID가 유효하지 않습니다.");

    // 게시물 정보 가져오기
    fetch(URL + '?article_id=' + article_id)
        .then(resp => resp.json())
        .then(function(data) {
            const article = data.Item;
            if (!article) {
                alert('게시글을 찾을 수 없습니다.');
                return;
            }

            // 현재 로그인한 사용자의 이름 가져오기
            const currentUser = localStorage.getItem('username');

            // 작성자와 현재 사용자 비교
            if (article.author === currentUser) {
                // DELETE 요청 - body에 article_id를 포함하여 JSON 문자열로 전송
                fetch(URL, {
                    method: 'DELETE',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ 
                        TableName: "simple_board",
                        Key: { "article_id": article_id }
                    })
                })
                .then(resp => {
                    if (resp.ok) {
                        alert('게시물이 삭제되었습니다.');
                        window.location.href = 'article_view.html';  // 목록으로 돌아가기
                    } else {
                        resp.json().then(data => alert('삭제 실패: ' + data.message));
                    }
                })
                .catch(err => console.log('Error:', err));
            } else {
                alert('본인의 게시물만 삭제할 수 있습니다.');
            }
        })
        .catch(err => console.log(err));
}


    </script>
</body>
</html>

 

 

<!--article_view.html-->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>게시물 보기 페이지</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>
    <script src="./js/s3_photoExample.js"></script>
</head>
<body onload="getArticles()">
    <header class="header">
        <div class="logo-nav">
            <a href="../main_page/index.html" class="logo">Hama Logo</a>
            <nav class="nav">
                <a href="../bulletin/article_view.html" class="nav-link">community</a>
                <a href="#" class="nav-link">news</a>
                <a href="#" class="nav-link">guide</a>
            </nav>
        </div>
        <button class="btn" id="userDataButton" onClick="goToUserData()"></button>
    </header>
    <div class="container">
        <h1>게시물 목록</h1>
        <ul id="articles"></ul>

        <div id="pagination"></div>
        
        <!-- 검색창과 검색 버튼 -->
        <div class="search-container">
            <input type="text" id="search-input" placeholder="검색어를 입력하세요...">
            <button id="search-button" onclick="filterArticles()">검색</button>
        </div>

        <button id="register-button" onclick="goToArticleAddPage()">등록</button>
    </div>

    <script>
        let article_arr = [];

        const URL = "https://8uetkgzthk.execute-api.ap-northeast-2.amazonaws.com/2024-08-26/article_resource";

        function getArticles() {
            fetch(URL, {
                method: "GET",
                headers: {
                    'Accept': 'application/json'
                }
            })
            .then(resp => resp.json())
            .then(function(data) {
                article_arr = data.Items;

                // timestamp 기준으로 최신순으로 정렬
                article_arr.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));

                displayArticles(article_arr);
            })
            .catch(err => console.log(err));
        }

        function displayArticles(articles) {
            const articlesList = document.getElementById('articles');
            articlesList.innerHTML = '';

            articles.forEach(function(article) {
                let li = document.createElement('li'); 
                li.innerHTML = `
                    <h3>${article.title}</h3>
                    <p>작성자: ${article.author}</p>
                    <p>작성 시간: ${new Date(article.timestamp).toLocaleString()}</p>
                `;
                li.onclick = function() {
                    viewArticleDetail(article.article_id);
                };
                articlesList.appendChild(li);
            });
        }

        function filterArticles() {
            const searchInput = document.getElementById('search-input').value.toLowerCase();
            const filteredArticles = article_arr.filter(article => 
                article.title.toLowerCase().includes(searchInput) || 
                article.content.toLowerCase().includes(searchInput)
            );
            displayArticles(filteredArticles);
        }

        function goToArticleAddPage() {
            window.location.href = 'article_add.html';  // 게시물 등록 페이지로 이동
        }

        function viewArticleDetail(article_id) {
            window.location.href = 'article_detail.html?id=' + article_id;  // 게시물 상세 보기 페이지로 이동
        }

    </script>
</body>
</html>

 

 

// s3_photoExample.js

var albumBucketName = "hama-bulletin";
var bucketRegion = "ap-northeast-2";
var IdentityPoolId = "ap-northeast-2:9d74a2fa-0a5b-4206-948d-54e6082933d4";
let currentPage = 1;
const articlesPerPage = 10;

function getArticles() {
    fetch(URL, {
        method: "GET",
        headers: {
            'Accept': 'application/json'
        }
    })
    .then(resp => resp.json())
    .then(function(data) {
        article_arr = data.Items;

        // timestamp 기준으로 최신순으로 정렬
        article_arr.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));

        displayArticles(article_arr, currentPage);
    })
    .catch(err => console.log(err));
}

function displayArticles(articles, page) {
    const articlesList = document.getElementById('articles');
    articlesList.innerHTML = '';

    const startIndex = (page - 1) * articlesPerPage;
    const endIndex = startIndex + articlesPerPage;
    const paginatedArticles = articles.slice(startIndex, endIndex);

    paginatedArticles.forEach(function(article) {
        let li = document.createElement('li'); 
        li.innerHTML = `
            <h3>${article.title}</h3>
            <p>작성자: ${article.author}</p>
            <p>작성 시간: ${new Date(article.timestamp).toLocaleString()}</p>
        `;
        li.onclick = function() {
            viewArticleDetail(article.article_id);
        };
        articlesList.appendChild(li);
    });

    // 페이지네이션 업데이트
    updatePagination(articles.length, page);
}

function updatePagination(totalArticles, currentPage) {
    const pagination = document.getElementById('pagination');
    pagination.innerHTML = '';

    const totalPages = Math.ceil(totalArticles / articlesPerPage);

    for (let i = 1; i <= totalPages; i++) {
        let button = document.createElement('button');
        button.textContent = i;
        button.className = i === currentPage ? 'active' : '';

        button.onclick = function() {
            changePage(i);
        };

        pagination.appendChild(button);
    }
}

function changePage(page) {
    currentPage = page;
    displayArticles(article_arr, currentPage);
}

function filterArticles() {
    const searchInput = document.getElementById('search-input').value.toLowerCase();
    const filteredArticles = article_arr.filter(article => 
        article.title.toLowerCase().includes(searchInput) || 
        article.content.toLowerCase().includes(searchInput)
    );
    currentPage = 1; // 검색 시 첫 페이지로 이동
    displayArticles(filteredArticles, currentPage);
}

 
AWS.config.update({
  region: bucketRegion,
  credentials: new AWS.CognitoIdentityCredentials({
    IdentityPoolId: IdentityPoolId
  })
});
 
var s3 = new AWS.S3({
  apiVersion: "2006-03-01",
  params: { Bucket: albumBucketName }
});
 
// UUID 생성 함수 추가
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

// db에 정보 올리는 함수
function upload_to_db(img_location) {
    var article_id = uuidv4();  // UUID로 article_id 자동 생성
    var article_title = document.querySelector("#title").value;
    var article_content = document.querySelector("#content").value;

    var username = localStorage.getItem('username');

    var Item = {
        'article_id': article_id,
        'title': article_title,
        'content': article_content,
        'img_source': img_location,
        'timestamp': new Date().toISOString(), // 현재 시간을 ISO 포맷으로 추가
        'author': username  // 작성자 정보 추가
    };
    console.log(Item);

    const URL = "https://8uetkgzthk.execute-api.ap-northeast-2.amazonaws.com/2024-08-26/article_resource";

    fetch(URL, {
        method: "POST",
        headers: {
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            "TableName": "simple_board",
            Item
        })
    }).then(resp => console.log(resp))
      .catch(err => console.log(err));
}

 
function add_article_with_photo(albumName, callback) {
    var files = document.getElementById("article_image").files;
    if (!files.length) {
        return alert("Please choose a file to upload first.");
    }
    var file = files[0];
    var fileName = file.name;
    var albumPhotosKey = encodeURIComponent(albumName) + "/";
 
    var photoKey = albumPhotosKey + fileName;
 
    var upload = new AWS.S3.ManagedUpload({
        params: {
            Bucket: albumBucketName,
            Key: photoKey,
            Body: file
        }
    });
 
    var promise = upload.promise();
 
    promise.then(
        function(data) {
            let img_location = data.Location;
            upload_to_db(img_location);

            alert("Successfully uploaded photo.");
            if (callback) callback();  // 콜백 함수 호출
        },
        function(err) {
            console.log(err);
            alert("There was an error uploading your photo: " + err.message);
        }
    );
}

 

 

 

S3 및 DynamoDB와 연동 (s3_photoExample.js)

 

var albumBucketName = "hama-bulletin";
var bucketRegion = "ap-northeast-2";
var IdentityPoolId = "ap-northeast-2:*******************************";
let currentPage = 1;
const articlesPerPage = 10;

 

  • albumBucketName: 이미지를 저장할 장소로 사용될 S3 버킷의 이름 
  • bucketRegion: S3 버킷이 위치한 AWS 리전(region)
  • IdentityPoolId: AWS Cognito에서 제공하는 인증 풀 ID로, 이 ID를 통해 익명의 사용자도 AWS 리소스에 접근할 수 있도록 인증을 받음(Amazon Cognito 콘솔탭의 자격 증명 풀에서 확인할 수 있다)

 

AWS.config.update({
    region: bucketRegion,
    credentials: new AWS.CognitoIdentityCredentials({
        IdentityPoolId: IdentityPoolId
    })
});

 

 

 

  • AWS.config.update: AWS SDK의 설정을 업데이트하여 Cognito 자격 증명을 사용해 AWS 리소스에 접근할 수 있도록 구성

 

var s3 = new AWS.S3({
    apiVersion: "2006-03-01",
    params: { Bucket: albumBucketName }
});

 

 

  • s3: S3 객체를 생성. 이 객체는 파일 업로드 및 S3 버킷과의 상호작용을 위해 사용된다. 여기서는 API 버전과 버킷 이름을 지정했다.

 

function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

 

  • uuidv4: 고유한 ID를 생성하는 함수. 게시물이나 데이터의 고유 식별자로 사용된다. 각 글마다 고유한 article_id가 부여된다.

 

function upload_to_db(img_location) {
    var article_id = uuidv4();  // UUID로 article_id 자동 생성
    var article_title = document.querySelector("#title").value;
    var article_content = document.querySelector("#content").value;

    var username = localStorage.getItem('username');  // 로컬 스토리지에서 사용자의 이름을 가져옴

 

 

  • upload_to_db: 이 함수는 사용자로부터 입력받은 게시물의 제목(title), 내용(content), 이미지의 S3 URL을 DynamoDB에 저장하는 역할을 한다.
  • article_id: 위에서 정의한 uuidv4 함수를 사용해 고유 ID를 생성한다.
  • article_title, article_content: HTML 폼에서 제목과 내용을 가져온다.
  • username: 로컬 스토리지에 저장된 사용자 이름을 가져온다.

 

    var Item = {
        'article_id': article_id,
        'title': article_title,
        'content': article_content,
        'img_source': img_location,
        'timestamp': new Date().toISOString(), // 현재 시간을 ISO 포맷으로 추가
        'author': username  // 작성자 정보 추가
    };
    console.log(Item);

 

 

  • Item: 게시물의 데이터를 객체로 구성. 여기에는 article_id, title, content, 이미지 URL(img_source), 작성 시간(timestamp), 작성자 정보(author)가 포함된다.

 

const URL = "https://8uetkgzthk.execute-api.ap-northeast-2.amazonaws.com/2024-08-26/article_resource";
  
      fetch(URL, {
          method: "POST",
          headers: {
              'Accept': 'application/json'
          },
          body: JSON.stringify({
              "TableName": "simple_board",
              Item
          })
      }).then(resp => console.log(resp))
        .catch(err => console.log(err));
  }

 

 

  • fetch: API Gateway를 통해 DynamoDB에 POST 요청을 보내는 코드. 게시물 정보를 Item 객체에 담아 전송한다. URL은 API Gateway의 엔드포인트.
  • Item 데이터가 JSON 형식으로 DynamoDB에 전송된다.

 

function add_article_with_photo(albumName, callback) {
    var files = document.getElementById("article_image").files;
    if (!files.length) {
        return alert("Please choose a file to upload first.");
    }
    var file = files[0];
    var fileName = file.name;
    var albumPhotosKey = encodeURIComponent(albumName) + "/";
 
    var photoKey = albumPhotosKey + fileName;

 

 

 

  • add_article_with_photo: 이미지 파일을 S3에 업로드하고, 업로드가 완료되면 콜백을 통해 DynamoDB에 저장하는 함수
  • files: 사용자가 업로드한 파일을 가져온다. 파일이 없으면 경고 메시지를 출력.
  • albumName: 이미지가 저장될 S3의 폴더(앨범) 이름을 지정
  • photoKey: 이미지가 저장될 S3의 경로

 

    var upload = new AWS.S3.ManagedUpload({
        params: {
            Bucket: albumBucketName,
            Key: photoKey,
            Body: file
        }
    });

 

 

  • upload: AWS.S3.ManagedUpload 객체를 생성해 S3에 이미지를 업로드. Bucket은 S3 버킷 이름, Key는 저장될 파일 경로, Body는 업로드할 파일의 내용을 가리킨다.

 

    var promise = upload.promise();
 
    promise.then(
        function(data) {
            let img_location = data.Location;
            upload_to_db(img_location);  // 이미지가 업로드되면 그 위치를 DB에 저장

            alert("Successfully uploaded photo.");
            if (callback) callback();  // 콜백 함수 호출
        },
        function(err) {
            console.log(err);
            alert("There was an error uploading your photo: " + err.message);
        }
    );
}

 

 

  • promise: 이미지를 S3에 업로드하는 비동기 작업을 처리. 업로드가 성공하면 data.Location에 이미지의 URL이 담기고, 이를 upload_to_db 함수로 전달하여 DynamoDB에 저장한다.
  • 콜백 함수는 성공 시 호출되며, 업로드 완료 후 게시물 등록 등 후속 작업을 처리할 수 있다.

 

 

게시물 업로드 구현 흐름 (article_add.html)

게시물을 업로드하는 페이지다. 아래와 같은 페이지가 보일 것이다.

 

구현과정에 대해 자세히 알아보자.

 

function submitToAPI(e) {
            e.preventDefault();
            add_article_with_photo('images', function() {
                window.location.href = 'article_view.html';  // 등록 후 게시물 목록 페이지로 이동
            });
        }

 

 

  • submitToAPI(e): 게시물 등록 버튼을 누르면 호출된다. add_article_with_photo 함수를 사용해 S3에 이미지를 업로드한 후, 글을 등록하고 article_view.html로 이동한다.
  • add_article_with_photo(albumName, callback): 파일 업로드 후 S3에 저장된 이미지 링크를 가져오고, upload_to_db(img_location)를 통해 DynamoDB에 게시물 정보를 저장한다.

 

 

게시물 보기 구현 흐름 (article_detail.html)

 

보고 싶은 게시물을 눌러보면 아래와 같이 게시물 내용을 확인할 수 있다.

 

 

 

 

 

 

이제 구현 흐름에 대해 알아보자.

 

getArticleDetail 함수는 현재 페이지의 URL에서 게시물 ID를 추출하고, 해당 ID를 이용해 게시물의 상세 정보를 서버에서 가져와 화면에 표시하는 기능을 한다.

function getArticleDetail() {
    // 1. URL에서 쿼리 파라미터 추출
    const params = new URLSearchParams(window.location.search);
    const article_id = params.get('id');
    
    // 2. 게시물 ID가 존재할 경우에만 API 호출
    if (article_id) {
        // 3. 서버에서 GET 요청으로 게시물 정보 가져오기
        fetch(URL + '?article_id=' + article_id, {
            method: "GET",
            headers: {
                'Accept': 'application/json'
            }
        })
        .then(resp => resp.json())  // 서버 응답을 JSON으로 변환
        .then(function(data) {
            let article = data.Item;  // 응답 데이터에서 게시물 정보 추출
            if (!article) {
                alert('게시글을 찾을 수 없습니다.');  // 게시물 정보가 없을 경우 알림
                return;
            }

            // 4. 가져온 게시물 정보를 HTML 요소에 표시
            document.getElementById('article-title').textContent = article.title;
            document.getElementById('article-content').textContent = article.content;
            document.getElementById('article-image').src = article.img_source;
        })
        .catch(err => console.log(err));  // 오류 발생 시 콘솔에 로그
    } else {
        alert('Invalid article ID');  // 게시물 ID가 없는 경우 알림
    }
}

 

 

  • window.location.search는 URL에서 쿼리 문자열을 가져오고, 이를 통해 게시물 ID를 추출
  • article_id가 존재하는지 확인한 후, 서버에 GET 요청을 보낸다.
  • 서버에서 응답을 받은 후 JSON 형태로 변환한다. 게시물이 존재하지 않으면 사용자에게 알림을 보여준다.
  • 정상적으로 데이터를 받아오면, HTML 요소(article-title, article-content, article-image)에 데이터를 출력한다.

 

 

 

 

deleteArticle 함수는 게시물을 삭제하는 기능을 수행한다. 게시물을 작성한 사용자만 삭제할 수 있으며, 삭제 요청을 서버로 전송한다.

function deleteArticle() {
    // 1. URL에서 게시물 ID 추출
    const params = new URLSearchParams(window.location.search);
    const article_id = params.get('id');
    
    // 2. 게시물 ID가 유효하지 않으면 알림 표시
    if (!article_id) return alert("게시물 ID가 유효하지 않습니다.");

    // 3. 서버에서 해당 게시물 정보 가져오기
    fetch(URL + '?article_id=' + article_id)
        .then(resp => resp.json())  // 응답을 JSON으로 변환
        .then(function(data) {
            const article = data.Item;
            if (!article) {
                alert('게시글을 찾을 수 없습니다.');  // 게시물이 없을 경우 알림
                return;
            }

            // 4. 로컬 스토리지에서 현재 로그인한 사용자의 이름 가져오기
            const currentUser = localStorage.getItem('username');

            // 5. 작성자와 현재 사용자가 동일한지 확인
            if (article.author === currentUser) {
                // 6. DELETE 요청 보내기
                fetch(URL, {
                    method: 'DELETE',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ 
                        TableName: "simple_board",
                        Key: { "article_id": article_id }
                    })
                })
                .then(resp => {
                    if (resp.ok) {
                        alert('게시물이 삭제되었습니다.');  // 삭제 성공 알림
                        window.location.href = 'article_view.html';  // 목록 페이지로 이동
                    } else {
                        resp.json().then(data => alert('삭제 실패: ' + data.message));  // 실패 시 메시지 표시
                    }
                })
                .catch(err => console.log('Error:', err));  // 오류 처리
            } else {
                alert('본인의 게시물만 삭제할 수 있습니다.');  // 작성자가 아닌 경우 알림
            }
        })
        .catch(err => console.log(err));  // 오류 처리
}

 

 

게시물 목록 구현 흐름 (article_view.html)

 

게시물 목록에 대한 페이지를 보면 글 제목, 작성자, 작성시간이 보인다.

 

 

 

getArticles()함수는 DynamoDB에서 게시물 목록을 가져다주는 역할을 한다.

function getArticles() {
    fetch(URL, {
        method: "GET",
        headers: {
            'Accept': 'application/json'
        }
    })
    .then(resp => resp.json())
    .then(function(data) {
        article_arr = data.Items;

        // 최신 게시물부터 정렬
        article_arr.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));

        displayArticles(article_arr);  // 게시물 목록을 화면에 표시
    })
    .catch(err => console.log(err));
}
  • article_arr.sort(): 서버에서 받아온 게시물을 timestamp(게시물이 작성된 시간)를 기준으로 내림차순으로 정렬하여 최신 게시물이 먼저 보이게 한다.
  • displayArticles(article_arr): 정렬된 게시물을 화면에 보여주기 위해 displayArticles 함수가 호출된다.

 

displayArticles 함수는 게시물을 표시해주는 역할을 한다.

function displayArticles(articles) {
    const articlesList = document.getElementById('articles');  // 게시물 리스트가 들어갈 영역
    articlesList.innerHTML = '';  // 기존 게시물 목록을 초기화

    articles.forEach(function(article) {
        let li = document.createElement('li');  // <li> 태그 생성
        li.innerHTML = `
            <h3>${article.title}</h3>
            <p>작성자: ${article.author}</p>
            <p>작성 시간: ${new Date(article.timestamp).toLocaleString()}</p>
        `;
        li.onclick = function() {
            viewArticleDetail(article.article_id);  // 게시물을 클릭하면 상세 페이지로 이동
        };
        articlesList.appendChild(li);  // 리스트에 항목 추가
    });
}
  • document.createElement('li'): 각 게시물을 리스트 항목(<li>)으로 만든다.
  • li.onclick = function(): 리스트 항목을 클릭하면 해당 게시물의 상세 페이지로 이동한다. 이동할 때 article_id를 URL에 포함시켜 게시물의 상세 정보를 보여준다.

 

function filterArticles() {
    const searchInput = document.getElementById('search-input').value.toLowerCase();
    const filteredArticles = article_arr.filter(article => 
        article.title.toLowerCase().includes(searchInput) || 
        article.content.toLowerCase().includes(searchInput)
    );
    displayArticles(filteredArticles);
}
  • 검색창의 입력값을 가져오기: searchInput에 사용자가 입력한 검색어를 소문자로 변환해 저장한다.
  • 필터링: article_arr에서 게시물의 제목 또는 내용을 검색어와 비교해서 포함된 항목만 골라낸다. 그 후 displayArticles(filteredArticles)로 필터링된 결과를 화면에 다시 표시한다.

 

 

소스코드를 이해하고 코드를 저장했다면, AWS 콘솔 창으로 들어가 백엔드 파트를 구현해주자.

2편 게시글을 참고하길 바란다.

 

https://vegetableworld.tistory.com/235

 

[AWS Final Project] S3와 DynamoDB를 활용하여 Serverless 게시판 만들기 - 2편 (Cognito, S3, API Gateway, Lambda, Dyna

 

vegetableworld.tistory.com