8. 게시글 작성 / 수정 / 삭제 기능

박준혁·2023년 6월 6일

현재 게시글을 표현하는 방식까지 나타내었고, 이번에는 게시글을 작성, 수정, 삭제하는 기능을 구현해보고자 한다.

게시글 기능 구현 방식

게시글 작성/수정/삭제 기능 모두 비슷한 방식으로 구현한다. 3개 단계로 구분할 수 있다. 첫 번째는 사용자가 게시글을 모두 작성하여 서버로 전달하는 과정이고, 마지막은 게시글의 정보들을 DataBase에 저장하는 과정이다. 이러한 과정에서 사용자가 직접 DB에 접근하거나, DB에 데이터를 직접 업로드할 수 있도록 하는 것은 위험성이 있다. 따라서 두 과정 사이에 서버가 수행할 수 있는 기능을 추가하여, 서버에서 사용자가 입력한 데이터를 받고, 해당 데이터를 가공해 대신 DB에 업로드 하는 과정을 수행하게 구현할 것이다.
사용자는 Form 태그를 이용하여 Next.js에서 구현한 api에 데이터를 보내주고, 해당 api는 DB에 연결하여 글을 업로드할 수 있도록 제작하는 방식을 사용할 계획이다.

작성 기능

User 글 작성

User가 글을 작성하는 코드는 아래와 같다. 해당 코드의 결과는 /post/write url에 접속하면 확인할 수 있고, 글을 작성할 수 있다. 비교적 코드에 들어가는 내용들이 많은데 form 태그부터 확인해보면 form 태그 내부에 있는 input 태그에 작성된 값을 POST 방식으로 /api/post/new 로 전달되고, 해당 내용은 이후 Server 기능에서 자세하게 설명하고자 한다.
먼저 input 태그 내에 readOnly라고 작성된 것은 사용자가 따로 변경할 수 없는 것으로 기존에 로그인된 사용자의 정보를 가지고 값을 지정하는 방식이다. 추가적으로 className에 hidden이면 화면에 나타나지 않도록 하는 tailwind CSS이다. 초기 작성자, 팀 이름은 readOnly만, 팀 번호, 기수, 가장 최근에 변경한 사용자 정보는 사용자에게 나타나지 않는다. 사용자가 변경할 수 있는 것은 글 제목과 글 내용이다. 따라서 두 항목을 작성하여 submit type의 button을 누르면 POST 요청을 보내게 된다.

// /post/write/page.js
export default async function Write(){
    let session = await getServerSession(authOptions)
    return (
        <div>
            <h1>New Post</h1>
            <form action="/api/post/new" method="POST" id="write">
                <div>
                    <div>
                        <span>Author</span>
                        <span>Team Name</span>
                        <span>Title</span>
                        <span>Content</span>
                    </div>
                    <div>
                        <input name="team" value={session.user.team} readOnly className=" hidden"/>
                        <input name="semester" value={session.user.semester} readOnly className=" hidden"/>
                        <input name="FirstAuthor" value={session.user.name} readOnly/><br />
                        <input name="LastAuthor" value={session.user.name} readOnly className=" hidden"/>
                        <input name="teamname" value={session.user.teamname} readOnly/><br />
                        <input name="title" placeholder="글 제목"/><br />
                        <textarea name="content" placeholder="글 내용" rows="15" cols="100"/><br />
                    </div>
                </div>
            </form>
            <button type="submit" form="write">Upload Post ✎</button>
        </div>
    )
}

Server 글 업로드

POST 요청을 보내면 아래 코드에서 데이터를 처리한다. 먼저 function에서는 request와 response를 받아들이고, request에는 POST 요청으로 온 정보들이 포함되어 있고, 앞선 form에서 제공한 데이터는 request.body에 저장되어있다. response의 경우 데이터 처리 이후 결과를 다루는 용도로 사용한다.
먼저 request가 POST 요청인지 확인한 뒤, db에 연결해 post collection에 request.body의 정보를 insert하는 과정을 수행한다. 추가적으로 해당 과정에서 firstCreated와 lastModified 시간을 따로 지정하는데 이 과정에서 getTime()이라는 함수를 이용해 업로드 시점의 한국 시간을 timeval로 저장한다. 한국 시간 표현 방법은 아래 링크에서 확인할 수 있다. 마지막으로 response에서 redirect를 통해 게시판 메인 화면인 '/post'로 이동한다.
(Javascript 한국 시간 표현법)

