기존에는 db-query.js 파일 내에서 직접 SQL쿼리를 통해 정보를 가져오고 수정하였다.
var db = require('./db-connect')
var useCrypto = require('./crypto');
var getList = function(amount,type,page,callback){
// 전체 글 조회
if (type=='all'){
db.query('SELECT COUNT(*) AS COUNT FROM POST',function(err,count){
if (err)
console.log('에러 발생 : ' + err);
else{
count = count[0].COUNT;
var maxPage = Math.ceil(count / amount);
db.query('SELECT * FROM POST ORDER BY ID DESC LIMIT ?, ?',[(page-1)*amount,amount],function(err,result){
if (err)
console.log('에러 발생 : ' + err);
else
callback(result,maxPage);
});
}
});
// 특정 게시판 글 조회
} else {
db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE TYPE=?',[type],function(err,count){
if (err)
console.log('에러 발생 : ' + err);
else{
count = count[0].COUNT;
var maxPage = Math.ceil(count / amount);
db.query('SELECT * FROM POST WHERE TYPE=? ORDER BY ID DESC LIMIT ?, ?',[type,(page-1)*amount,amount],function(err,result){
if (err)
console.log('에러 발생 : ' + err);
else
callback(result,maxPage);
});
}
});
}
}
var viewPost = function(id,callback){
// 조회수 증가
db.query('UPDATE POST SET VIEWS=VIEWS+1 WHERE ID=?',[id],function(){});
// 글 조회
db.query('SELECT * FROM POST WHERE ID=?',[id],function(err,post){
if(err || post[0] === undefined){
console.log('에러 발생 : ' + err);
callback(false);
} else{
callback(post[0]);
}
});
}
var writePost = function(data,callback){
// 쿼리에 insert
db.query('INSERT INTO POST (`TITLE`, `AUTHOR`, `TYPE`, `CONTENT`, `PASSWORD` , `isLogined`) VALUES (?, ?, ?, ?, ?, ?)',
[data.title,data.author,data.type,data.content,data.password,data.isLogined],function(err){
if (err){
console.log(err);
callback(false);
}
else
callback(true);
});
}
var checkPassword = function(id,plainPassword,callback){
useCrypto(plainPassword,function(password){
db.query('SELECT PASSWORD FROM POST WHERE ID=?',[id],function(err,savedPassword){
if (savedPassword[0].PASSWORD==password)
callback(true);
else
callback(false);
});
});
}
var deletePost = function(id,callback){
db.query('DELETE FROM POST WHERE ID=?',[id],(err)=>{
if (err)
callback(false);
else
callback(true);
});
};
var viewForUpdatePost = function(id,callback){
db.query('SELECT * FROM POST WHERE ID=?',[id],(err,post)=>{
if (err)
callback(false);
else
callback(post[0]);
});
};
var updatePost = function(data,callback){
db.query('UPDATE POST SET TITLE=? , CONTENT=? WHERE ID=? AND PASSWORD=?',[data.title,data.content,data.id,data.code],(err)=>{
callback(err);
});
}
var searchPost = function(data,amount,callback){
// DB 칼럼명에 맞게 변경
if (data.mode=='제목')
data.mode='TITLE';
else if (data.mode=='작성자')
data.mode='AUTHOR';
else if (data.mode=='내용')
data.mode='CONTENT';
if(data.type=='all'){
db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE ?? LIKE ?',[data.mode,"%"+data.keyword+"%"],(err,count)=>{
if (err){
console.log(err);
callback(false);
}
else{
count = count[0].COUNT;
var maxPage = Math.ceil(count / amount);
db.query('SELECT * FROM POST WHERE ?? LIKE ? ORDER BY ID DESC LIMIT ?, ?',[data.mode,"%"+data.keyword+"%",(data.page-1)*amount,amount],(err,result)=>{
if(err){
callback(false);
}
else{
callback(result,maxPage);
}
});
}
});
} else{
db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE TYPE=? AND ?? LIKE ?',[data.type,data.mode,"%"+data.keyword+"%"],(err,count)=>{
if (err)
callback(false);
else{
count = count[0].COUNT;
var maxPage = Math.ceil(count / amount);
db.query('SELECT * FROM POST WHERE TYPE=? AND ?? LIKE ? ORDER BY ID DESC LIMIT ?, ?',[data.type,data.mode,"%"+data.keyword+"%",(data.page-1)*amount,amount],(err,result)=>{
if(err)
callback(false);
else
callback(result,maxPage);
});
}
});
}
}
var thumbup = function(id,ip,callback){
// 추천수 카운트 변수
var count;
// 해당 글의 추천 수를 새로운 DB에서 카운트 해서 가져오기
db.query('SELECT COUNT(*) AS COUNT FROM THUMBUPS WHERE POST_ID=?',[id],(err,result)=>{
if (err){
errcode = 1
callback(false,errcode);
console.log(err);
return;
}
count = result[0].COUNT;
});
// 해당 글을 추천한 ip리스트 가져오기
db.query('SELECT USER_IP FROM THUMBUPS WHERE POST_ID=?',[id],(err,result)=>{
if (err){
errcode = 2
callback(false,errcode);
console.log(err);
return;
}
// 해당 ip가 이미 추천한 ip인지 확인
for (let savedIp of result)
if (savedIp.USER_IP == ip)
var forDelete = true;
if (forDelete){
// 추천 취소
db.query('DELETE FROM THUMBUPS WHERE POST_ID=? AND USER_IP=?',[id,ip],(err)=>{
if (err){
errcode = 3
console.log(err);
callback(false,errcode);
return;
}
count--;
// 해당 글의 추천수 조정
db.query('UPDATE POST SET THUMBUP=? WHERE ID=?',[count,id],()=>{});
callback('deleted',count);
});
} else{
// 추천 진행
db.query('INSERT INTO THUMBUPS (`POST_ID`, `USER_IP`) VALUES (?,?)',[id,ip],(err)=>{
if (err){
errcode = 4
callback(false,errcode);
console.log(err);
return;
}
count++;
// 해당 글의 추천수 조정
db.query('UPDATE POST SET THUMBUP=? WHERE ID=?',[count,id],()=>{});
callback('success',count);
});
}
});
}
var innerRight = function(callback){
db.query('SELECT ID, TITLE FROM POST ORDER BY THUMBUP DESC LIMIT 3',(err,bestPosts)=>{
if (err){
callback(false,false);
console.log('1')
}
else{
db.query('SELECT ID, TITLE FROM POST WHERE TYPE=\'공지사항\' ORDER BY ID DESC LIMIT 3', (err,announcements)=>{
if (err){
callback(false,false);
console.log(err)
}
else
callback(bestPosts,announcements);
});
}
});
};
var getReply = function(postId,callback){
db.query('SELECT ID,POST_ID,ROOT_REPLY_ID,AUTHOR,CONTENT,isLogined FROM REPLY WHERE POST_ID=?',[postId],(err,result)=>{
if (err){
console.log(err);
callback(false);
}
else{
callback(result);
}
});
}
var deleteReply = function(id,plainPassword,callback){
useCrypto(plainPassword,(password)=>{
db.query('SELECT PASSWORD FROM REPLY WHERE ID=?',[id],(err,result)=>{
if (err){
callback(1);
console.log(err);
return;
}
if (result[0].PASSWORD == password){
db.query('DELETE FROM REPLY WHERE ID=?',[id],(err)=>{
if (err){
callback(2);
console.log(err);
return;
}
callback(-1);
});
} else{
callback(0);
}
});
});
};
var writeReply = function(data,callback){
useCrypto(data.password,(password)=>{
if (data.rootReplyId == ''){
db.query('INSERT INTO REPLY (`POST_ID` ,`AUTHOR`, `CONTENT`, `isLogined`, `PASSWORD`) VALUES (?, ?, ?, ?, ?)',[data.postId,data.writer,data.content,data.isLogined,password],(err)=>{
if(err){
callback(1);
console.log(err);
return;
} else{
callback(-1);
}
});
} else{
db.query('INSERT INTO REPLY (`POST_ID` ,`AUTHOR`, `CONTENT`, `isLogined`, `ROOT_REPLY_ID`, `PASSWORD`) VALUES (?, ?, ?, ?, ?, ?)',[data.postId,data.writer,data.content,data.isLogined,data.rootReplyId,password],(err)=>{
if(err){
callback(1);
console.log(err);
return;
} else{
callback(-1);
}
});
}
});
};
var findUser = function(id,callback){
db.query('SELECT * FROM USER WHERE USER_CODE=?',[id],(err,result)=>{
if(err){
callback(1);
console.log(err);
return;
} else{
if (result.length == 1){
callback(result[0])
} else if (result.length == 0){
callback(0);
}
}
});
}
var newUser = function(id,nickname,callback){
db.query('INSERT INTO USER (`USER_CODE`, `NICKNAME`, `GROUP`) VALUES (?,?,?)',[id,nickname,'USER'],(err)=>{
if(err){
if (err.errno == 1062)
callback(1);
else
callback(0);
} else{
callback(-1);
}
})
}
var checkIsLogined = function(postId,callback){
db.query('SELECT isLogined FROM POST WHERE ID=?',[postId],(err,result)=>{
if(err){
callback(-1);
console.log(err);
return;
} else {
if(result.length == 1){
callback(result[0].isLogined);
} else {
callback(-2);
return;
}
}
});
}
그러나 기존 db-query.js 코드를 보면 알 수 있듯이 매우 가독성이 떨어지고, 오류 처리도 제각각이라 관리하기가 힘들었다. 그래서 객체지향적으로 DB를 관리하기 위해 ORM을 알아보던 중 nodejs에서 mysql을 사용할 때는 Sequelize라는 ORM을 많이 사용한다는 것을 알게 되었다.
기존의 방식에서 Sequelize 로 전환하기 위해서는 먼저 기존에 존재하던 테이블에 따라 모델을 추출해야 한다.
sequelize-auto를 사용하면 기존에 존재하던 테이블을 쉽게 모델로 추출할 수 있다.
https://github.com/sequelize/sequelize-auto
먼저 npm을 통해 sequelize를 설치한다.
// Sequelize와 mysql2 설치
npm install sequelize mysql2
// Sequelize cli 설치
npm install -g sequelize-cli
이후 프로젝트 폴더에서 초기화를 진행해준다.
// Sequelize 초기화
sequelize init
그러면 프로젝트 폴더에 Config 파일과 구조 폴더들이 생성된 것을 확인할 수 있다.
이제 원래 DB에서 모델을 추출한다.
sequelize-auto -h <host> -d <database> -u <user> -x [password] -p [port] --dialect [dialect] -c [/path/to/config] -o [/path/to/models] -t [tableName]
github 에는 위의 명령어를 기본적으로 사용하도록 되어 있다. 내가 사용한 명령어는
sequelize-auto -h localhost -d nodeboard -u root -x 111111 -p 3306 -o "./models"
테이블명을 입력하지 않으면 모든 테이블을 가져온다. 이러면
이렇게 모델이 추출된다.
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('POST', {
ID: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
TITLE: {
type: DataTypes.STRING(45),
allowNull: false
},
AUTHOR: {
type: DataTypes.STRING(45),
allowNull: false
},
TYPE: {
type: DataTypes.STRING(20),
allowNull: false
},
CONTENT: {
type: DataTypes.TEXT,
allowNull: false
},
VIEWS: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
PASSWORD: {
type: DataTypes.STRING(100),
allowNull: true
},
isLogined: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
THUMBUP: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
}
}, {
sequelize,
tableName: 'POST',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "ID" },
]
},
]
});
};
각 모델은 대충 이런식으로 되어 있다. 칼럼과 그에 대한 정보가 나와 있다.
이제 기존 db-query를 새로운 ORM을 적용함에 따라 코드를 다 뜯어고쳐야 한다.
SELECT (findAll, findOne)
// 글 가져오기
async function getList(type,page){
if (type==='all'){
return models.POST.findAll({
attributes:['ID','AUTHOR','isLogined','TITLE','VIEWS','THUMBUP'],
order:[['ID','DESC']],
limit: 20,
offset: (page-1)*20
});
} else {
return models.POST.findAll({
attributes:['ID','AUTHOR','isLogined','TITLE','VIEWS','THUMBUP'],
where:{
type: type
},
order:[['ID','DESC']],
limit: 20,
offset: (page-1)*20
});
}
}
기존 SQL의 SELECT문은 Sequelize 의 findAll 혹은 findOne 을 사용하면 된다. 한눈에 봐도 알 수 있듯이 가져올 칼럼, 정렬 기준, 가져올 개수, 스킵할 개수를 옵션으로서 정할 수 있다.
// 인증글 여부 확인
async function checkIsLogined(id){
const check = await models.POST.findOne({
attributes: ['isLogined'],
where: {ID: id}
});
return check.dataValues.isLogined;
}
findOne도 받는 인자는 같다. findAll에서 limit 1이 기본적으로 적용되어 있다고 보면 된다.
INSERT (create)
// 댓글 작성
async function writeReply(data){
if (data.rootReplyId == '') data.rootReplyId = null;
return models.REPLY.create({
POST_ID:data.postId,
ROOT_REPLY_ID:data.rootReplyId,
AUTHOR:data.writer,
CONTENT:data.content,
isLogined:data.isLogined,
PASSWORD:data.password
});
}
INSERT의 경우 create메서드를 사용하면 된다. create 에는 칼럼명: 데이터, key:value의 방식으로 객체에 정보를 담아서 넘기면 된다.
UPDATE (update)
async function updatePost(data){
return models.POST.update({
TITLE: data.title,
CONTENT: data.content
},{
where:{
ID:data.id,
PASSWORD:data.code
}
});
}
UPDATE는 sequelize에서도 update이다. create메서드 처럼 key:value로 값을 넘겨주는 객체를 첫 인자로 주고, 두번째 인자로는 row를 특정할 수 있는 옵션(where 등등) 을 넘기면 된다.
DELETE (destroy)
// 글 삭제
async function deletePost(id){
// 댓글 삭제
await models.REPLY.destroy({
where: { POST_ID: id}
});
// 글 삭제
return await models.POST.destroy({
where: { ID: id}
});
}
DELETE는 destory 메서드를 사용하면 된다. where 를 통해 row 만 특정해주면 삭제할 수 있다.
'Projects > NodeBoard' 카테고리의 다른 글
Summernote 에디터 적용, imgbb 서버로 게시글 이미지 업로드 (2) | 2021.04.29 |
---|---|
로그인 유저 글쓰기/댓글쓰기 차별화 (0) | 2021.04.05 |
passportjs를 이용한 구글 로그인 (0) | 2021.03.17 |
댓글 답글 기능 구현 (0) | 2021.03.09 |
AJAX로 댓글달기 기능 구현 (5) | 2021.03.06 |
댓글