상세 컨텐츠

본문 제목

구글파이어베이스를 사용해 노드js 웹서비스 만들기_2/5

노드js·자바스크립트

by 김일국 2023. 12. 25. 16:27

본문

### 지난번 표준노드js인 클라우드환경 K-PaaS 플랫폼에서 노드js 앱을 배포해 보았습니다.(아래)

현재 작업 중인 소스는 아래 깃 허브에 올려 놓았습니다.(단, 서비스계정키 파일은 보안때문에 제외 시켰습니다.)

 -. 표준노드js 웹서비스 코딩 소스 : https://github.com/miniplugin/nodejsboard 

 -. K-PaaS 플랫폼사용해 배포 : https://digitalsolveup.kr/platform.do 

 -. 표준노드js 앱 결과확인 URL : https://nodejsboard.apps.emergency-cloudplatform.kr/ 

오늘은, 위 기본 CRUD 표준노드js 앱에 파일업로드, 검색기능, 페이징처리 기능을 추가해 보았습니다.

초기엔 로컬스토리지(웹서버)에 파일 업로드를 구현해 보았고(주로 multer 라이브러리 사용),

이어서, 로컬스토리지 대신 구글 파이어베이스의 스토리지에 파일 업로드를 구현해 보았습니다.

 -. 주로 multer-firebase-storage 함수와 firebase-admin 의 storage함수를 사용했다.(정보가 흔치않아 구현하는데 쉽지 않았습니다. fileupload.js 라는 모듈을 별도로 만들었습니다. ^^)

이로써, 총 4가지로 시스템이 분리되었다.

1. 웹호스팅 : K-PaaS 플랫폼 : Node js 플랫폼 컨테이너 사용

2. 회원관리 : 파이어베이스 Authentication 인증을 사용하여 회원 로그인 관리.

3. DB관리 : 파이어베이스의 DB인 파이어 Store를 사용하여 게시판 문자 데이터를 저장.

4. 저장소관리 : 파이어베이스의 Storage 서비스를 사용하여 첨부파일을 업로드하는 저장소로 사용.

 

위 작업에 이어서, 게시판 제목 검색기능과 페이징 기능을 추가해 보았습니다.

예전에, MySQL 처럼 관계형DB 또는, NoSQL 데이터베이스로 몽고DB를 사용해 검색과 페이징을 처리해본 것과는 

대조적으로,

 -. 파이어Store 데이터베이스는 문장의 텍스트 검색이 되지 않아서(즉, like 검색이 없음), 구현에 제목 저장시 긴 문장을 배열로 저장하여, 배열의 단어별로 검색을 하는 코딩을 추가 하였습니다.

 -. 페이징처리에 필요한 조회 함수로 아래 query객체의 함수를 사용하면 2개의 document를 구해야 하기 때문에...

 = query.startAfter(시작document) ,

 = query. endBefore(끝document)

위 와 같은 함수를 사용하라는 지침이 있으나, 아래 시작 document만 구하는 것이 좀 더 간편하기 때문에 where()함수에 검색 일자를 기준으로 페이징을 구해 보았다.(아래 코딩 사용)

 = query.where('brddate', '>', Number(req.query.prev)); // 끝 document의 일자로 아래 시작 document의 일자를 구하여

 = query.where('brddate', '<=', Number(result)); // 시작 document의 일자

쿼리 안에서 또다른 쿼리를 실행해야 하기 때문에, async ~ await 비동기함수를 동기적으로 처리하는 promise 를 쿼리안에서 호출해서 처리 하였습니다.(아래 핵심 소스)