// pages/api/post/new.js
export default async function handler(request, response){
    if(request.method == 'POST'){
        const db = (await connectDB).db('vessweb');
        const timeval = getTime();
        request.body.lastModified = timeval;
        request.body.firstCreated = timeval;
        let result = await db.collection('post').insertOne(request.body)
        return response.redirect(302,'/post')
    }
}

작성 기능 구현 결과


Write로 이동하는 버튼을 만들어서 추가하였다

<Link href={"/post/write"}>New Post ✎</Link>

수정 기능

수정 기능은 작성 기능과 매우 유사하지만 다른 점은 첫 번째, 수정 페이지로 들어갈 경우 기존에 작성한 내용이 나타나야 한다는 점, 두 번째, DB에 새로 데이터를 넣는 것이 아닌 업데이트 해야한다는 점, 마지막, 수정 권한을 모두에게 주면 안된다는 점이 다르다. 각각의 차이점을 베이스로 구현해보고자 한다.
추가적으로 수정 페이지의 url은 '/post/[team]/[id]/edit' 경로이다. 글의 세부페이지에서 /edit이 추가 되었다고 생각하면 된다. 따라서 기존 세부 페이지를 제작할 때 만든 [id] 폴더 내부 [edit] 폴더를 만들어 작업하도록 한다.

User 수정 기능

user에게서 수정 기능이 나타나도록 하는 페이지의 코드이다. 코드는 앞서 만든 글 작성 페이지와 거의 동일하다. 주어진 id의 게시글을 찾고, 해당 값들을 바탕으로 input들의 value를 채우는 과정을 거친다. 글 작성과 다른 점은 form의 action이 '/api/post/edit'으로 변경되었고, input의 hidden 요소 중 DB에서의 post의 id를 추가하여 request.body에 id가 포함되어 server에 전달할 수 있도록 하였다.

// /post/[team]/[id]/edit/page.js
export default async function Edit(props){
    const db = (await connectDB).db('vessweb');
    let result = await db.collection('post').findOne({_id: new ObjectId(props.params.id)});
    return (
        <div>
            <h1>Edit Post</h1>
            <form action="/api/post/edit" method="POST" id="edit">
                <div>
                    <div>
                        <span>Author</span>
                        <span>Team Name</span>
                        <span>Title</span>
                        <span>Content</span>
                    </div>
                    <div>
                        <input name="team" value={result.team} readOnly className=" hidden"/>
                        <input name="semester" value={result.semester} readOnly className=" hidden"/>
                        <input name="_id" className=" hidden" defaultValue={result._id.toString()} />
                        <input name="author" readOnly defaultValue={result.FirstAuthor}/><br />
                        <input name="teamname" value={result.teamname} readOnly/><br />
                        <input name="title" placeholder="글 제목" defaultValue={result.title}/><br />
                        <textarea name="content" placeholder="글 내용" rows="15" cols="100" defaultValue={result.content}/><br />
                    </div>
                </div>
            </form>
            <button type="submit" form="edit">Edit Post</button>
        </div>
    )
}

Server 수정 기능

이번에는 edit 요청을 받아 server에서 수정하는 기능을 수행하는 코드이다. 앞서 request.body에 포함시켜 전달한 object의 id를 이용해 db에서 항목을 찾고, 해당 항목을 find에 저장한다. 또한, request에서 변경된 title과 content는 newVal이라는 변수에 저장하고, 가장 최근에 변경된 시간과 사용자 역시 newVal에 저장한다. (getTime 함수는 앞서 설명한 것과 동일하게 한국 시간을 받아오는 함수이다.)
다음이 중요한 파트인데, db에 결과를 update하기 전, 사용자의 권한을 확인해야 한다. 수정 및 삭제 권한은 게시글을 올린 사용자와 동일한 팀에 소속되어 있거나, 회장단(admin)이 가질 수 있도록 한다. 따라서 team이 동일하거나 role이 admin일 경우에 updateOne 함수를 통해 값을 변경할 수 있고, '/post'로 redirect 된다. 그렇지 않은 경우 '수정 권한이 없습니다'라는 에러 메시지를 출력한다.

