API 서버 - Article CRUD 추가

손대중·2022년 7월 6일
0

article 에 대해서 CRUD API 를 추가해보자.

이전 글에서 적어놓긴 했지만, article 에 대한 schema 는 아래와 같다.

// Article Schema
const articleSchema = new Schema({
    title: { type: String, required: true },
    contents: { type: Schema.Types.String, required: true },
    created: { type: Date },
    parent: { type: Schema.Types.ObjectId, required: true }
});

// Article Model
const ArticleModel = mongoose.model('article', articleSchema);

GET /article

일단 아래 3가지 정도의 기능을 제공하면 될것 같다.

  • 전체 article 가져오기
  • parent - menu, 그리고 그 자식 menu 들에 딸린 모든 article 가져오기
  • _id 에 대한 article 가져오기

사실 옵션을 좀 더 디테일하게 설정해야 하는게 맞다.
정렬이라던지... created 라던지, 키워드 검색이라던지...
이 부분은 추후에 하는 걸로...

일단 _id 와 parent 에 대해서만 query 하자.

  • query 가 null 인 경우
    • 모든 article 리턴
  • query 에 parent 가 존재하는 경우
    • parent - menu + 해당 menu 의 자식 menu 들을 target 으로 설정
    • 모든 target menu 들에 딸린 article 리턴
  • query 에 _id 가 존재하는 경우
    • _id 에 해당하는 article 리턴
  • query 에 parent, _id 가 존재하는 경우
    • and 조건이기 때문에 사실상 무쓸모

이제 실제 구현을 해보자.

다른 부분은 그냥 하면 되는데, target menu 와 그 자식 menu 들을 가져와야 하는 부분은 models/Menu.js 에 별도의 함수로 제공하는 것이 좋겠다.

models/Menu.js 내부에서도 이미 코드로서 존재하기도 하고, 각 data 에 관한 로직들은 각각의 js 에 존재하는 것이 코드 관리상으로도 그렇고 의미적으로도 더 맞다고 생각한다.

뭐 하는김에 models/Menu.js 코드 정리도 하고......

  • models/Menu.js

    // target 메뉴들 + 자신의 모든 자식 메뉴들을 array 로 리턴
    export const getMenusAndAllChildren = async query => {
        let targets = [];
        let children = [];
    
        try {
            // 타겟의 자식 메뉴들까지 다 지우기
            targets = await MenuModel.find(query);
            children = [...targets];
    
            for (let i = 0; i < children.length; i++) {
                if (children[i].children?.length > 0) {
                    const newChildren = await MenuModel.find({_id: children[i].children});
                    children = [...children, ...newChildren]
                }
            }
    
            children = children.slice(targets.length);
        } catch (e) {
            console.error(e);
            targets = [];
            children = [];
        }
    
        return { targets, children };
    }

이제 저 함수를 사용해서 GET /article 을 완성해보자.

  • router/article.js

    import { Router } from 'express';
    import { get } from '../models/Article.js';
    
    // GET /article
    router.get("/", async (req, res) => {
        console.log('get article......');
    
        const data = await get(req.query);
    
        res.send({data});
    });
  • models/Article.js

    // query 는 _id 혹은 parent 만 허용
    // parent 가 있을 경우에는 모든 자식 메뉴들도 다 대상임.
    // 둘 다 없음 all
    export const get = async query => {
        let data = null;
    
        try {
            let lastQuery = {};
    
            if (query?._id) {
                lastQuery._id = query._id;
            }
    
            if (query?.parent) {
                const { targets, children } = await getMenusAndAllChildren({ _id: query.parent });
                lastQuery.parent = [...targets, ...children].map(p => p._id);
            }
    
            lastQuery = lastQuery._id || lastQuery.parent ? lastQuery : null;
    
            data = await ArticleModel.find(lastQuery);
        } catch (e) {
            console.error(e);
        }
    
        return data;
    };

POST /article

article 을 생성하는 API 이며, 어드민 화면에서만 사용...

  • router/article.js

    import { create } from '../models/Article.js';
    
    // POST /article
    router.post("/", async (req, res) => {
        console.log('post article......');
    
        const success = await create(req.body);
    
        res.send({success});
    });
  • models/Article.js

    export const create = async data => {
        let success = false;
    
        const { parent } = data;
    
        try {
            // parent check
            const parents = await getMenu({ _id: parent});
    
            if (parents?.length !== 1) {
                return false;
            }
    
            // set created
            data.create = (new Date()).getTime();
    
            const article = new ArticleModel(data);
            await article.save();
    
            success = true;
        } catch (e) {
            console.error(e);
        }
    
        return success;
    };

DELETE /article

article 을 삭제하는 API 이며 여러개의 article 을 삭제할 수 있도록 해봤다. (검증은 안함)

이것도 어드민 화면에서만 사용....

  • router/article.js

    import { remove } from '../models/Article.js';
    
    // DELETE /article
    router.delete("/", async (req, res) => {
        console.log('delete article......');
    
        const success = await remove(req.body);
    
        res.send({success});
    });
  • models/Article.js

    export const remove = async query => {
        let success = false;
    
        try {
            const _id = query?._id;
            if (!_id?.length !== 0) {
                return false;
            }
    
            const targets = await ArticleModel.find({ _id });
    
            await ArticleModel.deleteMany({_id: targets.map(t => t._id)});
    
            success = true;
        } catch (e) {
            console.error(e);
        }
    
        return success;
    };

PATCH /menu

메뉴 데이터를 수정하는 API 이며 검증 안했다.

이것도 어드민 화면에서만 사용....

  • router/menu.js

    import { patch } from '../models/Article.js';
    
    // PATCH /article
    router.patch("/", async (req, res) => {
        console.log('patch article......');
    
        const success = await patch(req.body?.query, req.body?.data);
    
        res.send({success});
    });
  • models/Article.js

    // 무조건 _id 로
    export const patch = async (query, data) => {
        let success = false;
    
        try {
            const _id = query?._id;
            if (!_id?.length !== 0) {
                return false;
            }
    
            await ArticleModel.findOneAndUpdate({ _id }, { $set: data });
    
            success = true;
        } catch (e) {
            console.error(e);
        }
    
        return success;
    };

뭐 DELETE 와 PATCH 를 제외하고는 잘 동작하는 것 확인했다.

모든 api 에 request data - query, body 에 대한 체크 로직을 전반적으로 손봐야 할듯.

나중에......

0개의 댓글