오늘 작업한 것에 얘기하기전에 팀원분이 발견한 중요한 설정에 관련된 오류가 있었다. sequelize를 사용할 때, 각 model의 이름을 대문자로 하면 발생하는 오류였다. sequelize 는 기본적으로 model의 이름을 소문자로 정의하고 있기 때문에 만약 대문자로 작성하여 코드에서 해당 model 에 create()
를 통해 값을 추가하게 되면 테이블의 field 가 소/대문자로 나누어져 값이 두번 들어갈 수 있다.
우리 trails
테이블을 예로 들면
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| locationId | int(11) | YES | MUL | NULL | |
| userId | int(11) | YES | MUL | NULL | |
| categoryId | int(11) | YES | MUL | NULL | |
| imageId | int(11) | YES | MUL | NULL | |
| title | varchar(255) | NO | | NULL | |
| review | varchar(255) | NO | | NULL | |
| adminDistrict | varchar(255) | NO | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
| LocationId | int(11) | YES | MUL | NULL | |
| UserId | int(11) | YES | MUL | NULL | |
| CategoryId | int(11) | YES | MUL | NULL | |
| ImageId | int(11) | YES | MUL | NULL | |
+---------------+--------------+------+-----+---------+----------------+
이렇게 Foreign Key 들이 중복되어 들어갈 수도 있다. 항상 이름은 원하는 convention 에 맞추어서 작성하는 것이 중요하다는 걸 느꼈다.
오늘 작업한 부분은 POST /trails
파트이기에 GET
메소드는 제외하였다.
아래 코드는 처음 작성했던 post()
메소드이다.
/* eslint-disable no-console */
/* eslint-disable object-shorthand */
const jwt = require('jsonwebtoken');
const secretObj = require('../../config/jwt');
const {
trails,
users,
locations,
categories,
} = require('../../models');
module.exports = {
/**
* 새로운 산책로를 생성할 때 보내는 post 요청.
* 필요한 데이터: token(decoded -> (userId, email 포함)으로 인증,
* location1-5, category.tag, Image.fileName&Path, title, review, adminDistrict
* 1. 먼저 토큰의 유무를 확인한다. -> 없으면 401
* 2. 토큰을 통해 userId 를 받는다.
* 3. 그 외 정보들을 먼저 DB에 create() 한다.
* 4. DB에 create() 한 dataValues들에서 ID를 가져와서 trail 테이블의 Mul 로 추가하여 create()
* 5. status 201
*/
post: async (req, res) => {
const {
newLocations, imageId, tag, title, review, adminDistrict,
} = req.body;
// verify token -> 없으면 send 401
const token = req.cookies.user;
const decoded = jwt.verify(token, secretObj.secret);
// locations
const createLocation = await locations.create({
location1: newLocations[0],
location2: newLocations[1],
location3: newLocations[2],
location4: newLocations[3],
location5: newLocations[4],
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// categories
const createCategory = await categories.create({
tag: tag,
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// trails
const createTrail = await trails.create({
userId: decoded.userId,
locationId: createLocation.dataValues.id,
categoryId: createCategory.dataValues.id
imageId: imageId,
title: title,
review: review,
adminDistrict: adminDistrict,
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// send trails
res.json(createTrail.dataValues);
},
};
POST /trails
새로운 산책로를 추가하는 요청을 보내기 때문에 보내오는 요청의 정보가 많다: 토큰에 포함된 작성자의 id(userId), 설정한 좌표들의 모음(newLocations), 같이 업로드하는 사진파일의 id(imageId), 추가하려는 산책로의 제목(title), 후기(review), 행정구역(adminDistrict)이 있다..
JSON 형태로 보낸 예시를 보면 아래와 같다. (Postman 을 통해 보낸 fakedata)
{
"newLocations": "[[1,1],[2,2],[3,3],[4,4],[5,5]]",
"tag": "night view",
"imageId": "1",
"title": "my stroll",
"review": "so good",
"adminDistrict": "seoul"
}
일단 post()
메소드의 첫줄부터 살펴보면, 처음에는 destructuring 을 이용하여 req.body
의 값들을 할당하였다.
const {
newLocations, imageId, tag, title, review, adminDistrict,
} = req.body;
그 후 token
을 verify()
를 이용하여 확인하였다.
const token = req.cookies.user;
const decoded = jwt.verify(token, secretObj.secret);
//decoded 는 { userId: 1, email: 'test@test.com', iat: 1579355633, exp: 1579357433 } 와 같은 값을 가지고 있다.
여기에 포함시킨 userId
는 trails
테이블에 추가해준다.
그 다음은 요청에서 받아온 newLocations
안의 각 좌표들을 locations
테이블에 추가하였다.
const createLocation = await locations.create({
location1: newLocations[0],
location2: newLocations[1],
location3: newLocations[2],
location4: newLocations[3],
location5: newLocations[4],
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
그런데 이렇게 코드를 작동시키면 locations
테이블에 값들이 원하는대로 들어가지 않았다.
+----+-----------+-----------+-----------+-----------+-----------+---------------------+---------------------+
| id | location1 | location2 | location3 | location4 | location5 | createdAt | updatedAt |
+----+-----------+-----------+-----------+-----------+-----------+---------------------+---------------------+
| 1 | 6 | 7 | 8 | 9 | 10 | 2020-01-16 10:59:06 | 2020-01-16 10:59:06 |
| 2 | 6 | 7 | 8 | 9 | 10 | 2020-01-16 11:01:28 | 2020-01-16 11:01:28 |
| 3 | [ | [ | 1 | , | 1 | 2020-01-18 12:41:42 | 2020-01-18 12:41:42 |
+----+-----------+-----------+-----------+-----------+-----------+---------------------+---------------------+
```
마지막 레코드를 보면 요청에서 받아온 값이 JSON 형태였기 때문에 발생한 것을 알 수 있다. 그래서 아래와 같이 ```parse``` 후 다시 ```stringify``` 하는 과정을 거쳐야 했다.
```js
const {
imageId, tag, title, review, adminDistrict,
} = req.body;
const newLocations = JSON.parse(req.body.newLocations);
.
.
.
const createLocation = await locations.create({
location1: JSON.stringify(newLocations[0]),
location2: JSON.stringify(newLocations[1]),
location3: JSON.stringify(newLocations[2]),
location4: JSON.stringify(newLocations[3]),
location5: JSON.stringify(newLocations[4]),
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
categories
테이블에 요청받은 tag
의 값을 넣었다.
const createCategory = await categories.create({
tag: tag,
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
그런데 이미 존재하는 태그는 그 값을 찾아서 id를 가져오고 새로운 태그라면 새로 생성하는 편이 효율적이라는 생각이 들어 아래와 같이 findOrCreate
를 이용하여 수정하였다.
const [createCategory, created] = await categories.findOrCreate({
where: {
tag: tag,
},
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
이 때, 원래 findOrCreate()
는 .then
방식처럼 .spread
란 블럭을 사용하여 탐색하거나 생성한 값을 불러오는데 우리는 현재 async/await
방식을 사용중이라 이 부분을 어떻게 처리하는지 찾아봐야했다. 이곳에서 말한 방식처럼 destructuring 을 통해 해결할 수 있었다.
이렇게 할당해온 값들을 모아 아래처럼 trails
테이블에 추가할 수 있었다.
// trails
const createTrail = await trails.create({
userId: decoded.userId,
locationId: createLocation.dataValues.id,
categoryId: createCategory.dataValues.id || created.dataValues.id,
imageId: imageId,
title: title,
review: review,
adminDistrict: adminDistrict,
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// send trails
res.json(createTrail.dataValues);
우리가 보낸 응답을 Postman 을 통해 확인하면 아래(예시)와 같이 나온걸 확인할 수 있었다.
{
"id": 12,
"userId": 1,
"locationId": 18,
"categoryId": 9,
"imageId": "1",
"title": "my stroll",
"review": "so good",
"adminDistrict": "seoul",
"updatedAt": "2020-01-18T13:53:55.910Z",
"createdAt": "2020-01-18T13:53:55.910Z"
}
포스트를 마무리지으려 했는데, 지금 막 팀원으로부터 token이 만료될 때의 상황을처리를 해야할 것 같다는 연락이 왔다. npm에서 jsonwebtoken 을 검색하여 에러 핸들링을 찾아보니 이곳에서는 우리처럼 decoded
를 할당하지 않고 함수안의 err
와 함께 콜백의 인자로 사용하고 있었다. 그래서 콜백안에 if 문으로 조건을 추가하여 그 아랫줄의 코드를 조건문에 포함시켰다.
post: (req, res) => {
const token = req.cookies.user;
// verify token -> 없으면 send 401
jwt.verify(token, secretObj.secret, async (err, decoded) => {
if (decoded) {
const {
imageId, tag, title, review, adminDistrict,
} = req.body;
const newLocations = JSON.parse(req.body.newLocations);
// locations
const createLocation = await locations.create({
location1: JSON.stringify(newLocations[0]),
location2: JSON.stringify(newLocations[1]),
location3: JSON.stringify(newLocations[2]),
location4: JSON.stringify(newLocations[3]),
location5: JSON.stringify(newLocations[4]),
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// categories
const [createCategory, created] = await categories.findOrCreate({
where: {
tag: tag,
},
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// trails
const createTrail = await trails.create({
userId: decoded.userId,
locationId: createLocation.dataValues.id,
categoryId: createCategory.dataValues.id || created.dataValues.id,
imageId: imageId,
title: title,
review: review,
adminDistrict: adminDistrict,
}).catch((error) => {
console.log(error);
res.sendStatus(500);
});
// send trails
res.json(createTrail.dataValues);
} else {
res.sendStatus(401);
}
});
},
내일은 trails/:tag
부분을 구현해야하는데 시간이 생각보다 많지 않다. 역시 프로젝트에 와서는 사전준비와 기한의 중요성을 뼈저리게 느끼고 있다. 이 부분을 마무리하면 내일부터는 프론트와 함께 실제 요청/응답 처리를 모니터할 예정이다. 우리가 구현하는게 시각화된다는게 아직 신기하다. 내일 얼마나 많은 에러가 나올지 기대하며 포스트를 마친다.