// pages/api/post/edit.js
export default async function handler(request, response){
    if(request.method == 'POST'){
        const db = (await connectDB).db('vessweb');
        let session = await getServerSession(request, response, authOptions)
        let newVal = {
            title : request.body.title,
            content : request.body.content,
            lastModified : getTime(),
            LastAuthor : session.user.name,
        }
        let find = await db.collection('post').findOne({_id: new ObjectId(request.body._id)});
        if((session.user.team==find.team && session.user.semester==find.semester)  || session.user.role == 'admin'){
            await db.collection('post').updateOne(
                {_id : new ObjectId(request.body._id)},
                {$set : newVal}
            )
            return response.redirect(302,'/post')
        }
        else{
            response.status(500).json('수정 권한이 없습니다')
        }
    }
}

구현 결과

결과도 잘 수정되는 것을 확인할 수 있다.

삭제 기능

마지막 삭제 기능이다. 삭제 기능은 앞서 form 태그를 이용한 것과 다르게 fetch 함수를 이용해보고자 한다.

User 삭제 기능 (Edit Button 추가)

권한을 가진 사용자가 상세 페이지로 들어가면 Edit과 Delete를 수행할 수 있는 버튼이 따로 나타나게 할 계획이다. 이를 위해 두 버튼을 모아 EditDeleteBtn 함수를 만들었고, ✏️ 버튼을 누르면 상세페이지의 글에 맞는 edit 페이지로 이동하게 된다.
두 번째 🗑️ 버튼은 삭제 기능을 수행하는 버튼으로, 해당 버튼 클릭 시 '/api/post/delete'로 POST 방식의 요청을 보낸다. request의 body에는 글의 id가 포함되어있고, 수행한 결과를 alert를 통해 알리고, 다시 '/post'의 위치로 이동하게 되는 방식이다.

export default function EditDeleteBtn ({result}){
    const router = useRouter();
    return (
        <div>   
            <Link href={`/post/${result.semester}th${result.team}/${result._id}/edit`}>✏️</Link>
             <button onClick={(e)=>{
                    fetch('/api/post/delete',{method : 'POST', body:result._id})
                    .then((r)=> r.json())
                    .then((result)=>{ 
                        alert(result)
                        router.push('/post')
                    })
                }}>🗑️</button> 
        </div>
    )
}

Server 삭제 기능

Server에서는 삭제 요청이 들어왔을 때 먼저 find에 해당 id를 가진 post를 DB에서 검색한다. 이후 수정과 동일하게 권한을 확인한다. 동일한 팀 소속인지와 회장단(admin)인지 확인한 다음에 삭제 권한이 있으면 deleteOne 함수로 삭제하고, 아닐 경우 삭제 권한이 없다는 메시지를 return 한다.

// pages/api/post/delete.js
export default async function handler(request, response){
    if(request.method == 'POST'){
        const db = (await connectDB).db('vessweb');
        let session = await getServerSession(request, response, authOptions)
        let find = await db.collection('post').findOne({_id: new ObjectId(request.body)});
        if(session.user.team == find.team || session.user.role == 'admin'){
            let result = await db.collection('post').deleteOne({_id: new ObjectId(request.body)})
            response.status(200).json('삭제완료')
        }
        else{
            response.status(500).json('삭제 권한이 없습니다')
        }
    }
}

버튼 권한

추가적으로 앞서 제작한 Edit과 Delete 기능을 수행하는 버튼을 권한에 따라서 부여하는 것을 추가하였다. 게시글의 상세 페이지에 접속했을 때 권한이 있는 사람이면 아래 수정, 삭제 버튼이 나타나고 그렇지 않은 경우는 버튼이 따로 나타나지 않는다.

// /post/[team]/[id]/page.js 추가 내용
{session.user.role=='admin'
|| (session.user.team==result.team && session.user.semester==result.semester)
        ? <EditDeleteBtn result={result} /> : null}

구현 결과

삭제 완료시 결과 창

0개의 댓글