Skip to content

Latest commit

 

History

History
784 lines (720 loc) · 33.4 KB

README.md

File metadata and controls

784 lines (720 loc) · 33.4 KB

swagger-mongodb

lightweight swagger-ui crud-middleware backed by mongodb

NPM

live test-server

heroku.com test-server

build-status travis-ci.org build-status

build commit status

git-branch : master beta alpha
test-server : heroku.com test-server heroku.com test-server heroku.com test-server
test-report : test-report test-report test-report
coverage : istanbul-lite coverage istanbul-lite coverage istanbul-lite coverage
build-artifacts : build-artifacts build-artifacts build-artifacts

master branch

  • stable branch
  • HEAD should be tagged, npm-published package

beta branch

  • semi-stable branch
  • HEAD should be latest, npm-published package

alpha branch

  • unstable branch
  • HEAD is arbitrary
  • commit history may be rewritten

documentation

this package requires

  • darwin or linux os
  • mongodb 2.6 or higher

api-doc

quickstart web example

to run this example, follow the instruction in the script below

  • example.js
/*
example.js

this node script will serve a lightweight swagger-ui crud-api backed by mongodb

instruction
    1. save this script as example.js
    2. run the shell command:
          $ npm install swagger-mongodb && npm_config_server_port=1337 node example.js
    3. open a browser to http://localhost:1337
    4. interact with the swagger-ui crud-api
*/