// 페이징 변수 초기화 start : 시작점 doc정보, next: 다음 목록에 대한 정보
var pagingObj = {
    prev: null,
    next: null,
    size: 3,
}
let mypromise = (prevDate, keyword) => {
    return new Promise((resolve, reject) => {
        let query = db.collection('board').orderBy("brddate", "asc");
        if (keyword) {
            console.log(keyword);
            query = query.where('brdtitle', 'array-contains', keyword);
            //.where('brdwriter', '>=', keyword)
        }
        if (prevDate) {
            console.log("페이징 번호1 : ", Number(prevDate));
            //query = query.startAt(req.query.start); 실행이 적용되지 않아서 where 조건으로 변경
            query = query.where('brddate', '>', Number(prevDate));
        }
        query.limit(pagingObj.size)
            .get()
            .then((snapshot) => {
                console.log('여기', snapshot.docs[pagingObj.size - 1].data().brddate);
                resolve(snapshot.docs[pagingObj.size - 1].data().brddate);
            })
            .catch((err) => {
                console.log('Error getting documents', err);
                reject("실행에러")
            });
    });
}
router.get('/boardList', async function (req, res, next) {
    let keyword = '';
    if (req.query.keyword != undefined) {
        keyword = req.query.keyword;//.where('brdtitle', 'array-contains', keyword)
    }

    var query = db.collection('board').orderBy("brddate", "desc");
    if (keyword) {
        console.log(keyword);
        query = query.where('brdtitle', 'array-contains', keyword);
        //.where('brdwriter', '>=', keyword)
    }
    if (req.query.prev) {
        console.log("페이징 번호1 : ", Number(req.query.prev));
        //query = query.startAt(req.query.start); 실행이 적용되지 않아서 where 조건으로 변경
        query = query.where('brddate', '>', Number(req.query.prev));
        await mypromise(req.query.prev, keyword)
            .then((result) => {
                query = query.where('brddate', '<=', Number(result));
                console.log('여기2', result);
            })
            .catch((error) => {
                console.log(`Handling error as we received ${error}`);
            });
    }
    if (req.query.next) {
        console.log("페이징 번호1 : ", Number(req.query.next));
        //query = query.startAt(req.query.start); 실행이 적용되지 않아서 where 조건으로 변경
        query = query.where('brddate', '<', Number(req.query.next));
    }
    query.limit(pagingObj.size)
        .get()
        .then((snapshot) => {
            pagingObj = {
                size: pagingObj.size,
                prev: snapshot.docs[0], // document들 안에서 가장 첫번째 것을 가져온다. (내림차순이라서 변수명이 반대이다.)
                next: snapshot.docs.length === pagingObj.size ? snapshot.docs[snapshot.docs.length - 1] : null
                // 가져오는 데이터 갯수를 4개로 지정했는데 3개 밖에 없을 수 있다, 
                // 이때 next가 지정한 데이터 갯수(4) 가 아니라면 null을 넣어준다. 다음(next)이 없다는 뜻.
            }
            if (snapshot.docs.length == 0) {
                res.send('<script>alert("페이징 자료가 없습니다. 목록으로 돌아갑니다.");window.history.back();</script>');
            }
            console.log("페이징 번호2 : ", snapshot.docs.length, "===", pagingObj.size);
            let rows = [];
            snapshot.forEach((doc) => {
                var childData = doc.data();
                childData.brddate = dateFormat(childData.brddate, "yyyy-mm-dd");
                childData.brdtitle = (childData.brdtitle).join(" "); //파이어베이스DB는 텍스트검색을 지원하지 않기 때문에 배열로 저장
                rows.push(childData);
            });
            res.render('board3/boardList', { rows: rows, pagingObj: pagingObj });
        })
        .catch((err) => {
            console.log('Error getting documents', err);
        });
});

외부스토리지인 파이어베이스 Storage로 첨부파일 처리 및 파이어베이스 Store DB에서 위 검색 및 페이징 처리는 쉽지 않은 문제였으나 이렇게 해결을 볼 수 있었습니다. MySQL이나 몽고DB 처럼 검색과 페이징을 위한 함수가 풍부하게 지원되지 않아서 완벽하지는 않으나, 봐줄만하게 된 듯 합니다.^^

 

참고로, 파이어베이스의 Store 데이터베이스의 where()함수의 검색기능을 사용하려면, 아래 처럼 파이어베이스 콘솔화면에서 where()에 사용하는 인덱스를 만들어야 위 코드가 제대로 작동 합니다.(아래)

최초 검색기능을 위한brdtitle 제목 복합색인만들기(위)

https://console.firebase.google.com/project/nodejsboard-1129e/firestore/indexes

추가로 페이징 처리를 위한 brddate날자 단일필드 색인 만들기(위)

 

Ps. 앞으로 게시판 게시물 보기 내부에 댓글 달기 기능을 추가할 예정 입니다.

관련글 더보기

댓글 영역