HackerPunk 프로젝트 회고

mango7loco·2022년 5월 4일
0

블록체인

목록 보기
4/4
post-thumbnail

프로젝트를 진행하면서 들었던 생각과 과정들을 시간 순으로 회고합니다.

[깃허브]
https://github.com/codestates/beb-03-second-06-hackerpunk

[배포]
https://hacker-punk.netlify.app/
(서버를 heroku 무료 버전을 사용하여 배포해서, 오랫동안 서버에 요청이 없으면 자동으로 꺼지기 때문에 처음 들어가시면 loading이 약간 길 수 있습니다)


준비

백엔드와 스마트 컨트랙트에 관심이 있었는데, 이번 프로젝트는 두 역할이 해야 할 일이 저번보다 훨씬 늘어나 보였기 때문에 백엔드만 맡아 집중하게 되었다. 팀장님의 배려 덕분에 웹서버와 데이터베이스를 다루는 백엔드를 맡을 수 있었다. 그리고 두 분은 프론트엔드와 디자인을 담당했고, 한 분은 스마트 컨트랙트를 맡게 되었다. 이전 프로젝트에서 줌으로 모여 코딩을 함께 하고, 디스코드 서버에서 실시간으로 채팅이나 자료를 주고 받고, 노션에는 회의록을 작성하는 것이 괜찮았던 경험이 있었기 때문에 이번에도 해당하는 툴들을 활용했다.

구현

들어가기에 앞서

이번 프로젝트는 web2에서 블록체인 인센티브 기반 토론 시스템을 갖춘 커뮤니티 사이트 개발이 목표였다. 기존의 커뮤니티 사이트들이 중앙 서버에서 모든 정보를 관리하는 것과는 조금 다르게, 외부에서도 사용자들이 토큰의 분배가 실제로 어떻게 이뤄지고 있는지 이더스캔에서 확인할 수 있게 사용자의 계정 하나당 새로운 지갑을 할당했다. 여기서 사용자의 계정 하나당 새로운 이더 지갑을 할당했다는 말은, 사용자가 자신의 메타마스크를 웹사이트에 연결하는 것이 아니라 사용자가 회원가입을 할 때 새로운 지갑 주소를 만들어 사용자 정보와 함께 데이터베이스에 저장한다는 것을 의미한다. 그러므로 사용자에게 할당된 지갑의 니모닉 코드는 서버가 갖고 있고, 사용자는 가지고 있지 않은 상태가 된다. 그래서 토큰 이코노미에 의해서 해당 사용자에게 토큰이 분배되면, 단순히 웹사이트에서 표기되는 숫자로만 볼 수 있는 게 아니라 이더스캔을 활용해서 내부의 데이터베이스에 접근하지 않고도 자신에게 실제로 토큰이 전송됐는지 확인할 수 있다.

물론 처음부터 메타마스크를 웹사이트에 연동한 뒤에, 해당 지갑 주소로 토큰을 직접 전송할 수도 있다. 하지만 우리는 그 중간 지점의 서비스 형태로, 사용자에게 개인 지갑이 할당되지만 그 소유권은 서버가 가지고 있는 방식을 구현하고자 했다. 이로 인해서 생기는 단점은 서버의 데이터베이스에 문제가 생기면, 해당 지갑에도 위험한 일이 발생할 수 있다는 점이다. 즉, 니모닉 코드를 서버가 보관하고 있으므로 사용자에게 할당된 지갑이긴 하지만 그 지갑의 사용 권한과 보관 책임이 전적으로 서버에게 있다는 것이다. 하지만 아직 블록체인 생태계에 익숙하지 않은 사람들은 메타마스크를 사용하는 것도 불편하며, 메타마스크를 본인이 주체적으로 관리하고 그로 인한 책임을 전적으로 갖게 된다는 특성에 부담을 느낄 수 있다. 어떻게 보면 기존의 서비스들은 일단 문제가 발생하면, 그 서비스를 운영하는 회사에 찾아가서 어떻게든 따지면 그에 맞는 대응이 가능할 수도 있다. 허나, 개인 지갑을 잘못 보관 및 사용하여 생긴 문제는 스마트 컨트랙트를 배포하고 운영하는 관리자도 어떻게 해줄 수 있는 부분이 없다는 특징이 있으므로, web2 커뮤니티에서는 이러한 형태가 좋다는 판단을 했다.

그리고 HackerPunk라고 프로젝트 이름을 지은 이유는 주어진 시간이 짧아 기획한 모든 기능을 구현하지는 못했지만, 개발자들이 모인 커뮤니티를 만들고자 하는 아이디어에서 시작하여, 사이버펑크 같은 느낌을 주고자 그렇게 이름을 짓게 되었다. 실제로 웹사이트의 디자인적인 요소들은 SF 같은 느낌의 색과 글씨체, 미니멀리즘적인 느낌이 들도록 노력했다. 이러한 테마와 큰 틀을 바탕으로, 우리는 아래의 기능들을 구현했다.

  • 사이트에 회원가입을 할 수 있다.
  • 회원가입은 이메일 인증을 통해 이뤄지며, 회원가입이 완료됨과 동시에 개인 지갑이 할당된다.
  • 로그인 기능을 지원하여 사이트에 사용자 정보가 있는 사람들만 게시판 접근 권한을 얻는다.
  • 게시글을 작성하고, 수정하고, 삭제할 수 있다.
  • 사용자들은 자신의 마음에 든 게시글에 토큰을 기부할 수 있다.
  • 토큰을 첫 기부 받은 게시글은 일정 기간 동안 토큰을 기부받을 수 있고, 그후에는 락업된다.
  • 기한이 되면, 게시글 작성자는 기부받은 전체 토큰을 수령 신청할 수 있다.
  • 출석과 게시물에 기부받는 것을 통해 토큰을 모을 수 있고, 이를 외부 지갑으로 출금 신청할 수 있다.

이어지는 목록은 부분적으로 구현했지만, 완성하지 못해 배포하지 못한 기능들이다.

  • 각 게시글에는 덧글을 작성할 수 있다.
  • 검색을 통해 게시글과 덧글을 특정 조건에 맞춰 검색할 수 있다.
  • 게시물은 기부받은 토큰 수령 신청 완료 후에 락업이 되고, 해당 게시물은 NFT로 민팅된다.
  • 민팅된 NFT를 이용해서 스테이킹을 할 수 있고, 이 스테이킹을 통해서는 기존의 토큰과는 다른 토큰을 받을 수 있다.
  • NFT를 통해 받은 토큰이 많은 사용자는 의미있는 게시물을 많이 작성했다는 것이다. 따라서 그만큼 사이트가 풍성해지는데 기여했다는 의미이므로 이에 걸맞는 추가적인 혜택을 제공한다.
  • 초창기에 ICO를 진행하여서 토큰을 서비스 초기부터 분배하여, 초반에 토큰의 기부가 활발히 일어날 수 있게 유도한다.