/*jslint
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/

(function (local) {
    'use strict';
    switch (local.modeJs) {



    // run node js-env code
    case 'node':
        // export local
        module.exports = local;
        // init assets
        local.utility2.cacheDict.assets['/'] = '<!DOCTYPE html>\n' +
/* jslint-ignore-begin */
'<html>\n' +
'<head>\n' +
'    <meta charset="UTF-8">\n' +
'    <title>\n' +
'    {{envDict.npm_package_name}} [{{envDict.npm_package_version}}]\n' +
'    </title>\n' +
'    <link rel="stylesheet" href="/assets/utility2.css">\n' +
'    <style>\n' +
'    * {\n' +
'        box-sizing: border-box;\n' +
'    }\n' +
'    body {\n' +
'        background-color: #fff;\n' +
'        font-family: Helvetical Neue, Helvetica, Arial, sans-serif;\n' +
'    }\n' +
'    body > div {\n' +
'        margin: 20px 0 20px 0;\n' +
'    }\n' +
'    .testReportDiv {\n' +
'        display: none;\n' +
'    }\n' +
'    </style>\n' +
'    {{envDict.npm_config_html_head_extra}}\n' +
'</head>\n' +
'<body>\n' +
'    <div class="ajaxProgressDiv" style="display: none;">\n' +
'    <div class="ajaxProgressBarDiv ajaxProgressBarDivLoading">loading</div>\n' +
'    </div>\n' +
'    <h1>{{envDict.npm_package_name}} [{{envDict.npm_package_version}}]</h1>\n' +
'    <h3>{{envDict.npm_package_description}}</h3>\n' +
'    <div class="testReportDiv"></div>\n' +
'    <div id="swagger-ui-container" style="display: none;"></div>\n' +
'    <iframe height="512" src="/assets/swagger-ui.html" width="100%"></iframe>\n' +
'    <script src="/assets/utility2.js"></script>\n' +
'    <script src="/assets/swagger-ui.rollup.js"></script>\n' +
'    <script src="/assets/swagger-mongodb.js"></script>\n' +
'    <script src="/assets/example.js"></script>\n' +
'    <script src="/test/test.js"></script>\n' +
'    <script>\n' +
'    window.utility2 = window.utility2 || {};\n' +
'    window.utility2.envDict = {\n' +
'        npm_package_description: "{{envDict.npm_package_description}}",\n' +
'        npm_package_name: "{{envDict.npm_package_name}}",\n' +
'        npm_package_version: "{{envDict.npm_package_version}}"\n' +
'    };\n' +
'    document.querySelector("iframe").onload = function () {\n' +
'        var self;\n' +
'        self = this;\n' +
'        self.height = innerHeight - self.offsetTop - 20;\n' +
'        self.contentWindow.location.hash = location.hash;\n' +
'        self.contentWindow.onclick = function () {\n' +
'            setTimeout(function () {\n' +
'                location.hash = self.contentWindow.location.hash;\n' +
'            });\n' +
'        };\n' +
'    };\n' +
'    </script>\n' +
'    {{envDict.npm_config_html_body_extra}}\n' +
'</body>\n' +
/* jslint-ignore-end */
            '</html>\n';
        local.utility2.cacheDict.assets['/'] = local.utility2.stringFormat(
            local.utility2.cacheDict.assets['/'],
            { envDict: local.utility2.envDict },
            ''
        );
        local.utility2.cacheDict.assets['/assets/example.js'] =
            local.utility2.istanbul_lite.instrumentSync(
                local.fs.readFileSync(__dirname + '/example.js', 'utf8'),
                __dirname + '/example.js'
            );
        local.utility2.cacheDict.assets['/test/test.js'] =
            local.utility2.istanbul_lite.instrumentInPackage(
                local.fs.readFileSync(local.swmg.__dirname + '/test.js', 'utf8'),
                local.swmg.__dirname + '/test.js',
                'swagger-mongodb'
            );
        // init mongodb-client
        local.utility2.onReady.counter += 1;
        local.utility2.taskRunOrSubscribe({
            key: 'swagger-mongodb.mongodbConnect',
            onTask: function (onError) {
                local.mongodb.MongoClient.connect(
                    local.utility2.envDict.npm_config_mongodb_url ||
                        'mongodb://localhost:27017/test',
                    function (error, db) {
                            // validate no error occurred
                            local.utility2.assert(!error, error);
                            local.swmg.db = db;
                            onError();
                            local.utility2.onReady();
                        }
                );
            }
        });
        // init middleware
        local.middleware = local.utility2.middlewareGroupCreate([
            // init pre-middleware
            local.utility2.middlewareInit,
            // init cached-assets middleware
            local.utility2.middlewareAssetsCached,
            // init http-body-get middleware
            local.utility2.middlewareBodyGet,
            // init http-body-parse-upload middleware
            function (request, response, nextMiddleware) {
                var boundary, bodyText;
                // jslint-hack
                local.utility2.nop(response);
                local.utility2.testTryCatch(function () {
                    if ((request.headers['content-type'] || '')
                            .indexOf('multipart/form-data') !== 0) {
                        nextMiddleware();
                        return;
                    }
                    boundary =
                        '--' + (/boundary=(.*)/).exec(request.headers['content-type'])[1];
                    request.swmgBodyParsed = {};
                    bodyText = String(request.bodyRaw);
                    bodyText.split(boundary).slice(1, -1).forEach(function (part) {
                        request.swmgBodyParsed[
                            (/\bname="([^"]*)/).exec(part)[1]
                        ] = part.split('\r\n\r\n').slice(1).join('\r\n\r\n').slice(0, -2);
                    });
                    // set file
                    bodyText.replace('\r\n\r\n', function (match0, ii) {
                        // jslint-hack
                        local.utility2.nop(match0);
                        request.swmgBodyParsed.file = request.bodyRaw
                            .slice(ii + 4, -(boundary.length + 6))
                            .toString('base64');
                    });
                    request.swmgBodyParsed.file = request.bodyRaw
                        .slice(bodyText.lastIndexOf('\r\n\r\n') + 4, -(boundary.length + 6))
                        .toString('base64');
                    // set filename
                    request.swmgBodyParsed.filename = (/\bfilename="([^"]+)/).exec(bodyText);
                    request.swmgBodyParsed.filename =
                        request.swmgBodyParsed.filename &&
                        request.swmgBodyParsed.filename[1];
                    nextMiddleware();
                }, nextMiddleware);
            },
            // init http-body-parse middleware
            local.swmg.middlewareBodyParse,
            // init swagger pre-middleware
            function (request, response, nextMiddleware) {
                // jslint-hack
                local.utility2.nop(request);
                // enable cors
                // http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
                response.setHeader(
                    'Access-Control-Allow-Methods',
                    'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'
                );
                response.setHeader('Access-Control-Allow-Origin', '*');
                // init content-type
                response.setHeader('Content-Type', 'application/json; charset=UTF-8');
                nextMiddleware();
            },
            // init swagger middleware
            local.swmg.middlewareSwagger
        ]);
        // init error-middleware
        local.middlewareError = local.swmg.middlewareError;
        // init petstore-api
        (function () {
            var methodPath, options, schema;
            options = local.utility2.jsonCopy(require(local.swmg.local
                .swagger_ui_lite.__dirname + '/swagger.json'));
            options = {
                definitions: options.definitions,
                paths: options.paths,
                tags: options.tags
            };
            // remove unused properties
            delete options.definitions.ApiResponse;
            // init schema
            Object.keys(options.definitions).forEach(function (schemaName) {
                schema = options.definitions[schemaName];
                // init id
                schema.properties.id = { type: 'string' };
                schema['x-inheritList'] = [{ $ref: '#/definitions/JsonapiResource' }];
            });
            local.utility2.objectSetOverride(options, {
                definitions: {
                    // init Pet schema
                    Pet: {
                        // drop collection on init
                        _collectionDrop: true,
                        // upsert fixtures
                        _collectionFixtureList: [{
                            id: 'pet0',
                            name: 'birdie',
                            photoUrls: [],
                            status: 'available',
                            tags: [{ name: 'bird'}]
                        }, {
                            id: 'pet1',
                            name: 'kittie',
                            status: 'pending',
                            photoUrls: [],
                            tags: [{ name: 'cat'}]
                        }, {
                            id: 'pet2',
                            name: 'doggie',
                            photoUrls: [],
                            status: 'sold',
                            tags: [{ name: 'dog'}]
                        }],
                        _collectionName: 'SwmgPet'
                    },
                    // init Order schema
                    Order: {
                        // create index
                        _collectionCreateIndexList: [{
                            key: { status: 1 },
                            name: 'status_1'
                        }],
                        // drop collection on init
                        _collectionDrop: true,
                        // upsert fixtures
                        _collectionFixtureList: [{
                            id: 'order0',
                            status: 'available'
                        }, {
                            id: 'order1',
                            status: 'pending'
                        }, {
                            id: 'order2',
                            status: 'sold'
                        }],
                        _collectionName: 'SwmgOrder',
                        properties: {
                            petId: { type: 'string' }
                        }
                    },
                    // init User schema
                    User: {
                        // create index
                        _collectionCreateIndexList: [{
                            key: { username: 1 },
                            name: 'username_1',
                            unique: true
                        }],
                        // drop collection on init
                        _collectionDrop: true,
                        // upsert fixtures
                        _collectionFixtureList: [{
                            email: '[email protected]',
                            firstName: 'john',
                            id: 'user0',
                            lastName: 'doe',
                            password: 'hello',
                            phone: '1234-5678',
                            username: 'john.doe'
                        }, {
                            email: '[email protected]',
                            firstName: 'jane',
                            id: 'user1',
                            lastName: 'doe',
                            password: 'bye',
                            phone: '8765-4321',
                            username: 'jane.doe'
                        }],
                        _collectionName: 'SwmgUser'
                    }
                },
                // init crud-api
                paths: {
                    '/pet/crudGetByQueryMany': { get: {
                        _collectionName: 'SwmgPet',
                        _crudApi: 'pet',
                        _schemaName: 'Pet',
                        operationId: 'crudGetByQueryMany',
                        tags: ['pet']
                    } },
                    '/store/crudGetByQueryMany': { get: {
                        _collectionName: 'SwmgOrder',
                        _crudApi: 'store',
                        _schemaName: 'Order',
                        operationId: 'crudGetByQueryMany',
                        tags: ['store']
                    } },
                    '/user/crudGetByQueryMany': { get: {
                        _collectionName: 'SwmgUser',
                        _crudApi: 'user',
                        _schemaName: 'User',
                        operationId: 'crudGetByQueryMany',
                        tags: ['user']
                    } }
                }
            }, 4);
            // transform petstore-api to swagger-mongodb's crud-api
            Object.keys(options.paths).forEach(function (path) {
                Object.keys(options.paths[path]).forEach(function (method) {
                    methodPath = options.paths[path][method];
                    // init methodPath._schemaName
                    switch (path.split('/')[1]) {
                    case 'pet':
                        methodPath._schemaName = 'Pet';
                        break;
                    case 'store':
                        methodPath._schemaName = 'Order';
                        break;
                    case 'user':
                        methodPath._schemaName = 'User';
                        break;
                    }
                    methodPath._collectionName = 'Swmg' + methodPath._schemaName;
                    delete methodPath.produces;
                    delete methodPath.responses;
                    delete methodPath.security;
                    // init jsonapi response
                    local.utility2.objectSetDefault(methodPath, { responses: {
                        200: {
                            description: '200 ok - http://jsonapi.org/format' +
                                '/#document-structure-top-level',
                            schema: { $ref: '#/definitions/JsonapiResponse{{_schemaName}}' }
                        }
                    } }, 2);
                    // init crudCreateMany / crudCreateOne / crudDeleteByIdOne / crudGetByIdOne
                    switch (methodPath.operationId) {
                    case 'addPet':
                    case 'createUser':
                    case 'placeOrder':
                        methodPath.operationId = 'crudCreateOne';
                        break;
                    case 'createUsersWithArrayInput':
                    case 'createUsersWithListInput':
                        methodPath.operationId = 'crudCreateMany';
                        break;
                    case 'deleteOrder':
                    case 'deletePet':
                    case 'deleteUser':
                        methodPath.operationId = 'crudDeleteByIdOne';
                        break;
                    case 'getOrderById':
                    case 'getPetById':
                    case 'getUserByName':
                        methodPath.operationId = 'crudGetByIdOne';
                        break;
                    }
                    // init id
                    (methodPath.parameters || []).forEach(function (paramDef) {
                        switch (paramDef.name) {
                        case 'orderId':
                        case 'petId':
                            delete paramDef.format;
                            paramDef.type = 'string';
                            break;
                        }
                    });
                });
            });
            local.swmg.apiUpdate(options);
        }());
        // init petstore-middleware
        local.middleware.middlewareList.push(function (request, response, nextMiddleware) {
            var modeNext, onNext, options;
            modeNext = 0;
            onNext = function (error, data) {
                local.utility2.testTryCatch(function () {
                    modeNext = error
                        ? Infinity
                        : modeNext + 1;
                    switch (modeNext) {
                    case 1:
                        // init id
                        ((request.swmgMethodPath && request.swmgMethodPath.parameters) || [
                        ]).forEach(function (paramDef) {
                            switch (paramDef.name) {
                            case 'orderId':
                            case 'petId':
                                request.swmgParamDict.id = request.swmgParamDict[paramDef.name];
                                break;
                            }
                        });
                        // init options
                        if (request.swmgMethodPath) {
                            options = {
                                collectionName: request.swmgMethodPath._collectionName,
                                data: request.swmgParamDict,
                                operationId: request.swmgMethodPath.operationId,
                                paramDefList: request.swmgMethodPath.parameters,
                                schemaName: request.swmgMethodPath._schemaName
                            };
                        }
                        switch (request.swmgPathname) {
                        // handle pet request
                        case 'DELETE /pet/':
                        case 'GET /pet/':
                        case 'POST /pet':
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'GET /pet/findByStatus':
                            options.operationId = 'crudGetByQueryMany';
                            options.data.fields = '{}';
                            options.data.hint = '{}';
                            options.data.limit = 100;
                            options.data.query = '{"status":{"$in":' +
                                JSON.stringify(options.data.status) + '}}';
                            options.data.skip = 0;
                            options.data.sort = '{"_timeModified":-1}';
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'GET /pet/findByTags':
                            options.operationId = 'crudGetByQueryMany';
                            options.data.fields = '{}';
                            options.data.hint = '{}';
                            options.data.limit = 100;
                            options.data.query = '{"status":{"$in":' +
                                JSON.stringify(options.data.tags) + '}}';
                            options.data.skip = 0;
                            options.data.sort = '{"_timeModified":-1}';
                            options.paramDefList[0].default = 'bird,cat,dog';
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'POST /pet/':
                            options.data.upsert = true;
                            options.data.body = {
                                id: options.data.id,
                                name: options.data.name,
                                status: options.data.status
                            };
                            options.operationId = 'crudUpdateOne';
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'POST /pet//':
                            options.data.body = {
                                additionalMetadata: options.data.additionalMetadata,
                                file: options.data.file,
                                filename:
                                    request.swmgBodyParsed && request.swmgBodyParsed.filename,
                                id: options.id
                            };
                            options.data.upsert = true;
                            options.operationId = 'crudUpdateOne';
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'PUT /pet':
                            options.data.upsert = true;
                            options.operationId = 'crudReplaceOne';
                            local.swmg._crudApi(options, onNext);
                            break;
                        // handle store request
                        case 'DELETE /store/order/':
                        case 'GET /store/order/':
                        case 'POST /store/order':
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'GET /store/inventory':
                            options.data = { body: [{
                                $group: { _id: '$status', total: { $sum: 1} }
                            }, {
                                $project: { _id: 0, status: '$_id', total: '$total' }
                            }]};
                            options.operationId = 'crudAggregateMany';
                            local.swmg._crudApi(options, onNext);
                            break;
                        // handle user request
                        case 'DELETE /user/':
                        case 'GET /user/':
                        case 'POST /user/createWithArray':
                        case 'POST /user/createWithList':
                            options.optionsId = { username: request.swmgParamDict.username};
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'POST /user':
                            options.data.username = options.data.body.username;
                            options.optionsId = { username: request.swmgParamDict.username};
                            local.swmg._crudApi(options, onNext);
                            break;
                        case 'PUT /user/':
                            options.data.body.username = options.data.username;
                            options.data.upsert = true;
                            options.operationId = 'crudReplaceOne';
                            options.optionsId = { username: request.swmgParamDict.username};
                            local.swmg._crudApi(options, onNext);
                            break;
                        default:
                            nextMiddleware();
                        }
                        break;
                    default:
                        // validate no error occurred
                        local.utility2.assert(!error, error);
                        // respond with json-object
                        response.end(JSON.stringify(data));
                    }
                }, nextMiddleware);
            };
            onNext();
        });
        // run server-test
        local.utility2.testRunServer(local);
        break;
    }
}((function () {
    'use strict';
    var local;



    // run shared js-env code
    (function () {
        // init local
        local = {};
        // init js-env
        local.modeJs = (function () {
            try {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            } catch (errorCaughtNode) {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    'browser';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        // export local
        local.global.local = local;
        // init swagger-mongodb
        local.swmg = local.modeJs === 'browser'
            ? window.swmg
            : require('swagger-mongodb');
        // import swmg.local
        Object.keys(local.swmg.local).forEach(function (key) {
            local[key] = local[key] || local.swmg.local[key];
        });
        // init utility2
        local.utility2 = local.swmg.local.utility2;
        // init onReady
        local.utility2.onReadyInit();
    }());
    return local;
}())));

output from shell

screen-capture

output from phantomjs-lite

screen-capture

npm-dependencies

package-listing

screen-capture

package.json

{
    "author": "kai zhu <[email protected]>",
    "bin": { "swagger-mongodb": "index.js" },
    "dependencies": {
        "mongodb-minimal": "2015.8.1",
        "swagger-ui-lite": "2015.6.2",
        "utility2": "~2015.8.5"
    },
    "description": "lightweight swagger-ui crud-middleware backed by mongodb",
    "devDependencies": {
        "phantomjs-lite": "2015.7.1"
    },
    "engines": { "node": ">=0.10 <=0.12" },
    "keywords": [
        "api",
        "browser",
        "cms", "crud",
        "mongo", "mongodb",
        "swagger", "swagger-ui",
        "web"
    ],
    "license": "MIT",
    "name": "swagger-mongodb",
    "os": ["darwin", "linux"],
    "repository" : {
        "type" : "git",
        "url" : "https://github.com/kaizhu256/node-swagger-mongodb.git"
    },
    "scripts": {
        "build-ci": "node_modules/.bin/utility2 shRun shReadmeBuild",
        "build-doc": "node_modules/.bin/utility2 shRun shReadmeExportPackageJson && \
node_modules/.bin/utility2 shRun shDocApiCreate \"{ \
exampleFileList:['example.js','test.js','index.js'], \
moduleDict:{'swagger-mongodb':{aliasList:['swmg'],exports:require('./index.js')}} \
}\"",
        "start": "npm_config_mode_auto_restart=1 node_modules/.bin/utility2 shRun node test.js",
        "test": "node_modules/.bin/utility2 shRun shReadmeExportPackageJson && \
node_modules/.bin/utility2 test test.js"
    },
    "version": "2015.8.3"
}

todo

  • add logging feature
  • rename delete to remove for naming consistency
  • migrate to travis-ci docker container build
  • add cached param for crudGetByQueryMany
  • add SwmgUserLoginTokenCapped
  • re-enable user login/logout
  • test /user/login and /user/logout
  • add max / min validation
  • none

change since af87c5b9

  • npm publish 2015.8.3
  • lockdown npm dependencies
  • none

changelog of last 50 commits

screen-capture

internal build-script

  • build.sh
# build.sh

# this shell script will run the build for this package

shBuild() {
    # this function will run the main build
    local TEST_URL || return $?

    # init env
    export npm_config_mode_slimerjs=1 || return $?
    . node_modules/.bin/utility2 && shInit || return $?

    # run npm-test on published package
    shRun shNpmTestPublished || return $?

    # test example js script
    export npm_config_timeout_exit=10000 || return $?
    MODE_BUILD=testExampleJs shRunScreenCapture shReadmeTestJs example.js || return $?
    unset npm_config_timeout_exit || return $?

    # run npm-test
    MODE_BUILD=npmTest shRunScreenCapture npm test || return $?

    # create api-doc
    npm run-script build-doc || return $?

    # if running legacy-node, then do not continue
    [ "$(node --version)" \< "v0.12" ] && return

    # deploy app to heroku
    shRun shHerokuDeploy hrku01-$npm_package_name-$CI_BRANCH || return $?

    # test deployed app to heroku
    if [ "$CI_BRANCH" = alpha ] ||
        [ "$CI_BRANCH" = beta ] ||
        [ "$CI_BRANCH" = master ]
    then
        TEST_URL="https://hrku01-$npm_package_name-$CI_BRANCH.herokuapp.com" || return $?
        TEST_URL="$TEST_URL?modeTest=phantom&timeExit={{timeExit}}" || return $?
        MODE_BUILD=herokuTest shPhantomTest "$TEST_URL" || return $?
    fi
}
shBuild

# save exit-code
EXIT_CODE=$?
# create package-listing
MODE_BUILD=gitLsTree shRunScreenCapture shGitLsTree || exit $?
# create recent changelog of last 50 commits
MODE_BUILD=gitLog shRunScreenCapture git log -50 --pretty="%ai\u000a%B" || exit $?
# if running legacy-node, then do not continue
[ "$(node --version)" \< "v0.12" ] && exit $EXIT_CODE
# upload build-artifacts to github, and if number of commits > 16, then squash older commits
COMMIT_LIMIT=16 shBuildGithubUpload || exit $?
exit $EXIT_CODE