express
서버 열기sample_project
폴더 생성 (프로젝트 폴더 만들기)
npm init -y
→ npm 시작
npm install express ejs
→ 필요한 npm 모듈 설치
npm install yarn -g
npm으로 오류가 나는 경우가 많은데 그럴 땐 yarn
사용해보자!
npm보다 빠르고 안전하다~
npm, yarn은 자바스크립트 node.js 환경 기반의 Package Manager를 말한다.
yarn add caver-js
→ npm 모듈 설치 (yarn으로 설치)
index.js
파일 생성 → 가장 기본이 되는 자바스크립트 파일
// express 로드
const express = require('express');
const app = express();
// view 파일들의 기본 경로
app.set('views', __dirname+'/views');
// view engine 설정
app.set('view engine', 'ejs');
// post 방식으로 데이터가 들어오는 경우 json 형태로 데이터를 변환
app.use(express.urlencoded({extended: false}));
// dotenv 설정
require('dotenv').config();
const server = app.listen(port, function() {
console.log(port, "Server Start");
});
여기까지가 가장 기본적인 서버 구조!
(server
변수 같은 경우엔... 프로젝트 만들면서 서버가 하나면(?) 굳이 변수 설정을 해 줄 필요는 없다.)
truffle init
init
: 생성자 명령어, 기본 폴더구조 생성)명령어 실행하면
폴더들 생성됨! (파일디렉토리 구조 확인) truffle-config.js
파일 들어가서 버전 바꿔주자!
contracts
파일에 board.sol
파일 생성// SDPX-License-Identifier: MIT
pragma solidity = 0.8.19;
contract board {
// owner 주소 변수 선언
address internal owner;
// 글번호 초기 변수
uint internal content_no;
// 구조체 생성
struct content_info {
string title;
string content;
address writer;
string image;
string create_dt;
}
// mapping 변수 생성
mapping (uint => content_info) internal contents;
// deploy가 될 때 최초로 한 번만 실행이 되는 함수(초기화 함수)
constructor() {
owner = msg.sender;
content_no = 0;
}
// modifier 함수: 함수의 추가적인 행동을 지정할 때 사용
modifier onlyOwner {
require(msg.sender == owner, "Only owner can call function");
_;
}
modifier increament {
_;
content_no++;
}
// 게시판에 글을 추가하는 함수 생성
function add_content(
string memory _title,
string memory _content,
address _writer,
string memory _image,
string memory _create_dt
) public onlyOwner increament {
// mapping data에 값들을 추가한다.
contents[content_no].title = _title;
contents[content_no].content = _content;
contents[content_no].writer = _writer;
contents[content_no].image = _image;
contents[content_no].create_dt = _create_dt;
// 글 번호를 1 증가 → modifier로 처리해버림
// content_no++;
}
// 글의 번호를 출력하는 함수
function view_content_no() public view returns(uint) {
returns (content_no);
}
// 구조체를 리턴하는 함수
function view_content(
// 키 값으로 불러와야!
uint _no
) public view returns (
string memory,
string memory,
address writer,
string memory,
string memory
) {
string memory title = contents[_no].title;
string memory content = contents[_no].content;
address writer = contents[_no].writer;
string memory image = contents[_no].image;
string memory create_dt = contents[_no].create_dt;
return (title, content, writer, image, create_dt);
}
}
정적/동적 차이?
→ 정적 파일: 선언할 때 타입이 있음
Javascript, CSS, Image 등 서비스에서 사용하기 위해 미리 서버에 저장해 놓은 파일
→ 동적 파일: 타입이 없음 (있긴 한데 변환이 가능한지/가능하지 않은지만 있음)
클래스는 무엇일까?
→ 변수와 함수를 모아둔 집합체
→ 솔리디티에서 deploy를 하는 것이 클래스 생성과 비슷하다
→ 배포될 때 constructor
(생성자 함수) 무조건 실행 - 최초로 무조건 실행되니까 이름이나 권한이 필요 없다
modifier
→ 함수의 결합 (해당하는 함수와 modifier
의 결합, modifier
자체도 함수)
→ modifier
의 이름은 필요하지만 권한은 필요 없다 (독립적으로 사용할 수 없기 때문에 권한 필요 x)
→ 괄호를 넣지 않음! 함수인데! 왜? 함수에 추가적으로 붙어있는 것이기 때문에 매개변수를 만들 수 없다
→ 사용하는 이유: 반복되는 코드를 하나로 묶어서 함수로 호출하는 것이 훨씬 보기에 편하니깐~
require
→ 요구를 하는 것! 뭐를? 조건을! (require
= if
)
→ requier
도 함수임
mapping data
에 새로운 값을 집어넣으려면?
→ json 형태의 데이터를 넣는 것과 같음! (새로운) 키 값에 데이터 넣기
→ json안의 json이라고 생각하세용~ (파일 구조 확인)
view
와 pure
차이
→ view
- function
밖의 변수들을 볼 수 있지만 변경은 불가
→ pure
- function
밖의 변수들을 볼 수도, 변경할 수도 없을 때 사용
→ view
는 정적 파일의 끝을 달린다! 어떠한 파일을 return 해줄 것인지 알려줘야 함
truffle compile
처음엔 매개변수에 이건 놀랍게도 성공한 화면... 아마도? warning 메세지는 무시해도 된다고 하신 것 같다. 왤까...? 솔리디티는 이상해!_
안 써서 컴파일 실패가 떴었다.
migrations
폴더에 01_deploy.js
파일 생성 (오타내지 말고 잘 만들어야 한다고 하셨다)
migration
- 계약을 배포하고 변경하는 일련의 과정을 프로그래밍 언어 중 하나인 자바스크립트(js)로 기술한 것
계약을 배포하는 migration 작성
const board = artifacts.require('board');
module.exports = function (deployer) {
deployer.deploy(board).then(function () {
console.log('Contract Deploy');
});
};
✔️ artifacts.require('board')
→ 'board'라는 계약 정보를 읽어온다.
✔️ module.exports
→ 외부에서 모듈을 가져온다
✔️ deployer
: truffle이 제공해주는 배포를 위한 툴
✔️ deployer.deploy(board)
→ deploy()
함수 호출 후 인자로 위에서 읽어온 계약 정보인 board
를 넘겨준다.
→ .then
을 붙여서 콜백함수로 컨트랙트를 배포하기 전 테스트하면 좋다
truffle-config.js
에 아래 코드 추가require('dotenv').config()
const HDWalletProvider = require('truffle-hdwallet-provider-klaytn')
const URL = "https://api.baobab.klaytn.net:8651"
const private_key = process.env.private_key
/* */
// abi 안에 있음
networks: {
baobab : {
provider : new HDWalletProvider(private_key, URL),
network_id : 1001,
gas : 2000000,
gasPrice : null
}
➡️ truffle-hdwallet-provider-klaytn
: truffle-hdwallet-provider에서 파생된 자바스크립트 HD 지갑 제공자
'HD 지갑'이 뭔데?
→ Hierarchiacal Deterministic Wallet, 하나의 마스터 시드(seed) 키를 사용하여 무수히 많은 주소를 생성할 수 있는 암호화폐 지갑 (=계층적 결정 지갑)
→ 왜 사용하는지? 보안적 측면 때문이다
단 하나의 주소값만 사용하면 내가 보내고 받는 트랜잭션을 누군가 지켜보거나 감시할 수도 있기 때문이다. 그래서 HD 지갑에서는 하나의 지갑 내에 여러 개의 주소를 생성하여 알아서 분산 보관해 이러한 점을 어느정도 방지할 수 있다.
https://steemit.com/kr/@goldenman/hd-wallet
https://velog.io/@dik654/HDwallet
.env
에 private_key
환경변수 설정 private_key = '0xde6c6b789783bd02381a71b671f545e435452c86d13395831f419e4dd918a692'
# mysql server 정보
host = '127.0.0.1'
port = 3306
user = 'root'
password = '1234'
database = 'new_deal2'
# session secret key
secret = 'rosencrantz'
지난번 수업 때 작성했던 거 복사해옴 (어차피 똑같이 쓸 것)
require 한 모듈 설치 npm install truffle-hdwallet-provider-klaytn
이제 배포를 합시다 truffle migrate
라고 그냥 하면 에러가 뜬다! (우리는 바오밥에 할 거니까) ➡️ truffle migrate --network baobab
(자동으로 컴파일 후) 배포 완료
truffle migrate
(가나슈에 배포하는 명령어)를 한 후 build
폴더에 board.json
을 확인해보면 abi
의 networks
>> 1001
, 5777
생성
클레이튼의 바보밥 테스트넷, 가나슈에 각각 배포된 컨트랙트 정보를 보여주는 것 (→ 두 개의 address
값이 다르다)
views
, routes
폴더 생성 + 지난번에 만든 token 폴더 쌔벼오기
라우트 나누기 → index.js
→ mysql(DB), 컨트랙트에 접근하는 라우트를 두 개 만들 것임
→ 라우팅: 모듈화 → 기능별로 파일을 나눠서 사용하겠다
→ 주소를 기준으로 해당하는 파일을 가져올 것
// localhost:3000 [get] 요청이 들어왔을 때
app.get('/', function(req, res) {
res.redirect("/user");
});
// routing 사용
const sql = require('./routes/sql.js')();
app.use('/user', sql);
const contract = require('./routes/contract.js')();
app.use('/contract', contract)
mysql 계정 연결을 관리하는 라우터와 contract를 관리하는 라우터로 나눠준다
contract.js
파일 생성 // express 로드
const express = require('express');
// Route() 함수를 변수에 대입
const router = express.Router();
// baobab network에 배포한 컨트랙트를 연동하기 위한 모듈을 로드
const Caver = require('caver-js');
// 컨트랙트의 정보 로드
const contract_info = require('../build/contracts/board.json');
// baobab 네트워크 주소를 입력
const caver = new Caver('https://api.baobab.klaytn.net:8651');
// 배포된 컨트랙트를 연동
const smartcontract = new caver.klay.Contract(
contract_info.abi,
contract_info.networks['1001'].address
)
// 수수료 지불할 지갑을 등록
const account = caver.klay.accouts.createWithAccountKey(
process.env.public_key,
process.env.private_key
)
caver.klay.accounts.wallet.add(account);
➡️ abi = Application Binary Interface : 스마트 컨트랙트 안에 존재하는 함수와 매개변수들을 json 형식으로 나타낸 리스트이다. abi를 사용해 컨트랙트 내의 함수를 호출하거나 컨트랙트로 부터 데이터를 얻을 수 있다.
➡️ createWithAccountKey(address, accountKey)
: 주어진 AccountKey로 Account 인스턴스를 생성한다 (Account는 계정 주소와 AccountKey를 관리하기 위한 클래스)
caver-js에서 사용되는 데이터 구조체일 뿐 Klaytn 네트워크에 게정을 생성하거나 네트워크에 있는 계정을 업데이트 하지는 않는다!
https://ko.docs.klaytn.foundation/content/dapp/sdk/caver-js/v1.4.1/api-references/caver.klay.accounts
.env
에 public_key
만들어주기 (카이카스 지갑 공개 주소)
public_key = '0xA36e88DF42D4Ac17ed695B56502a01e499Bd9982'
contract.js
에 api 생성 module.exports = function() {
// api들 생성
// 해당하는 파일의 api의 기본 경로: loaclhost:3000/contract
// localhost:3000/contract 요청 시 등록된 게시글의 목록을 보여준다
router.get('/', async function(req, res) {
// contract를 이용하여 저장된 모든 데이터를 로드 (오래 걸릴 것 같으니 블로킹 코드 적어주기 → async)
// 저장된 데이터의 개수를 확인하는 함수를 호출
const contents_length = await smartcontract
.methods
.view_content_no()
// view함수는 수수료 발생 x
// 수수료를 낼 지갑의 정보가 필요하지 않다.
.call(); // 바로 콜~
console.log(contents_length);
// 비어 있는 배열을 하나 생성
const data = new Array();
for(let i=0; i<contents_length; i++) {
// view_content() 함수를 호출해서
// return 받을 결과값을 data라는 비어있는 배열에 push
const result = await smartcontract
.methods
.view_content(i)
.call();
data.push(result);
}
console.log(data);
res.render('content_list.ejs', {
'data' : data
})
})
return router;
}
views
에 content_list.ejs
파일 생성 → 컨트랙트에 저장되어 있는 글 목록을 불러올 화면 (간단한 테이블 구성) https://getbootstrap.com/docs/5.3/getting-started/download/
압축 푼 다음 public
폴더 만들어서 파일 옮겨두기
설정 할 때 파일 디렉토리 참고
여기에서 코드 복사
https://code.jquery.com/jquery-3.7.0.min.js
복사 후
content_list.ejs
헤드에 코드 추가
index.js
에서! // 외부의 js, css와 같은 정적 파일
app.use(express.static('public'));
그리고 content_list.ejs
헤드 부분에 코드 추가
아래 코드에는 고쳤는데.. css 링크에서 ../public
까지는 자동으로 설정이 되기 때문에 (index.js
에서 설정해뒀으니까) 생략! /css~
부터 시작하면 된다. 이거 때문에 부트스트랩 오류남...
views
폴더에 header.ejs
만들고 붙여넣기! <!-- 제이쿼리 -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<!-- 부트스트랩 css-->
<link rel="stylesheet" href="/css/bootstrap.min.css" />
그리고 자바스크립트 코드로 붙여넣어준다.
content_list.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<%- include('header.ejs')%>
</head>
<body></body>
</html>
→ 관리하기 쉽게 하려고 header.ejs
로 분리해서 사용해준다.
content_list.ejs
본격적으로 작성!<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<%- include('header.ejs')%>
</head>
<body>
<!-- 글목록 table -->
<!-- 서버에서 글목록 data라는 변수에 담아서 유저에게 보내는 상황
data 변수의 데이터 타입 : list
[
{
'0' : title,
'1' : content,
'2' : writer,
'3' : image,
'4' : create_dt
}, ...
] -->
<div class="container">
<table class="table table-dark">
<tr>
<th>No</th>
<th>Title</th>
<th>Writer</th>
<th>Create_dt</th>
</tr>
<!-- 데이터가 존재하지 않는다면 -->
<%if (data.length == 0) {%>
<tr>
<td colspan="4">저장된 게시글이 존재하지 않습니다.</td>
</tr>
<%} else {%>
<!-- 데이터가 존재하는 경우 -->
<%for (let i=0; i<data.length; i++) {%>
<tr>
<td><%=i+1%></td>
<td><%=data[i]['0']%></td>
<td><%=data[i]['2']%></td>
<td><%=data[i]['4']%></td>
</tr>
<%}%>
<%}%>
</table>
</div>
</body>
</html>
숫자 키 값은 꼭 [''] 이 형태로 불러와야 한다! 안그러면 에러가 나요~
class를 사용하는 이유? - css 선택자
1. 같은 클래스 이름을 중복적으로 사용할 수 있다
2. 여러 개의 클래스를 하나의 태그로 로드할 수 있다 (두 개 이상 아마도 다섯 개 이하의 클래스를 동시에 사용할 수 있다)
→ 부트스트랩으로 테이블 예쁘게 꾸며줬다!
nodemon index.js
잘 나옴... 사실 오타 개많았어서 많이 고침 ㅎㅎ
contract.js
module.export
부분에 코드 추가 // 글의 내용을 추가할 수 있는 페이지
// localhost:3000/contract/add [get]
router.get('/add', function(req, res) {
res.render('add_content.ejs');
});
content_list.ejs
에 글쓰기 버튼 추가 <div class="container">
<button onclick="location.href = '/container/add'">
글쓰기
</button>
</div>
add_content.ejs
파일 생성 <!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<%- include('header.ejs')%>
</head>
<body>
<div class="container">
<form action="/contract/add2" method="post">
<span class="input-group-text" id="basic1">Title</span>
<input type="text" name="_title" class="form-control" aria-describedby="basic1" />
<span class="input-group-text" id="basic2">Content</span>
<input type="text" name="_content" class="form-control" aria-describedby="basic2" />
<span class="input-group-text" id="basic3">Writer</span>
<input type="text" name="_content" class="form-control" aria-describedby="basic3" />
<span class="input-group-text" id="basic4">Image</span>
<input type="text" name="_image" class="form-control" aria-describedby="basic4" />
<input type="submit" value="글 등록" class="btn-primary" />
</form>
</div>
</body>
</html>
contract.js
에 라우터 추가 // moment 모듈 로드
const moment = require('moment');
const date = moment();
/* */
// 글의 내용들을 contract를 이용하여 저장하는 api
// localhost:3000/contract/add2 [post]
router.post('/add2', async function (req, res) {
// 유저가 보낸 데이터를 변수에 대입, 확인
const input_title = req.body._title;
const input_content = req.body._content;
const input_writer = account.address;
const input_image = req.body._image;
console.log(input_title, input_content, input_writer, input_image);
// 현재시간
const create_dt = date.format('YYYY-MM-DD HH:mm');
console.log(create_dt);
// smartcontract에 있는 함수를 호출하여 데이터를 저장
const receipt = await smartcontract
.methods
.add_content(
input_title,
input_content,
input_writer,
input_image,
create_dt
)
.send(
{
from : account.address,
gax : 2000000
}
)
console.log(receipt);
res.redirect('/contract')
});
➡️ moment
: 자바스크립트에서 사용되는 날짜 관련 라이브러리 중 가장 많이 사용되었던 라이브러리
https://jsikim1.tistory.com/195
npm install moment
해서 모듈 설치 후 입력값을 주면 등록되어 테이블에 목록이 뜬다! 끔찍한 css는 넘어가도록 해요...
콘솔 창의 결과도 확인!
contract.js
에서 const
로 정의한 date
를 let
으로 바꾸고
이쪽에 복붙해주세요 let date = moment();
그럼 문제 해결~!
데이터 구조 한 번 확인~!
content_list.ejs
)이동함
contract.js
에 라우터 추가 // 글의 상세정보를 보여주는 페이지 api
// localhost:3000/contract/view [get]
router.get('/view/:_no', function (req, res) {
const input_no = req.params._no;
console.log(input_no);
res.send(input_no);
});
들어온당~~!!!
// 글의 상세정보를 보여주는 페이지 api
// localhost:3000/contract/view [get]
router.get('/view/:_no', async function (req, res) {
const input_no = req.params._no;
console.log(input_no);
// res.send(input_no); // 확인 했으니까 주석처리
const data = await smartcontract
.methods
.view_content(input_no)
.call();
res.render('view_content.ejs', {
'data' : data
})
});
마저 완성 후 화면을 만들러 간다~
views
에 view_content.ejs
파일 생성 (글 정보 보여줄 화면)<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<%- include('header.ejs')%>
</head>
<body>
<div class="container">
<ul class="list-group">
<li class="list-group-item">Title : <%=data['0']%></li>
<li class="list-group-item">Content: <%=data['1']%></li>
<li class="list-group-item">Writer: <%=data['2']%></li>
<li class="list-group-item">Image: <img src="<%=data['3']%>"></img>
<li class="list-group-item">Create_dt: <%=data['4']%></li>
</ul>
<button onclick="location.href='/contract'" class="btn btn-danger">글 목록</button>
<button onclick="window.history.back()">뒤로가기</button>
</div>
</body>
</html>
하면~!!
이렇게 뜬다...
뭐야 내가 가져온 사진이지만 개이쁨... 저도 마실래요
add_content.ejs
수정 contract.js
에 파일 업로드 설정 // 파일업로드 시 사용하는 모듈 (multer)
const multer = require('multer');
const storage = multer.diskStorage(
{
destination: function(req, file, cb) {
cb(null, 'public/upload/');
},
filename: function(req, file, cb) {
cb(null, file.originalname);
}
}
);
const upload = multer({
storage: storage
})
➡️ multer
: 파일 업로드를 위해 사용되는 node.js의 미들웨어
→ multipart/form-data 형식으로 단일 및 다중 파일 업로드를 지원하기 때문에 가장 많이 사용한다
https://www.npmjs.com/package/multer
그리고 다시 테스트! (당연히 모듈 설치 해줘야 함 npm install multer
)
콘솔에 데이터 업로드 잘 됐는지 확인!
(주소 안 바꿔줘서... 안뜨는거 당연함...)
view_content.ejs
파일 업로드 코드 수정해준 담에 보면~
대신 앞에 주소로 해둔 건 안 뜬다. (당연함 수정했으니까) 암튼 끝~~~