[프로젝트-Stroll] Devlog-4

김대연·2020년 1월 18일
0

Project Stroll

목록 보기
4/7

오늘 작업한 것에 얘기하기전에 팀원분이 발견한 중요한 설정에 관련된 오류가 있었다. 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;

그 후 tokenverify() 를 이용하여 확인하였다.

    const token = req.cookies.user;
    const decoded = jwt.verify(token, secretObj.secret);
	//decoded 는 { userId: 1, email: 'test@test.com', iat: 1579355633, exp: 1579357433 } 와 같은 값을 가지고 있다.

여기에 포함시킨 userIdtrails 테이블에 추가해준다.

그 다음은 요청에서 받아온 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 부분을 구현해야하는데 시간이 생각보다 많지 않다. 역시 프로젝트에 와서는 사전준비와 기한의 중요성을 뼈저리게 느끼고 있다. 이 부분을 마무리하면 내일부터는 프론트와 함께 실제 요청/응답 처리를 모니터할 예정이다. 우리가 구현하는게 시각화된다는게 아직 신기하다. 내일 얼마나 많은 에러가 나올지 기대하며 포스트를 마친다.

0개의 댓글