궤도

[백엔드] Node.js + Sequelize + MySQL 메인 화면을 만들어보자 본문

💻 현생/📃 VIVA

[백엔드] Node.js + Sequelize + MySQL 메인 화면을 만들어보자

영이오 2021. 2. 4. 23:14

지난 시간에 회원가입과 로그인을 구현했다. 로그인을 했으면? 이제 메인 화면이 나와야 한다.

 

우리의 메인화면 프로토타입이다.

 

화면을 보면 내가 줘야 하는건 다음과 같다.

1. 유저의 '닉네임', '학년', '프로필 사진'

2. 유저가 보유하고 있는 '내 문제집', '학원 교재', '오답노트'

 

프론트에서 교재의 종류마다 api를 다르게 해서 달라고 했으니 내가 만들어야 하는 api는 이렇게 되는 것이다.

1. 유저 정보

2. 유저의 내 문제집

3. 유저의 학원 교재

4. 유저의 오답노트

 

이제 시작해보도록 하겠다.

 

app.js

app.use('/api/user/register', require("./routes/user.register"));
app.use('/api/user/login', require("./routes/user.login"));
app.use('/api/home', require("./routes/home")); //추가

routes 폴더에 home.js 파일을 만들고, app.js 파일에 app.use로 추가한다.


유저 정보

유저 정보를 저장해둔 student 테이블이다.

 

routes/home.js (localhost:3001/api/home?stu_id=stu_ID)

router.get('/', async function (req, res, next) {
    const input_stu_id = req.query.stu_id;

    let result = await models.student.findOne({
        where: {
            stu_id: input_stu_id
        }
    });

    if (result) {
        //유저 정보
        const retrievedUser = {
            stu_nick: result.dataValues.stu_nick,
            stu_grade: result.dataValues.stu_grade,
            stu_photo: result.dataValues.stu_photo
        }
        res.send({
            message: "Retrieve data",
            status: 'success',
            data: {
                retrievedUser
            }
        });
    }
    else {
        res.status(500).send({
            message: "Retrieve fail"
        });
    }
});

프론트에서 user의 id값(stu_id)을 넘겨준다고 했으니, input_stu_id에 해당 값을 저장해둔다.

해당 값을 where 조건으로 넣어서 student 테이블에서 해당하는 유저를 불러오자.

pk는 stu_sn이지만 회원가입시 stu_id가 중복되는 일이 없도록 처리했기 때문에 unique하다.

프론트에 필요한 정보는 닉네임, 학년, 프로필 사진이니까 이것들만 retrievedUser에 담아서 넘겨주자.

 

localhost:3001/api/home?stu_id=samdol로 들어가면 id가 samdol인 유저의 정보가 나올 것이다.


내 문제집 & 학원 교재

우리 DB에선 내 문제집과 학원 교재를 같은 테이블에 다른 코드로 저장했고, 오답노트는 다른 테이블로 저장했다.

그니까 엄밀히 말하면 내 문제집과 학원 교재는 같은 방법으로 빼낼 수 있단 것이다.

 

routes/home.js (localhost:3001/api/home/workbook?stu_id=stu_ID)

router.get('/workbook', async function (req, res, next) {
    const input_stu_id = req.query.stu_id;

    let result = await models.student.findOne({
        where: {
            stu_id: input_stu_id
        }
    });

    //일반 교재만
    let workbooks = await models.stu_workbook.findAll({
        where: {
            stu_sn: result.dataValues.stu_sn,
            workbook_sn: {
                [Op.lt]: 1000000
            }
        }
    });

    retrieveBook(workbooks, res);
});

 

routes/home.js (localhost:3001/api/home/academy?stu_id=stu_ID)

router.get('/academy', async function (req, res, next) {
    const input_stu_id = req.query.stu_id;

    let result = await models.student.findOne({
        where: {
            stu_id: input_stu_id
        }
    });

    //학원 교재만
    let workbooks = await models.stu_workbook.findAll({
        where: {
            stu_sn: result.dataValues.stu_sn,
            workbook_sn: {
                [Op.gt]: 999999
            }
        }
    });

    retrieveBook(workbooks, res);
});

 

딱 봐도 굉장히 비슷하다는 것을 알 수 있다. 사실 다른 점이라곤 where 부분의 workbook_sn 처리 밖에 없다.

일단 아까와 같이 넘겨받은 stu_id로 해당 학생의 정보를 받고, stu_workbook 테이블에서 해당 학생의 stu_sn을 가지고 그 학생이 보유하고 있는 교재들을 전부 찾아 workbooks에 저장한다.

 