개인적으로는 스마트 컨트랙트를 게시물 NFT민팅과 스테이킹 기능까지 어느정도 구현하셨는데, 실제 테스트넷에서 합을 맞춰보지 못한 것이 아쉬웠다.

데이터베이스 스키마

User

{
    userId: String, // 유저 아이디
    userPassword: String, // 유저 비밀번호
    userEmail: String, // 유저 이메일
    userDate: String, // 유저 최근 접속 일자
    
	userAction: Number, // 0: 다른 행동 가능, 1: donate, 2: cancel, 3: reward, 4: withdraw
    userDonated: Number, // 유저가 게시글을 작성해서 기부받은 HP 토큰 총량

    userPubKey: String, // 외부 인증된 지갑 주소
    servUserPubKey: String, // 서버 내부 지갑 주소
    servUserPrivKey: String, // 서버 내부 지갑 프라이빗키
    servUserMnemonic: String, // 서버 내부 지갑 니모닉 코드

	donateArticles: [Number], // 유저가 기부한 게시글들
	rewardedArticles: [Number], // 유저가 HP 토큰 보상받은 게시글들

    userArticles: [Number], // 유저가 작성한 게시글들
    userComments: [String], // 유저가 작성한 덧글들
}

Article

{
    no: Number, // 게시글 번호
    author: String, // 게시글 저자의 아이디
	authorPubKey: String, // 게시글 저자의 서버 내부 지갑 주소
    title: String, // 게시글 제목
    views: Number, // 게시글 조회수
    content: String, // 게시글 내용
    deleted: Boolean, // 게시글 삭제여부, 0: 삭제 안 된 상태, 1: 삭제된 상태
    comments: [commentSchema], // 덧글들
	createdAt: Date, // 게시글이 생성된 날짜
	updatedAt: Date, // 게시글이 최근 수정된 날짜
}

회원가입

회원가입은 sign up 기능을 통해서 ID, password, email 정보를 서버에 넘겨주면 서버에서는 bcrypt 모듈을 사용해 password를 해쉬화한 후에 입력된 데이터를 모아 jwt 토큰을 만들어 사용자가 입력한 email로 confirmation 용도의 token을 보내주었다. 그러면 사용자는 자신이 등록한 email로 온 이메일 내용에서 안내된 링크를 클릭하여 회원가입을 완료할 수 있다. 이때 사용자가 입력한 이메일 주소로 이메일을 보내기 위해서 nodemailer 모듈을 사용했고, gmail에서 서버용 계정을 새로 만들어 사용했다. 링크를 클릭하면, 서버에 confirmation token이 도착하고 이를 통해서 사용자 정보를 데이터베이스에 저장하면서 그 사용자에게 할당될 개인 지갑을 만들어 함께 저장한다. 사용자는 링크를 클릭하기만 하면 회원가입 과정이 끝나면서 로그인된 채로 사이트 게시판 화면으로 옮겨진다.

로그인

ID와 password를 이용하여 로그인을 시도하면, 데이터베이스에서 입력한 값을 토대로 우리 사이트의 사용자가 맞는지 확인한다. access token을 발급하여 사이트 내부에서 로그인 상태를 유지해 사이트를 이용할 수 있게 한다. 로그인을 할 때마다 서버는 데이터베이스에 사용자가 최근 접속한 날짜를 저장해둔다. 그리고 로그인을 시도하면 데이터베이스에서 최근 사용자가 접속한 날짜와 비교해서, 오늘 처음 접속한 것이면 출석을 한 것에 대한 보상으로 토큰을 발행해준다. 게임에 접속하면 사용자가 직접 출석체크를 눌러서 보상을 받는 방식으로 구현할지 이야기를 했는데, 사용자의 편리성을 위해서 로그인을 하면 자동으로 서버에서 출석체크 보상을 주는 것으로 구현했다. 그런데 다시 생각해보니 단순히 출석체크 버튼을 눌러서 받는 것이 아니라, 몇일차 마다 누적될수록 더 괜찮은 보상을 주거나 한달마다 초기화해서 사람들이 지속적으로 접속할 수 있는 유인 요소를 시각적으로 구현하는 것도 괜찮았을 것 같았다는 생각이 든다.

외부 지갑 인증

외부 지갑을 연결하는 행위는 굉장히 중요한 의미를 가진다고 생각했다. 그래서 이를 위해 HackerPunk의 토큰을 의미하는 HP token 스마트 컨트랙트에 더불어, ExternalHP 스마트 컨트랙트를 준비했다. 우리 사이트는 회원가입을 할 당시에 니모닉 지갑을 만들어 사용자 한 명에게 할당해준다. 그 지갑 주소를 internal address로 두고, 해당 주소로 보상에 의한 토큰 전송이 모두 이루어진다. 하지만 앞서 말했다시피 현재 데이터베이스에는 그 지갑의 니모닉 코드가 저장되어 있기 때문에 외부의 공격으로부터 데이터베이스에서 해당 정보가 탈취당할 경우, 다른 사람이 니모닉 코드를 통해서 지갑을 restore하여 사용자가 이제까지 모은 HP 토큰을 훔칠 수 있게 된다.

그런데 우리 사이트에서 인증을 하게 되면, 해당 internal address와 외부 지갑인 external address를 묶어서 일대일 매칭한다. 그렇게 함으로써 데이터베이스에서 니모닉 코드를 탈취 당해도, 이미 인증된 whitelist인 external address가 아니면 HP 토큰이 전송이 안되게 함으로써 안전을 도모하고자 했다. 또한, 지갑을 인증하는 과정에서 일정 금액의 가입비를 받는데, 그 가입비는 우리 사이트의 운영비로 들어감과 동시에 internal address에 들어있는 HP 토큰의 사용 권한을 서버의 마스터 지갑과 HPTimeLock 스마트컨트랙트에게 approve할 때 드는 트랜잭션 비용에 쓰고자 했다. approve를 해야 하는 이유는, 만약 HP 토큰 사용 권한을 주지 않는다면, internal address에 쌓인 HP 토큰을 외부 지갑으로 인출하려고 할 때 해당 지갑으로 트랜잭션을 일으켜야 하는데 그럴 때마다 수수료가 들기 때문에 일정 수준의 이더를 항상 채워야 하며, 게시물에 기부하려고 할 때도 수수료가 들기 때문에 이 과정을 스마트 컨트랙트와 마스터 지갑이 대신 처리해주기 위해서이다. 가입비를 받을 때도 금액별로 차등을 두는 등급제 운영을 염두에 두고 credential type을 만들어 두었지만, 그 기능을 구체화하여 구현하지 못해 아쉬웠다.

다시 정리하면, 외부 지갑을 인증(연결)하는 행위는, 데이터베이스에서 니모닉 코드가 털렸어도 사용자의 HP 토큰을 훔쳐가지 못하게 하고, 우리 사이트에 가입비를 내어 사용자에게 할당된 니모닉 지갑이 approve 트랜잭션을 일으키는 비용과 사이트 운영 비용(계속해서 트랜잭션을 일으키는 마스터 계정에 미리 비축)과 결과적으로 도입하지는 못했지만 가입 금액에 따른 등급제를 통해 각 등급마다 차별된 혜택을 주기 위한 것이었다. 나아가서 외부 지갑 주소를 알게 되면 블록체인 네트워크에서는 해당 주소에서 소유하고 있는 이더, 토큰, nft를 확인할 수 있으니, 이를 통해 추가적인 서비스를 제공해줄 수도 있으므로 사용자 정보와 매칭해서 이를 관리하면 도움이 되지 않을까 하는 의도가 담겨있었다.

하지만 프로젝트 초반부에 위와 같은 회의를 거치고 진행하였는데, 진행 중후반부에 들어서서 과연 우리의 가설대로 이 솔루션이 필요하는지 의문이 들었다. ExternalHP 스마트 컨트랙트에 internal address와 external address를 매칭시켜 정보를 저장하더라도, HP 토큰 자체의 transfer 함수를 이용해서 외부 지갑으로 옮길 수 있다. 즉, whitelist로 등록된 주소에만 HP 토큰을 전송할 수 있게 하려면, 다른 스마트 컨트랙트 상에 해당 기능을 구현하는 것이 아니라, HP 스마트 컨트랙트에 그 기능을 만들어서 transfer를 실행할 때 제한을 걸었어야 하지 않았나 생각이 들었다. 또한, 결국에 서버에서 사용자의 지갑에 대한 권한을 전적으로 갖고 있으므로, 이를 외부 이더스캔으로 확인할 수 있다는 점을 장점이라고 할 수 있는가였다. 사용자가 외부로 토큰을 인출하기 전까지는, 사용자는 토큰을 사이트에 어떻게 해주라고 요청할 수 있을 뿐이지, 실질적인 소유권은 갖고 있지 않다. 즉, 아무리 이더스캔 상으로 자신의 지갑에 토큰이 차곡차곡 쌓인 것을 확인하는 것과 별개로, 서버에서 토큰 인출을 해줄 것이라는 믿음이 추가적으로 필요하다.

만약 이처럼 사이트에 대한 믿음이 전제되어야만 한다면, 사용자마다 따로 지갑을 발행하지 않고 데이터베이스에 토큰의 변동 사항 정보를 보관한 다음, 마스터 지갑에서 외부 지갑으로 곧장 토큰을 인출해주는 것이 사이트 내부에서 일어나는 액션마다 블록체인 네트워크 이용 수수료를 내지 않아도 되므로 비용이 더 절감된다. 그리고 가입비를 통해서 등급을 나누는 방법 자체도 외부 지갑에서 마스터 지갑으로 들어온 금액을 기준으로 데이터베이스 상에서 유저의 정보를 바꿔주면 되었다. 또한, 현재 뒤에 나올 게시물 기부 기능을 수행하려면 해당 계정의 HP 토큰을 사용할 권한을 approve 해줘야 하는데, 그 과정이 외부 지갑을 인증할 때 이루어져서 이를 위한 수수료도 그때 전송된다. 즉, 사실상 외부 지갑을 인증하지 않으면 사용자 내부 지갑에는 이더가 하나도 없어서 트랜잭션을 일으킬 수 없고, 현재 구현된 커뮤니티의 주요 기능인 기부하기도 HPTimeLock 스마트컨트랙트가 HP 토큰 사용 권한이 없어 실행할 수 없다. 물론 지갑 인증을 유도하기 위해서 이 점을 알고 구현했지만, 이렇게 되면 지갑을 인증하기 전에 이메일로 가입만 하여 가볍게 사용해볼 생각인 사람들의 사용자 편의성은 떨어지게 된다. 어쩌면 초반부에 조금 더 깊이있는 고민을 했으면 더 나은 솔루션이 나왔을 수 있다. 앞으로는 전체적인 큰 틀에 대한 충분한 고민을 하고 기능을 구현해야겠다는 생각이 들었다.

구체적으로 외부 지갑 인증 과정은, 클라이언트에서 외부 지갑 연결을 누르면 서버에서는 req에 들어있는 access token을 이용해서 ID를 얻는다. 해당 ID를 이용해 데이터베이스에서 내부 지갑 주소와 프라이빗키를 찾는다. 그리고 프라이빗키를 이용해 내부 지갑 공개주소가 해쉬화된 데이터에 서명한다. 해당 데이터와 서명을 클라이언트에 보내면, 클라이언트는 받은 값들을 이용해 자신의 외부 지갑을 통해 externalHP 스마트 컨트랙트의 authenticate 함수를 실행시키는 트랜잭션을 일으킨다. 그러면 verify 함수를 통해 해당 데이터를 서명한 주소를 얻어내고, 그 주소가 internal address와 일치하는지 확인한다. 이것이 일치한다는 말은 internal address에 대응하는 프라이빗키를 이용한 서명을 클라이언트가 받았다는 말이므로, 스마트 컨트랙트 입장에서는 이 클라이언트가 서버에 등록된 사용자인 것을 확인할 수 있다. 그리고 그 트랜잭션을 일으킨 주소는 외부지갑 주소가 되므로, 그 외부지갑 주소와 internal address를 매칭시켜놓는 작업을 한다. 이로써 인증과정이 끝나고, 이 authenticate를 하면서 이더를 가입비로 낸다.

토큰 인출

토큰 인출은 악의적인 사용자가 적은 금액으로 인출을 엄청 많이 신청할 때, 그 수수료를 전부 마스터 계정이 부담하는 식으로 되어있기 때문에 일정 금액 이상만 신청 가능하게 만들었다. 데이터베이스가 털리지 않는 이상, 사용자 계정에 쌓여있는 토큰을 인출하려면 서버에 요청을 해야 한다. 이때 토큰 인출을 할 수 있는 지갑을 개인이 적는 것이 아니라, 앞에서 인증한 외부 지갑 주소로만 인출이 되게 만들었다. 즉, 외부 인출 신청을 하면 토큰을 받고 싶은 지갑 주소를 적을 수 없고, 기존에 등록된 외부 지갑으로만 인출할 수 있다. 그런데 이렇게 할 경우에는 혹시라도 외부 지갑을 인증하기 전에 게시물을 많이 작성해서 많은 토큰을 기부 받았는데, 사용자의 ID와 password가 유출되어 다른 악의적인 사용자가 자신의 지갑을 internal address와 매칭해서 등록해 놓을 수 있다. 이를 방지하기 위해서 사이트는 이메일 인증한 가입자에게 최대한 지갑 인증을 빨리 하라고 유도하자고 하였다. 이러한 사항은 초창기부터 이야기 됐던 부분인데, 직접적인 안내 사항을 만들어 놓지 않아서 아쉬움이 남는다.