우린 학원 교재의 workbook_sn이 1000000부터 시작하도록 저장했고, 일반 교재의 workbook_sn은 그보다 작기 때문에 where 부분에서 저렇게 처리해줬다.

Sequelize 문법에서 [Op.lt]:1000000은 <1000000과 같고, [Op.gt]:999999은 >999999과 같다.

 

이렇게 일반 교재와 학원 교재를 분리해서 빼낸 뒤 retrieveBook이란 함수를 실행한다.

두 군데에서 중복되는 코드를 함수로 빼놓은 것 뿐이다.

 

retrieveBook

async function retrieveBook(workbooks, res) {
    if (workbooks.length != 0) { //교재 있냐?
        var bookInfo = new Array();
        for (var i in workbooks) {
            let temp = await models.workbook.findOne({
                where: {
                    workbook_sn: workbooks[i].dataValues.workbook_sn
                }
            });
            bookInfo.push(temp);
        }
        try {
            res.send({ //교재 정보 넘김
                message: "Retrieve books",
                status: 'success',
                data: {
                    bookInfo
                }
            });
        } catch (err) { //무언가 문제가 생김
            res.send({
                message: "ERROR",
                status: 'fail'
            })
        }
    }
    else { //교재 없거나 실패한 것임
        res.send({
            message: "Null or fail",
            status: 'null'
        });
    }
}

일단 학생이 보유하고 있는 교재가 존재하는지부터 확인한다. 처음에는 단순히 if(workbooks)로 존재 여부를 파악했는데, 영 제대로 작동하지 않아 workbooks.length의 길이를 확인하는 방법으로 했다.

교재가 존재한다면, 교재 정보를 저장할 bookInfo 배열을 선언한 뒤 각 workbooks들의 workbook_sn을 참고하여 해당하는 workbook 정보를 빼낸 뒤, 이를 bookInfo에 저장한다.

workbooks의 값이 여러개일 수 있기 때문에 for문을 사용하여 workbooks[i]로 하나하나 접근해야 한다.

 

마지막엔 빼낸 정보들을 한 번에 넘겨주면 된다. 그럼 postman으로 확인해보자.

일반 교재 정보
학원 교재 정보


오답노트

다른 문제집들과는 달리 오답노트 테이블을 몇가지 정보가 더 필요하다.

오답노트의 생성날짜와 각 오답노트의 몇개의 문제가 저장되어 있는가이다.

생성날짜야 DB에 저장한다지만 오답노트 내 문제의 수를 얻기 위해선 오답 테이블에서 검색해야 한다.

 

routes/home.js (localhost:3001/api/home/incor-note?stu_id=stu_ID)

router.get('/incor-note', async function (req, res, next) {
    const input_stu_id = req.query.stu_id;

    let result = await models.student.findOne({
        where: {
            stu_id: input_stu_id
        }
    });

    //오답노트만
    let bookInfo = await models.incor_note.findAll({
        where: {
            stu_sn: result.dataValues.stu_sn
        }
    });

    if (bookInfo.length != 0) {
        var pbCount = new Array();
        for(var i in bookInfo){
            var temp = await models.incor_problem.count({
                where: {
                    note_sn: bookInfo[i].dataValues.note_sn
                }
            });
            pbCount.push(temp);
        }
        try {
            res.send({ //교재 정보 넘김
                message: "Retrieve books",
                status: 'success',
                data: {
                    bookInfo,
                    pbCount
                }
            });
        } catch (err) { //무언가 문제가 생김
            res.send({
                message: "ERROR",
                status: 'fail'
            })
        }
    }
    else { //교재 없거나 실패한 것임
        res.send({
            message: "Null or fail",
            status: 'null'
        });
    }
});

입력받은 정보로 유저 정보를 가져오고 그 정보를 기반으로 해당 유저의 오답노트를 찾아온다.

이 역시 유저가 오답노트를 만들지 않았을 가능성이 있으니 length로 존재여부를 확인해준다.

유저가 보유한 각 오답노트들에 대해 해당 오답노트에 소속된 문제들을 incor_problem에서 찾아와 다 더하여 pbCount 배열에 저장한다. 위 코드를 보면 sequelize에서 count를 사용하는 방법을 알 수 있을 것이다.

 

res.send로 넘겨주는 값은 각 오답노트의 정보와 그 오답노트의 문제 수가 될 것이다.

오답노트 정보

Comments