게시물 CRUD

게시물 작성은 게시물의 내용과 제목이 제대로 입력되어 들어오면 데이터베이스에 해당 게시물에 관련된 내용을 담아 저장한다. 게시물 번호는 첫 글이 1번부터 시작해서 1씩 증가한다. 그리고 삭제가 되더라도 해당 글은 삭제 표시를 남겨서 사용자들에게 노출되지는 않지만, 데이터베이스에는 해당 자료가 남아있게 했다. 게시글을 읽을 때는, 게시글 번호를 쿼리로 보내면 해당 게시물의 조회수를 1 증가시키고 해당 내용들을 담아 보내준다. 이때 해당 게시글에 대해 기부금이 있을 수 있는데, 이 금액은 데이터베이스에 저장된 값을 불러오는 게 아니라 요청이 들어왔을 때마다 실시간으로 스마트 컨트랙트에 저장된 값을 확인하여 응답한다. 금액에 관련된 부분은 데이터베이스에 저장하는 것보다 블록체인에 저장되어 있는 값을 불러오는 게 더 투명하다는 생각이 있었기 때문이다. 또한, 아래의 기부 파트에서 더 자세히 설명하겠지만, 게시글은 첫 기부가 시작되기 전, 기부가 진행되는 기간, 기부가 끝났는데 작성자가 기부금을 받아가지 않은 상태, 기부가 종료되었고 작성자도 기부금을 받아간 상태가 있다. 그 상태에 관련한 내용도 스마트 컨트랙트에서 확인하고 돌려준다.

작성자 쿼리를 보내면 해당 작성자가 작성한 게시글들을 목록으로 보내준다. 그리고 amount와 num을 이용해서는 게시물 목록을 불러들일 수 있다. 처음에 이 부분을 구현할 때는 단순하게 작은 숫자부터 시작해서 게시물 정보를 응답했는데, 곧 그렇게 하면 안되는 것을 깨달았다. 왜냐하면 일반적으로 게시판의 게시글들은 최신 순으로 보여준다. 그런데 클라이언트가 처음 요청을 할 때는 가장 최신 게시글의 번호가 무엇인지 모른다. 그러므로 amount는 불러들일 게시글의 숫자, num은 시작 숫자인데, 이 시작 숫자를 모르므로 처음에는 amount만큼 게시물 번호 최근 순으로 보내준다. 이후에는 클라이언트도 현재 불러들인 게시글의 번호들을 가지고 있기 때문에 num을 이용해서 이후의 게시글들을 차례로 불러들일 수 있다. 이때 게시글의 번호는 있지만 게시글이 삭제 처리된 것들은 제외하고 amount 숫자만큼 보내준다. 또한, 게시물에는 첫 기부가 이루어진 후에 일정 기간 동안만 기부가 가능하다. 따라서 기부를 할 수 있는 남은 시간에 대한 정보도 담아 보내준다.

이렇게 게시글 목록을 보내주면서 사용자 정보도 함께 보내준다. 사용자의 기본적인 정보 뿐만 아니라, 스마트 컨트랙트에 기록된 HP 토큰의 양을 실시간으로 확인하여 보내준다. 그리고 사용자의 액션 상태도 보내준다. 그 이유는 스마트 컨트랙트 호출을 하는 경우에 아직 해당 트랜잭션이 완료되지 않은 경우가 있다. 예를 들어서, 사용자가 기부를 하였는데 아직 기부 트랜잭션이 처리되지 않아서 기부한 금액만큼 깎이지 않아 있는데, 이 상태에서 다른 게시글에 또 기부를 하면 기부금이 부족함에도, 아직 처리되지 않은 금액도 자신이 소유한 줄 알고 기부 트랜잭션을 일으킬 수 있는 상황이 발생할 수 있다. 그러므로 사용자가 블록체인에 트랜잭션을 보낸 경우에는, 해당 트랜잭션이 체결되기 전까지는 다른 스마트 컨트랙트 기능을 사용할 수 없게 통제한다. 그리고 어떠한 상태이기 때문에 다른 액션을 취할 수 없는지 알려주기 위해서, 그 상태 또한 유저 정보에 담아 보내준다. 그리고 자신이 기부했던 게시글의 목록과 자신이 작성한 게시글 목록도 넘겨준다.

게시글을 수정할 때는 해당 게시글이 삭제되었을 경우에는 접근할 수 없게 만드는 것을 제외하고는 특별히 주의할 점이 없다. 하지만 게시글을 삭제할 때는 한가지를 더 신경써야 한다. 게시글을 삭제하려는데 기부금액이 있을 경우에는, 해당 게시글에 기부한 사람들에게 기부한 금액을 되돌려주는 작업을 해야 한다. 그리고 데이터베이스에 사용자마다 자신이 기부한 게시글 정보를 보관하고 있는데, 그 정보에서 해당 게시글을 빼주는 작업을 해줘야 한다. 이 작업들을 마친 뒤에 해당 게시글은 삭제 표시되어서 다른 사용자들은 더이상 접근할 수 없는 상태가 된다.

게시물 기부

커뮤니티에서 좋은 게시글을 작성될수록 커뮤니티는 성장할 수 있게 된다. 그러므로 좋은 게시글들을 많이 작성할 수 있게 유도하고, 많은 게시글들 중에서 좋은 게시글들을 발굴하는 것이 중요하다. 그래서 우리는 사용자가 자신의 HP 토큰을 특정 게시글에 기부할 수 있는 기능을 구현했다. 기부 과정은 다음과 같은 사이클로 진행된다. 먼저 게시글에 처음으로 누군가 기부를 한 시점부터, 카운트다운이 시작된다. 카운트다운을 하는 이유는, 무한정 기부를 받을 수 있게 하는 것보다 제한된 기간 동안 많은 관심을 받은 글을 선별해내기 위해서였다. 실제적으로도 게시글들은 계속해서 생성되기 때문에, 시간이 지날수록 그 노출도가 떨어질 것을 생각해보면 기간을 길게 잡는 것은 효용이 높지 않다고 생각했기 때문이다.

그래서 처음으로 기부가 시작되면, 해당 게시글은 기부를 받는 중인 상태로 변하게 된다. 그리고 남은 기간이 게시글에 조회했을 때 나오게 해서, 마치 마감 임박인 세일 상품을 파는 것처럼 이 글을 좋은 게시글이라 느낀 사람들이 기부를 행동으로 옮기는 것을 유도하게 했다. 그렇게 정해진 기간이 지나면 기부를 더 하고 싶어도 하지 못하게 된다. 이후에 게시글 작성자는 모아진 기부 금액 받기를 신청할 수 있다. 이때 기부를 받은 기간에는 자신이 기부를 했더라도 그 기부금을 취소할 수 있게 했다. 기부 금액이 최종 결정되면 그 기부받은 금액은, 사용자 정보에도 업데이트가 된다. 기부금액이 높을수록 해당 사용자는 커뮤니티에서 인정받은 좋은 글을 많이 썼다는 의미로, 다른 사용자들과 구별하기 위해서 기부받은 금액에 비례해서 레벨을 매겨준다.


데모

제일 처음 사이트에 접속하면 나오는 화면으로, 계정이 이미 존재하는 사용자는 ID와 password를 입력하여 로그인을 할 수 있게 만들었다.
처음 사이트에 접속한 사람은 가입된 계정이 없을 것이므로, Sign up 버튼을 눌러서 나온 화면에서 회원가입을 신청해야 한다.
회원가입 란에 작성한 이메일에서 확인 메일을 체크하라는 요청 화면이 나온다.
이메일을 확인해보면 다음과 같은 형식의 메일이 도착해있고, Click here 링크를 클릭하면 회원가입 절차가 마무리 된다.
링크를 클릭하면 곧장 게시판 화면으로 전환되는 것을 확인할 수 있다. 게시판은 미니멀리즘과 게임 인터페이스 같은 느낌을 주기 위해 노력했다. 게시글은 무한 스크롤로 불러들인다. 게시글 작성자, 게시글 제목, 게시물 작성일, 조회수가 나와있으며, 유저 정보란이 우측 상단에 있다. 우측 상단에는 빨간색 점이 있는데, 이것이 로그아웃 버튼이다.
게시글을 클릭하면 게시글의 내용도 함께 볼 수 있고, 게시글 작성자의 경우에는 Donate버튼이 아닌 Edit, Delete 버튼이 나오는 것을 확인할 수 있다.
제목과 컨텐츠에 [UPDATE]를 붙여서 게시글을 수정해본 상태이다.
게시글을 새로 작성할 수도 있으며, 제목과 내용 란의 우측에 있는 x표를 누르면 작성 중인 내용을 한꺼번에 없앨 수 있다.
게시글 삭제도 잘 작동함을 확인할 수 있다.
게시판의 우측 상단에 Connect To External Wallet을 클릭하면 위에서 언급한 메타마스크 외부 지갑 인증 과정이 실행됨을 확인할 수 있다.
이전과 다르게 게 모양 이모지와 함께 닉네임 색깔이 바뀌며, 가입 축하 기념 1 HP 토큰을 받은 것을 확인할 수 있다. 외부 지갑으로 연결하는 버튼을 눌렀을 때 수수료 계산 문제로 연결이 안 될 수도 있으니, 실제 배포된 사이트에 들어가서 테스트 하실 분은 일정 량의 이더를 자신의 내부 지갑 주소에 보낸 후에 누르는 것을 추천한다. 만약 이 글을 읽지 않고 바로 테스트 해보셔서, 작동이 잘 안된 분은 회원가입부터 다시 진행해서 이더를 보낸 후에 해보시길 권한다.
방금 생성한 아이디로 '데모 기부 테스트 중입니다' 게시글을 작성한 다음에, 다른 아이디로 접속하여 해당 게시글에 들어가면 작성자가 자신의 게시글에 들어갈 때와 다르게 Donate 버튼이 있는 것을 확인할 수 있다.
Donate 버튼을 누르면 기부하고 싶은 HP 토큰 금액을 작성한 뒤 확인을 누르면 된다.
이렇게 기부를 하면 기부한 금액에서 수수료만큼은 마스터 계정으로 뺀 뒤에, 남은 금액이 Total Donation에 더해진다. 이 경우에는, 0.6 HP 토큰을 기부했는데, 0.582 HP 토큰이 Total Donation으로 되어있음을 확인할 수 있다.
기부 취소를 하면 자신이 해당 게시글에 기부한 금액이 회수된다. 그리고 위에 했던 설명처럼 트랜잭션이 아직 체결되지 않은 상태에서 또다시 트랜잭션을 일으키면 안되니까, 자신이 최근 어떤 트랜잭션을 일으켰기 때문에 행동을 취할 수 없는지 알려준다. 현재 상황은 0.582 HP 토큰을 환불 신청했는데 아직 0.582 HP 토큰을 돌려받지 않은 상태에서, 또다시 0.582 HP 토큰을 환불 신청하면 트랜잭션 수수료 낭비이기 때문이다.
이전에 기부 취소한 0.582 HP 토큰이 다시 들어와서 0.897 HP 토큰으로 바뀌었음을 확인할 수 있다. 그리고 이번에는 게시글 기부가 완료되면 어떻게 되는지 알아보기 위해서, 새로운 게시글을 만든 뒤에 0.5 HP 토큰을 기부했다. 기부한 직후에는 기부 트랜잭션을 일으켰다고 나오는 것을 알 수 있다. 그리고 트랜잭션이 체결되면 수수료를 빼고 0.485 HP 토큰만큼 기부가 된 것을 확인할 수 있다. Remain에 나온 시간은 기부를 받을 수 있는 남은 기간인데, 지금은 테스트를 해야하므로 3분으로 맞춰놨다. 이 시간은 마스터 계정이 스마트 컨트랙트를 호출해서 시간 설정을 바꿀 수 있는데, 실제 서비스를 제공한다면 일주일 정도의 기부 기간을 잡으면 된다고 생각한다.
첫 기부가 일어난 후로 3분이 지나니까 Locking으로 변했음을 알 수 있다. 해당 상태에서는, 게시물에 더이상 기부를 할 수 없다. 그리고 해당 기부금을 아직 게시글 작성자가 회수해가지 않았음을 뜻한다.
이렇게 게시글 작성자 아이디로 다시 접속해서 똑같은 게시글에 들어가면 Locking이 아닌 Lock 버튼이 활성화됨을 알 수 있다. 이 버튼을 누르게 되면, 기부금을 받으면서 최종적으로 기부 사이클이 종료된다. 트랜잭션이 체결되면 빨간색으로 기부금을 전부 회수했음을 알려준다. 실제로 Lock을 누르기 전에는 1 HP 토큰이었는데, 기부금 총액만큼이 늘어난 1.485 HP 토큰으로 변했음을 확인할 수 있다.
이제 내부 지갑에 있는 HP 토큰을 외부 지갑으로 옮길 것인데, 그에 앞서서 메타마스크에 HP 스마트 컨트랙트 주소를 추가하여 메타마스크에서 해당 토큰이 보일 수 있게 미리 준비해둔다.
우측 상단의 Withdraw To External Wallet을 클릭하면 인출하고 싶은 토큰 금액을 입력할 수 있다. 위에서 설명했듯이, 처음 연결한 외부 지갑 외에는 아예 다른 지갑 주소는 적을 수도 없게 만들어서 지정된 외부 지갑으로만 토큰 인출이 가능하게 만들었다.
인증된 외부 지갑으로 0.8 HP 토큰이 잘 들어왔고, 내부 지갑에서는 그만큼 토큰 개수가 줄어든 것을 확인할 수 있다.


Keep, Problem, Try

Keep

  • 이전 프로젝트에서는 스마트 컨트랙트에 기록된 정보를 로컬 데이터베이스에 업데이트 하는 프로그램과 로컬 데이터베이스를 이용해서 들어오는 요청에 응답하는 웹서버 프로그램 두개를 따로 작동시키는 방식으로 구현했다. 이것에 대한 아쉬움이 있었는데, 이번 프로젝트에서는 서버 프로그램 하나만으로 mongoDB Atlas를 활용해서 데이터베이스를 관리하고 스마트 컨트랙트에서 event가 emit 될 때마다 서버에서 해당 정보를 업데이트 하는 식으로 구현할 수 있었다.
  • 똑같은 기능을 클라이언트, 서버, 스마트 컨트랙트 모두에서 할 수 있는 상황이 있었다. 예를 들어서, 일정 금액 이상으로만 토큰이 인출되게 하는 기능을 도입했는데 이는 세 곳에서 모두 처리할 수 있었는데 한쪽에서 문제가 발생할 수 있으므로 각각에서 똑같은 기능을 모두 구현했다. 한 곳이 오작동했을 때 치명적인 문제가 발생할 수 있는 경우에는 여러 곳에서 안전 장치를 마련해두는 것은 좋은 것 같다.
  • 에러가 발생했을 때 에러에 출력된 내용들을 읽어보면서 어떤 방식으로 해결하는 게 좋을지 차분히 생각해보는 것이 중요한 것 같다. 그리고 테스트하는 동안 올려놓은 서버가 완전히 뻗어버리는 상황이 발생했는데, 이때 정상적으로 동작되지 않을 법한 부분들을 검토해서 에러 핸들링을 했다. 이런식으로 최대한 예측 가능한 에러들에 대응책을 마련해두는 것이 서버를 이용해 테스트하는 입장에서 매우 중요함을 배웠다.

Problem

  • 팀원 간의 소통은 그 무엇보다 중요하다. 온라인으로 진행된 프로젝트임에도 디스코드, 노션, 줌을 통해서 정말 활발한 소통이 이뤄졌다고 생각한다. 하지만 프로젝트가 진행되면서 각자의 역할대로 집중하고 있는 부분이 다른 상황에서, 기존에 이야기했던 것과 다르게 수정되거나 새로 추가된 부분은 꼭 문서화해야 한다는 생각이 들었다. 예를 들어서, 원래는 기부를 할 수 있는 시스템이 아니라 커뮤니티에서 게시글을 작성한 것에 대한 보상으로 토큰을 민팅해 주는 것이 초안이었다. 그런데 이렇게 할 경우에 단순히 보상을 받기 위해서 품질이 낮은 게시글이 대량 생산될 수도 있다는 생각에 다른 사용자들의 인정을 받아야 하는 기부 형태로 바뀌게 되었다. 그리고 게시물이 올라간 직후부터 카운트 다운을 세는 것으로 기능을 다 구현했는데, 결과적으로 첫 기부가 일어난 시점부터 카운트 다운이 시작되는 것으로 바뀌게 되었다. 이렇게 된 까닭은 처음에는 게시물이 올라간 직후로 결정했다가, 첫 기부가 시작된 시점으로 바꾸기로 했다가, 다시 게시물이 올라간 직후로 결정해서 코드를 작성했는데 다른 팀원 분이 이 내용을 전달받지 못해서, 최종적으로 다같이 이야기를 한 다음 첫 기부가 시작된 시점으로 결정됐다. 이 과정 속에서 팀원들 중 일부만 참여해서 결정된 사항도 팀원 전체에게 항시 공유를 해야 하며, 공식 회의 시간이 아닌 코딩 중에 나눈 대화도 문서화를 체계적으로 하지 않으면 어디서부터 오해가 발생한 것인지 확인하기 어렵기 때문에 이것이 꼭 필요한 일임을 깨달았다.
  • 프론트엔드에서 사용하기 편하게 데이터를 담아 보내는 것도 생산성을 높이는 일임을 깨달았다. 프론트엔드와 협업할 때 항상 어떤 형태로 데이터를 건네주는 것이 효과적일지를 고민하며, 경험치를 늘려서 처음 API 문서를 작성할 때부터 좋은 구조를 만들 수 있게 노력해야 한다.
  • ether 단위로 인자를 넣었는데 wei 단위였다든지, number 타입을 넣었는데 string 타입였다든지, 주소값을 저장할 때 대문자가 섞여 있었는데 그걸 모르고, 검색으로 찾아지지 않아서 소문자로 바꿔서 저장하고 찾으면 되는데 엄한 곳을 수정하며 시간을 날렸던 적이 있었다. 좀 더 꼼꼼하게 단위, 타입, 저장 형태를 확인해서 괜한 시간 낭비를 하지 않는 게 필요하다.
  • 테스트를 하다보니 특정 게시글 번호에서는 기부 동작이 되지 않은 상황이 있었다. 원인을 찾아보다가 깨달은 것은, 코드가 잘 동작하는지 테스트하려고 데이터베이스에 들어있는 데이터들을 원하는대로 수동으로 수정하며 테스트했다. 그러다보니 스마트 컨트랙트에서는 해당 게시글 번호는 기부가 완료되어 lock 걸린 상태로 저장되어 있는데, 데이터베이스에서는 동일한 게시글 번호로 새로운 게시글로 만들어 저장하므로 문제가 발생한 것이었다. 테스트할 때도 체계적으로 꼼꼼하게 확인하는 것이 필요하다.
  • 서비스를 사용하는 사용자는 예상하지 못한 요청을 할 수 있음을 배웠다. 예를 들어서, 게시글 목록을 불러오는 요청을 할 때는 amount와 num을 통해서 게시글 목록을 부른다. 처음에는 클라이언트는 현재 최대 게시글 번호가 무엇인지 모르므로 amount만 보낸다고 생각해서 이에 대한 응답만 준비했다. 그리고 최대 게시글 번호 이상의 num을 입력하더라도 최신순으로 amount에 맞게 게시물 목록을 보내주는 식으로 작성했다. 그런데 프론트엔드와 이를 맞춰보면서 사용자가 직접 주소창에 num을 넣어 요청할 때, 입력한 num 값이 최대 게시글 번호보다 높으면 아예 에러를 띄우는 것이 더 낫지 않냐는 의견을 받았다. 이에 대해 서로 의견을 공유했고 결론적으로는 기존의 방식을 유지하면서 최대 게시글 번호를 따로 담아서 보내주는 방식으로 해결했다. 응답을 설계할 때 사용자가 직접 주소창에 최대 게시글 번호를 넣어서 요청할 것이라는 생각을 못했고, 설사 그렇게 하더라도 최신 게시글을 보내는 게 에러를 띄우는 것보다 낫다고 생각했는데 그렇게 생각하지 않을 수 있음을 배울 수 있었다.

Try

  • 비동기 처리에 대해 추가적인 공부가 필요함을 느꼈다.
  • 특정 과정이 중간에 중지되면 이전에 일어났던 일들도 다시 되돌려야 하는, atomic 해야 하는 동작을 어떻게 효율적으로 구현할지 더 공부하면 좋을 것 같다.
  • 정해진 기간 안에 일정한 수준 이상의 결과물을 내는 것이, 욕심을 부려서 프로젝트 완성도가 조금 더 올라가더라도 계획을 지키지 못하는 것보다 훨씬 나음을 절감했다.
  • 스마트 컨트랙트 상에서 트랜잭션이 하나라도 체결되지 않은 상황이면 아예 다른 트랜잭션을 일으킬 수 없게 만들어 두었다. 그런데 예를 들어서, 기부를 하는 상황이라면 100 토큰이 있을 때 50토큰을 기부하는 트랜잭션을 일으켰어도, 남은 50토큰에 대해서는 또 다른 트랜잭션을 일으켜도 문제가 없을 것이므로 이러한 상황은 서버가 트랜잭션을 일으킬 수 있게 허가하는 방식으로 해서 사용자 편의성을 높일 수 있으면 좋을 것 같다.
  • 동작이 잘 이뤄지는지 일일이 테스트하는 것이 아니라, 채점 프로그램 식으로 만들어서 더 효율적으로 테스트를 진행할 수 있으면 좋을 것 같다.
  • 해야 할 일 목록을 더 잘게 쪼개어, 그 작업을 완료할 때마다 정확히 무슨 작업이 수정되었는지 적어서 커밋하는 습관을 들여야 된다. 이제까지는 어쩔 때는 조금만 수정해도 커밋하고, 어쩔 때는 더 큰 단위로 묶어서 커밋을 했는데, 그 수준을 적당하게 유지하는 것이 더 낫다는 생각이 들었다.
  • 애프터 이팩트 툴을 사용해서 작업물을 만들어주셨는데, 기회만 된다면 이러한 툴들도 조금 배워보면 좋을 것 같다.

API문서

해당 게시물에 기부하기

POST /donate
  • req.body

{ article_id : 3, amount : 23 }

article_id : Number, amount : Number // ‘ether’ 단위

  • res

{ message : ‘wait, donate processing’ }

해당 게시물에 기부한 거 취소하기

POST /donate/cancel
  • req.body

{ article_id : 3 }

article_id : Number

  • res

{ message : ‘wait, cancel processing’ }

해당 게시물이 락업 기간 지났을 때, 보상 신청하기

POST /donate/reward
  • req.body

{ article_id : 3 }

article_id : Number

  • res

{ message : ‘wait, reward processing’ }


Withdraw

외부 지갑으로 인출하기

POST /withdraw
  • req.body

{ amount : 30 }

amount : Number // ‘ether’ 단위

  • res

{ message : ‘wait, withdraw processing’ }


Connect

외부 지갑 인증

GET /connect
  • res
{
    sign : {
        v : 28,
        r : '0xb881b1c9bbb7538799924db53ad449c5442279b557229c246da4d9107ec90b91',
        s : '0x594b9cd9d71a8826b093596a9e92d31d3c7d3d15929aa6fc67eb644d6b91b6cf',
        hashedMessage : '0xb307c59d340b273a90ddcee676029b882f613c49523c6fc71910a771811a8976'
    },
		message : 'succeed'
}

Login

가입되어있는 계정으로 로그인

POST /login
  • req.body

{ id : ‘testid01’, password : ‘testpassword01’ }

id : String, password : String

  • res

{ access_token : ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImlkMDEiLCJpYXQiOjE2NTEwNDQ4NTIsImV4cCI6MTY1MTEzMTI1Mn0.11fjmczG69COvlcLArW0m2EHFwMGg6gyKTeVwVHybMI’, message : ‘succeed, login’ }


Register

처음 회원가입 신청

POST /register
  • req.body

{ id : ‘testid01’, password : ‘testpassword01’, email : ‘test01@gmail.com’ }

id : String, password : String, email : String

  • res

{ message : ‘succeed, please verify the account’ }


Confirm

이메일을 인증을 한 계정의 회원정보를 최종적으로 DB에 저장

POST /confirm
  • req.body

{ token : ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImlkMDIiLCJwYXNzd29yZCI6IiQyYiQxMCRXR2czSnVPa1B5R0ZnNEt5R1gucUp1UWp4Q04yWHFqNmI5VWJrTDlVb1VQc0NJbDMyMmU4YSIsImVtYWlsIjoiZG83bXk3YmVzdEBnbWFpbC5jb20iLCJpYXQiOjE2NTEwNTk1ODR9.SyYLCl8YIuXS50iQD-C3nKP26lJMECz0rtAeGqYzJek’ }

token : String

  • res

{ access_token : ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImlkMDEiLCJpYXQiOjE2NTEwNDQ4NTIsImV4cCI6MTY1MTEzMTI1Mn0.11fjmczG69COvlcLArW0m2EHFwMGg6gyKTeVwVHybM’, message : ‘succeed’ }


Refresh

access token이 만료되었는데, refresh token은 있을 때, 새로운 access_token 발급

GET /refresh
  • res

{ access_token : ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImlkMDEiLCJpYXQiOjE2NTEwNDQ4NTIsImV4cCI6MTY1MTEzMTI1Mn0.11fjmczG69COvlcLArW0m2EHFwMGg6gyKTeVwVHybMI’, message : ‘succeed’ }


Article

게시물 하나 작성

POST /article
  • req.body

{ article_title : ‘title01’, article_content : ‘content01’ }

article_title : String, article_content : String

  • res

{ article_id : 3, message : ‘succeed, create article’ }

게시물 하나를 읽거나, 게시물 목록을 가져오기

GET /article
  • query parameter를 무엇을 쓰느냐에 크게 세가지 분기점으로 나눌 수 있음.

1) article_id → 해당 번호 게시물의 전체 정보를 가져옴

ex) /article?article_id=3

  • res
{
	max_article_id : 123
	article_id : 3 // 게시물 번호
	article_author : 'testid01' // 게시글 작성자 아이디
	artile_title : 'title01' // 게시글의 제목
	article_views : 27 // 게시글의 조회수
	article_content : 'hello, world' // 게시글의 내용
	article_donated : 234 // 게시글 기부받은 총 토큰 ('ether' 단위)
	article_donate_status : 1 // 0 : 아무도 기부 안 함, 1: 기부 가능, 2: 기부 더이상 불가능, 보상은 신청하기 전 3: 기부는 불가능하고, 보상도 받은 상황
	article_donate_remain_time : '4D 20:40:30' // 기부 불가능 시점까지 남은 시간
	article_created_at : 2022-04-22T07:50:54.883Z // 게시글 생성 시간
	article_updated_at : 2022-04-27T17:55:34.452Z // 게시글의 가장 최근 업데이트 시간
	article_comments : [
												{max_author_article_id : 4},
												{
													new_id : 2, // 이 응답에서, 해당 덧글의 순서
													comment_id : 4 // 덧글의 고유 번호
													comment_author : 'testid02' // 덧글 작성자
													comment_title : 'titleabc' // 덧글 제목
													comment_content : 'Good' // 덧글 내용
													comment_created_at : 2022-04-23T07:50:54.542Z
													comment_updated_at : 2022-04-24T17:34:22.883Z
												}
												, //...
											]

}

2) article_author → 특정 사용자가 작성한 모든 게시물 목록을 가져옴

ex) /article?article_author=testid03

  • res
[
	{
		new_id : 2
		article_id: 3
		article_author : 'testid03'
		article_title : 'test article'
		article_views : 23
		article_created_at : 2022-03-25T12:20:41.243Z
		article_updated_at : 2022-04-21T07:50:54.883Z
	}
	,//...
]

3) amount → 불러들일 게시물 목록의 최대 개수를 몇 개로 할 것인지 결정해서 가져옴(최근에 작성된 순으로 가져옴)

ex1) /article?amount=30 → 가장 최근 게시물을 최대 30개 가져옴

ex2) /article?amount=30&num=85 → 게시물 번호 85 번 이전 중에서(게시물 번호가 85번 이하인 것들. 게시물 번호가 작을수록 오래된 게시물), 가장 최근에 가까운 게시물을 최대 30개 가져옴. 그리고 이 요청을 한 계정의 정보도 함께 가져옴

  • res
{
	user: {
		id : 'testid01'
		email : 'test@gmail.com'
		internal_pub_key : '0xF1fB7114Dc87CeB4efC0D6a5dbc05b042412745F'
		external_pub_key : '0x16EB1675CEBE17DfA1920e836962952430c5c9F0'
		amount : 32 // 'ether' 단위
		level : 22
		action : 0 // 0: 다른 행동 가능, 1: donate, 2: cancel, 3: reward, 4: withdraw
		donate_article_id : [3, 5, 12] // 해당 유저가 기부한 게시물들 중에서, 환불 가능한 상태의 게시물들만 보내줌(게시물 작성자가 reward 신청하기 전까지는 락업이 시작되면 기부는 못해도, 환불은 가능함)
		user_article: [ //해당 지갑 소유자가 작성한 모든 게시물들 목록
										{
											new_id : 2
											article_id: 3
											article_author : 'testid01'
											article_title : 'test article'
											article_views : 23
											article_reward : true // reward를 받을 수 있는데, 아직 신청하지 않은 article이면 true. 아니면 false
											article_created_at : 생성 일자
											article_updated_at : 최근 업데이트 일자
										}
										,//...
									]
	},
	max_article_id : 123,
	articles: [{
		new_id : 1
		article_id : 2
		article_author : 'testid02'
		article_title : 'test article'
		article_views : 38
		article_donate_status : 1 // 0 : 아무도 도네이션 안 함, 1: 도네이션 가능, 2: 도네이션 더이상 불가능, reward는 신청하기 전 3: 도네이션은 불가능하고, reward도 받은 상황
		article_donate_remain_time : '4D 20:40:30' // 도네이션 불가능 시점까지 남은 시간
		article_created_at : 생성 일자
		article_updated_at : 최근 업데이트 일자	
	},
	{
		new_id : 2
		article_id : 3
		article_author : 'testid01'
		article_title : 'test article'
		article_views : 23
		article_donate_status : 1 // 0 : 아무도 도네이션 안 함, 1: 도네이션 가능, 2: 도네이션 더이상 불가능, reward는 신청하기 전 3: 도네이션은 불가능하고, reward도 받은 상황
		article_donate_remain_time : '4D 20:40:30' // 도네이션 불가능 시점까지 남은 시간
		article_created_at : 생성 일자
		article_updated_at : 최근 업데이트 일자	
	}
	,//...
	]
}

4) query parameter가 아예 없는 경우

→ 에러 처리함

게시물 수정

PUT /article
  • req.body

{ article_id : 3, article_title : ‘updated_title’, article_content : ‘updated_content’ }

article_id : Number, article_title : String, article_content : String

  • res

{ message : ‘succeed, update’ }

게시물 삭제

DELETE /article
  • req.body

{ article_id : 3 }

article_id : Number

  • res

{ message : ‘wait, delete processing’ }


Comment

답글 하나 작성

POST /comment
  • req.body

{ article_id : 5, comment_title : ‘first comment’, comment_content : ‘hello world content’ }

article_id : Number, comment_title : String, comment_content : String

  • res

{ article_id : 5, comment_id : 2, message : ‘succeed’ }

현재 요청을 한 계정으로 작성한 답글들 모두 불러오기

GET /comment
  • res
[
	{max_comment_id : 4},
	{
		new_id : 1
		comment_id : 42
		article_id : 2
		comment_author : 'testid01'
		comment_title : 'first comment'
		comment_content : 'hello everybody'
		comment_created_at : 생성 일자
		comment_updated_at : 가장 최근 업데이트 일자
	}
	,{
		new_id : 2
		comment_id : 45
		article_id : 3
		comment_author : 'testid01'
		comment_title : 'title01'
		comment_content : 'lalala'
		comment_created_at : 생성 일자
		comment_updated_at : 가장 최근 업데이트 일자
	}
	, //...
]

답글 하나 수정

PUT /comment
  • req.body

{ article_id : 3, comment_id : 8, comment_title : ‘updated_title’, comment_content : ‘updated_content’ }

article_id : Number, comment_id : Number, comment_title : String, comment_content : String

  • res

{ message : ‘succeed’ }

답글 하나 삭제

DELETE /comment
  • req.body

{ article_id : 3, comment_id : 14}

article_id : Number, comment_id : Number

  • res

{ message : ‘succeed’ }

profile
I can do this all day

0개의 댓글