├── .editorconfig
├── .gitattributes
├── .gitignore
├── .idea
├── encodings.xml
├── generator-node-restify-mongodb.iml
├── misc.xml
└── modules.xml
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── eslint.json
├── generators
└── app
│ ├── index.js
│ └── templates
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .jshintrc
│ ├── .npmignore
│ ├── Gruntfile.js
│ ├── app.js
│ ├── app
│ ├── models
│ │ ├── index.js
│ │ └── widget.js
│ └── routes
│ │ ├── index.js
│ │ ├── utilities.js
│ │ └── widget.js
│ ├── config
│ └── config.js
│ ├── data
│ └── widgets.json
│ ├── db-connection.js
│ ├── log.js
│ ├── package.json
│ ├── postman
│ ├── Widgets.postman_collection.json
│ └── localhost-restify.postman_environment.json
│ └── spec
│ ├── utils_spec.js
│ └── widget_spec.js
├── gulpfile.js
├── package-lock.json
├── package.json
├── preview.png
└── test
└── app.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | ### Node template
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 | ### JetBrains template
40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
42 |
43 | # User-specific stuff:
44 | .idea/workspace.xml
45 | .idea/tasks.xml
46 | .idea/dictionaries
47 | .idea/vcs.xml
48 | .idea/jsLibraryMappings.xml
49 |
50 | # Sensitive or high-churn files:
51 | .idea/dataSources.ids
52 | .idea/dataSources.xml
53 | .idea/dataSources.local.xml
54 | .idea/sqlDataSources.xml
55 | .idea/dynamic.xml
56 | .idea/uiDesigner.xml
57 |
58 | # Gradle:
59 | .idea/gradle.xml
60 | .idea/libraries
61 |
62 | # Mongo Explorer plugin:
63 | .idea/mongoSettings.xml
64 |
65 | ## File-based project format:
66 | *.iws
67 |
68 | ## Plugin-specific files:
69 |
70 | # IntelliJ
71 | /out/
72 |
73 | # mpeltonen/sbt-idea plugin
74 | .idea_modules/
75 |
76 | # JIRA plugin
77 | atlassian-ide-plugin.xml
78 |
79 | # Crashlytics plugin (for Android Studio and IntelliJ)
80 | com_crashlytics_export_strings.xml
81 | crashlytics.properties
82 | crashlytics-build.properties
83 | fabric.properties
84 | ### Node template
85 | # Logs
86 | logs
87 | *.log
88 | npm-debug.log*
89 |
90 | # Runtime data
91 | pids
92 | *.pid
93 | *.seed
94 |
95 | # Directory for instrumented libs generated by jscoverage/JSCover
96 | lib-cov
97 |
98 | # Coverage directory used by tools like istanbul
99 | coverage
100 |
101 | # nyc test coverage
102 | .nyc_output
103 |
104 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
105 | .grunt
106 |
107 | # node-waf configuration
108 | .lock-wscript
109 |
110 | # Compiled binary addons (http://nodejs.org/api/addons.html)
111 | build/Release
112 |
113 | # Dependency directories
114 | node_modules
115 | jspm_packages
116 |
117 | # Optional npm cache directory
118 | .npm
119 |
120 | # Optional REPL history
121 | .node_repl_history
122 | ### JetBrains template
123 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
124 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
125 |
126 | # User-specific stuff:
127 | .idea/workspace.xml
128 | .idea/tasks.xml
129 | .idea/dictionaries
130 | .idea/vcs.xml
131 | .idea/jsLibraryMappings.xml
132 |
133 | # Sensitive or high-churn files:
134 | .idea/dataSources.ids
135 | .idea/dataSources.xml
136 | .idea/dataSources.local.xml
137 | .idea/sqlDataSources.xml
138 | .idea/dynamic.xml
139 | .idea/uiDesigner.xml
140 |
141 | # Gradle:
142 | .idea/gradle.xml
143 | .idea/libraries
144 |
145 | # Mongo Explorer plugin:
146 | .idea/mongoSettings.xml
147 |
148 | ## File-based project format:
149 | *.iws
150 |
151 | ## Plugin-specific files:
152 |
153 | # IntelliJ
154 | /out/
155 |
156 | # mpeltonen/sbt-idea plugin
157 | .idea_modules/
158 |
159 | # JIRA plugin
160 | atlassian-ide-plugin.xml
161 |
162 | # Crashlytics plugin (for Android Studio and IntelliJ)
163 | com_crashlytics_export_strings.xml
164 | crashlytics.properties
165 | crashlytics-build.properties
166 | fabric.properties
167 |
168 |
169 | **.DS_Store
170 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/generator-node-restify-mongodb.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | /usr/local/bin/bower
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6
3 | }
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'node'
4 | - '8'
5 | - '7'
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Gary A. Stafford
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # generator-node-restify-mongodb
2 |
3 | [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url]
4 |
5 | ## Introduction
6 |
7 | __Project updated November, 2017 (v0.3.0), to Restify 6.x from 4.x.__
8 |
9 | This Yeoman generator scaffolds a basic RESTful CRUD API service, a Node application, based on [Node.js](https://nodejs.org), [Restify](http://restify.com), and [MongoDB](https://www.mongodb.com).
10 |
11 | According to their website, Restify, used most notably by [Netflix](http://techblog.netflix.com/2014/11/nodejs-in-flames.html), borrows heavily from [Express](http://expressjs.com). However, while Express is targeted at browser applications, with templating and rendering, Restify is keenly focused on building API services that are maintainable and observable.
12 |
13 | Along with Node, Restify, and MongoDB, the project also implements the following packages: [Bunyan](https://github.com/trentm/node-bunyan) (includes [DTrace](http://dtrace.org/blogs/about/) support), [Jasmine](https://github.com/mhevery/jasmine-node) (using [jasmine-node](https://github.com/mhevery/jasmine-node)), [Mongoose](http://mongoosejs.com/index.html), and [Grunt](http://gruntjs.com).
14 |
15 | Portions of this project's file structure and code are derived from what I consider the best parts of several different projects, including [generator-express](https://github.com/expressjs/generator), [generator-restify-mongo](https://github.com/lawrence-yu/generator-restify-mongo), and [generator-restify](https://github.com/chris-l/generator-restify).
16 |
17 | ## Installation
18 |
19 | To begin, install install [Yeoman](http://yeoman.io) and generator-node-restify-mongodb using [npm](https://www.npmjs.com/), using npm. The generator assumes you have pre-installed Node and MongoDB.
20 |
21 | ```shell
22 | npm install -g yo
23 | npm install -g generator-node-restify-mongodb
24 | ```
25 |
26 | Then, generate the new project.
27 |
28 | ```bash
29 | mkdir node-restify-mongodb
30 | cd $_
31 | yo node-restify-mongodb
32 | ```
33 |
34 | Yeoman scaffolds the application, creating the directory structure, copying required files, and running 'npm install' to load the npm package dependencies.
35 |
36 | 
37 |
38 | ## Using the Generated Application
39 |
40 | Next, import the supplied set of sample widget documents into the local development instance of MongoDB from the supplied 'data/widgets.json' file.
41 |
42 | ```bash
43 | NODE_ENV=development grunt mongoimport --verbose
44 | ```
45 |
46 | Similar to Yeoman's Express Generator, this application contains configuration for three typical environments: 'Development' (default), 'Test', and 'Production'. If you want to import the sample widget documents into your Test or Production instances of MongoDB, first change the 'NODE_ENV' environment variable value.
47 |
48 | ```bash
49 | NODE_ENV=production grunt mongoimport --verbose
50 | ```
51 |
52 | To start the application in a new terminal window, use the following command.
53 |
54 | ```bash
55 | npm start
56 | ```
57 |
58 | To test the application, using jshint and the jasmine-node module, the sample documents must be imported into MongoDB and the application must be running (see above). To test the application, open a separate terminal window, and use the following command.
59 |
60 | ```bash
61 | npm test
62 | ```
63 |
64 | The project contains a set of jasmine-node tests, split between the '/widgets' and the '/utils' endpoints. If the application is running correctly, you should see the test results displayed in the terminal window.
65 |
66 | Similarly, the following command displays a code coverage report, using the grunt, mocha, istanbul, and grunt-mocha-istanbul node modules.
67 |
68 | ```bash
69 | grunt coverage
70 | ```
71 |
72 | Grunt uses the grunt-mocha-istanbul module to execute the same set of jasmine-node tests. Based on those tests, the application's code coverage (statement, line, function, and branch coverage) is displayed.
73 |
74 | You may test the running application, directly, by cURLing the '/widgets' endpoints.
75 |
76 | ```bash
77 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets"
78 | ```
79 |
80 | For more legible output, try [HTTPie](https://httpie.org/) or [prettyjson](https://www.npmjs.com/package/prettyjson).
81 |
82 | ```bash
83 | brew install httpie
84 | http http://localhost:3000/widgets
85 | http http://localhost:3000/widgets/SVHXPAWEOD
86 | ```
87 |
88 | ```bash
89 | npm install -g prettyjson
90 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets" --silent | prettyjson
91 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets/SVHXPAWEOD" --silent | prettyjson
92 | ```
93 |
94 | A much better RESTful API testing solution is [Postman](https://www.getpostman.com/). Postman provides the ability to individually configure each environment and abstract that environment-specific configuration, such as host and port, from the actual HTTP requests.
95 |
96 | ## API REST Endpoints
97 |
98 | The scaffolded application includes the following endpoints.
99 |
100 | ```javascript
101 | # widget resources
102 | var PATH = '/widgets';
103 | server.get({path: PATH, version: VERSION}, findDocuments);
104 | server.get({path: PATH + '/:product_id', version: VERSION}, findOneDocument);
105 | server.post({path: PATH, version: VERSION}, createDocument);
106 | server.put({path: PATH, version: VERSION}, updateDocument);
107 | server.del({path: PATH + '/:product_id', version: VERSION}, deleteDocument);
108 |
109 | # utility resources
110 | var PATH = '/utils';
111 | server.get({path: PATH + '/ping', version: VERSION}, ping);
112 | server.get({path: PATH + '/health', version: VERSION}, health);
113 | server.get({path: PATH + '/info', version: VERSION}, information);
114 | server.get({path: PATH + '/config', version: VERSION}, configuraton);
115 | server.get({path: PATH + '/env', version: VERSION}, environment);
116 | ```
117 |
118 | ## Widget
119 |
120 | The Widget is the basic document object used throughout the application. It is used, primarily, to demonstrate Mongoose's [Model](http://mongoosejs.com/docs/models.html) and [Schema](http://mongoosejs.com/docs/guide.html). The Widget object contains the following fields, as shown in the sample widget, below.
121 |
122 | ```json
123 | {
124 | "product_id": "4OZNPBMIDR",
125 | "name": "Fapster",
126 | "color": "Orange",
127 | "size": "Medium",
128 | "price": "29.99",
129 | "inventory": 5
130 | }
131 | ```
132 |
133 | ## MongoDB
134 |
135 | Use the [mongo shell](https://docs.mongodb.com/manual/mongo/) to access the application's MongoDB instance and display the imported sample documents.
136 |
137 | ```text
138 | mongo
139 | > show dbs
140 | > use node-restify-mongodb-development
141 | > show tables
142 | > db.widgets.find()
143 | { "_id" : ObjectId("574cf9bb0f515d7c67a87026"), "product_id" : "4OZNPBMIDR", "name" : "Fapster", "color" : "Orange", "size" : "Medium", "price" : "29.99", "inventory" : 5 }
144 | { "_id" : ObjectId("574cf9bb0f515d7c67a87027"), "product_id" : "SVHXPAWEOD", "name" : "Voonex", "color" : "Green", "size" : "Medium", "price" : "$10.99", "inventory" : 50 }
145 | { "_id" : ObjectId("574cf9bb0f515d7c67a87028"), "product_id" : "3YIRGZ6TDW", "name" : "Groopster", "color" : "Yellow", "size" : "Large", "price" : "$99.95", "inventory" : 100 }
146 | { "_id" : ObjectId("574cf9bb0f515d7c67a87029"), "product_id" : "6T2HC5MIZ9", "name" : "Chaintwist", "color" : "Purple", "size" : "Tiny", "price" : "$99.95", "inventory" : 15 }
147 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702a"), "product_id" : "ERZ1RMJFR3", "name" : "Glozzom", "color" : "Red", "size" : "Huge", "price" : "$199.98", "inventory" : 35 }
148 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702b"), "product_id" : "N43WV5234S", "name" : "Zapster", "color" : "Green", "size" : "Tiny", "price" : "$17.49", "inventory" : 65 }
149 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702c"), "product_id" : "0BVCLPDZ42", "name" : "Chaintwist", "color" : "Blue", "size" : "Medium", "price" : "$89.95", "inventory" : 55 }
150 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702d"), "product_id" : "N212QZOD9B", "name" : "Pentwist", "color" : "Yellow", "size" : "Huge", "price" : "$159.98", "inventory" : 95 }
151 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702e"), "product_id" : "RTHGP1FCGN", "name" : "Reflupper", "color" : "Red", "size" : "Large", "price" : "$12.95", "inventory" : 25 }
152 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702f"), "product_id" : "GKO1SFX04M", "name" : "Jukelox", "color" : "Blue", "size" : "Small", "price" : "$25.49", "inventory" : 75 }
153 | ```
154 |
155 | ## Environmental Variables
156 |
157 | The scaffolded application relies on several environment variables to determine its environment-specific runtime configuration. If these environment variables are present, the application defaults to using the Development environment values, as shown below, in the application's 'config/config.js' file.
158 |
159 | ```javascript
160 | var NODE_ENV = process.env.NODE_ENV || 'development';
161 | var NODE_HOST = process.env.NODE_HOST || '127.0.0.1';
162 | var NODE_PORT = process.env.NODE_PORT || 3000;
163 | var MONGO_HOST = process.env.MONGO_HOST || '127.0.0.1';
164 | var MONGO_PORT = process.env.MONGO_PORT || 27017;
165 | var LOG_LEVEL = process.env.LOG_LEVEL || 'info';
166 | var APP_NAME = 'node-restify-mongodb-';
167 | ```
168 |
169 | ## Future Project TODOs
170 |
171 | Future project enhancements include the following:
172 |
173 | - Add filtering, sorting, field selection and paging
174 | - Add basic HATEOAS-based response features
175 | - Add authentication and authorization to production MongoDB instance
176 | - Convert from out-dated jasmine-node to Jasmine?
177 |
178 | ## License
179 |
180 | MIT © [Gary A. Stafford](http://www.programmaticponderings.com)
181 |
182 | [coveralls-image]: https://coveralls.io/repos/garystafford/generator-node-restify-mongodb/badge.svg
183 | [coveralls-url]: https://coveralls.io/r/garystafford/generator-node-restify-mongodb
184 | [daviddm-image]: https://david-dm.org/garystafford/generator-node-restify-mongodb.svg?theme=shields.io
185 | [daviddm-url]: https://david-dm.org/garystafford/generator-node-restify-mongodb
186 | [npm-image]: https://badge.fury.io/js/generator-node-restify-mongodb.svg
187 | [npm-url]: https://npmjs.org/package/generator-node-restify-mongodb
188 | [travis-image]: https://travis-ci.org/garystafford/generator-node-restify-mongodb.svg?branch=master
189 | [travis-url]: https://travis-ci.org/garystafford/generator-node-restify-mongodb
190 |
--------------------------------------------------------------------------------
/eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "eqeqeq": "off",
4 | "curly": "warn",
5 | "quotes": ["warn", "double"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/generators/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Generator = require('yeoman-generator');
3 | const chalk = require('chalk');
4 | const yosay = require('yosay');
5 | const mkdirp = require('mkdirp');
6 |
7 | module.exports = class extends Generator {
8 | prompting() {
9 | // Have Yeoman greet the user.
10 | this.log(yosay(
11 | 'Welcome to the ' + chalk.red('Node-Restify-MongoDB v0.3.0') + ' Generator!'
12 | ));
13 | }
14 |
15 | install() {
16 | this.installDependencies({
17 | bower: false,
18 | npm: true
19 | }).then(() => console.log('npm dependencies installed!'))
20 | }
21 |
22 | app() {
23 | this.fs.copy(this.templatePath('app.js'), this.destinationPath('app.js'));
24 | mkdirp('app/models');
25 | mkdirp('app/routes');
26 | this.fs.copy(this.templatePath('app/models/index.js'), this.destinationPath('app/models/index.js'));
27 | this.fs.copy(this.templatePath('app/models/widget.js'), this.destinationPath('app/models/widget.js'));
28 | this.fs.copy(this.templatePath('app/routes/index.js'), this.destinationPath('app/routes/index.js'));
29 | this.fs.copy(this.templatePath('app/routes/widget.js'), this.destinationPath('app/routes/widget.js'));
30 | this.fs.copy(this.templatePath('app/routes/utilities.js'), this.destinationPath('app/routes/utilities.js'));
31 | }
32 |
33 | config() {
34 | mkdirp('config');
35 | this.fs.copy(this.templatePath('config/config.js'), this.destinationPath('config/config.js'));
36 | }
37 |
38 | data() {
39 | mkdirp('data');
40 | this.fs.copy(this.templatePath('data/widgets.json'), this.destinationPath('data/widgets.json'));
41 | }
42 |
43 | spec() {
44 | mkdirp('spec');
45 | this.fs.copy(this.templatePath('spec/utils_spec.js'), this.destinationPath('spec/utils_spec.js'));
46 | this.fs.copy(this.templatePath('spec/widget_spec.js'), this.destinationPath('spec/widget_spec.js'));
47 | }
48 |
49 | postman() {
50 | mkdirp('spec');
51 | this.fs.copy(this.templatePath('postman/localhost-restify.postman_environment.json'),
52 | this.destinationPath('postman/localhost-restify.postman_environment.json'));
53 | this.fs.copy(this.templatePath('postman/Widgets.postman_collection.json'),
54 | this.destinationPath('postman/Widgets.postman_collection.json'));
55 | }
56 |
57 | grunt() {
58 | this.fs.copy(this.templatePath('Gruntfile.js'), this.destinationPath('Gruntfile.js'));
59 | }
60 |
61 | projectfiles() {
62 | this.fs.copy(this.templatePath('package.json'), this.destinationPath('package.json'));
63 | this.fs.copy(this.templatePath('db-connection.js'), this.destinationPath('db-connection.js'));
64 | this.fs.copy(this.templatePath('log.js'), this.destinationPath('log.js'));
65 | this.fs.copy(this.templatePath('.npmignore'), this.destinationPath('.npmignore'));
66 | this.fs.copy(this.templatePath('.gitignore'), this.destinationPath('.gitignore'));
67 | this.fs.copy(this.templatePath('.jshintrc'), this.destinationPath('.jshintrc'));
68 | //npm renames .gitignore in .npmignore when publishing,
69 | //so rename back to .gitignore
70 | if (this.fs.exists(this.templatePath('.npmignore'))) {
71 | this.fs.copy(this.templatePath('.npmignore'), this.destinationPath('.gitignore'));
72 | }
73 | this.fs.copy(this.templatePath('.editorconfig'), this.destinationPath('.editorconfig'));
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/generators/app/templates/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/generators/app/templates/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | # Created by .ignore support plugin (hsz.mobi)
4 | ### Node template
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 |
30 | # Dependency directories
31 | node_modules
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 | ### JetBrains template
40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
42 |
43 | # User-specific stuff:
44 | .idea/workspace.xml
45 | .idea/tasks.xml
46 | .idea/dictionaries
47 | .idea/vcs.xml
48 | .idea/jsLibraryMappings.xml
49 |
50 | # Sensitive or high-churn files:
51 | .idea/dataSources.ids
52 | .idea/dataSources.xml
53 | .idea/dataSources.local.xml
54 | .idea/sqlDataSources.xml
55 | .idea/dynamic.xml
56 | .idea/uiDesigner.xml
57 |
58 | # Gradle:
59 | .idea/gradle.xml
60 | .idea/libraries
61 |
62 | # Mongo Explorer plugin:
63 | .idea/mongoSettings.xml
64 |
65 | ## File-based project format:
66 | *.iws
67 |
68 | ## Plugin-specific files:
69 |
70 | # IntelliJ
71 | /out/
72 |
73 | # mpeltonen/sbt-idea plugin
74 | .idea_modules/
75 |
76 | # JIRA plugin
77 | atlassian-ide-plugin.xml
78 |
79 | # Crashlytics plugin (for Android Studio and IntelliJ)
80 | com_crashlytics_export_strings.xml
81 | crashlytics.properties
82 | crashlytics-build.properties
83 | fabric.properties
84 |
--------------------------------------------------------------------------------
/generators/app/templates/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6
3 | }
4 |
--------------------------------------------------------------------------------
/generators/app/templates/.npmignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 | ### Yeoman template
41 | node_modules/
42 | bower_components/
43 |
44 | build/
45 | dist/
46 | ### JetBrains template
47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
49 |
50 | # User-specific stuff:
51 | .idea/workspace.xml
52 | .idea/tasks.xml
53 | .idea/dictionaries
54 | .idea/vcs.xml
55 | .idea/jsLibraryMappings.xml
56 |
57 | # Sensitive or high-churn files:
58 | .idea/dataSources.ids
59 | .idea/dataSources.xml
60 | .idea/dataSources.local.xml
61 | .idea/sqlDataSources.xml
62 | .idea/dynamic.xml
63 | .idea/uiDesigner.xml
64 |
65 | # Gradle:
66 | .idea/gradle.xml
67 | .idea/libraries
68 |
69 | # Mongo Explorer plugin:
70 | .idea/mongoSettings.xml
71 |
72 | ## File-based project format:
73 | *.iws
74 |
75 | ## Plugin-specific files:
76 |
77 | # IntelliJ
78 | /out/
79 |
80 | # mpeltonen/sbt-idea plugin
81 | .idea_modules/
82 |
83 | # JIRA plugin
84 | atlassian-ide-plugin.xml
85 |
86 | # Crashlytics plugin (for Android Studio and IntelliJ)
87 | com_crashlytics_export_strings.xml
88 | crashlytics.properties
89 | crashlytics-build.properties
90 | fabric.properties
91 |
--------------------------------------------------------------------------------
/generators/app/templates/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var config = require('./config/config');
2 |
3 | module.exports = function (grunt) {
4 | grunt.initConfig({
5 | mongoimport: {
6 | options: {
7 | db: config.db.name,
8 | host: config.db.host,
9 | port: config.db.port,
10 | collections: [{
11 | name: 'widgets',
12 | file: 'data/widgets.json',
13 | jsonArray: true,
14 | drop: true
15 | }]
16 | }
17 | },
18 | exec: {
19 | jshint_test: {
20 | cmd: 'node_modules/jshint/bin/jshint --verbose app/ config/ app.js *.js'
21 | },
22 | jasmine_test: {
23 | cmd: 'node node_modules/jasmine-node/lib/jasmine-node/cli.js --color --verbose --captureExceptions --forceexit spec'
24 | }
25 | },
26 | mocha_istanbul: {
27 | coverage: {
28 | src: 'spec', // a folder works nicely
29 | options: {
30 | mask: '*_spec.js'
31 | }
32 | }
33 | },
34 | istanbul_check_coverage: {
35 | default: {
36 | options: {
37 | coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results
38 | check: {
39 | lines: 80,
40 | statements: 80
41 | }
42 | }
43 | }
44 | }
45 | });
46 |
47 | grunt.event.on('coverage', function (lcov, done) {
48 | console.log(lcov);
49 | done(); // or done(false); in case of error
50 | });
51 |
52 |
53 | grunt.loadNpmTasks('grunt-mongoimport');
54 | grunt.loadNpmTasks('grunt-exec');
55 | grunt.loadNpmTasks('grunt-mocha-istanbul');
56 |
57 | grunt.registerTask('default', ['mongoimport', 'exec:jshint_test', 'exec:jasmine_test']);
58 | grunt.registerTask('test', ['exec:jshint_test', 'exec:jasmine_test']);
59 | grunt.registerTask('coverage', ['mocha_istanbul:coverage']);
60 | };
61 |
--------------------------------------------------------------------------------
/generators/app/templates/app.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let restify = require('restify');
5 | let path = require('path');
6 |
7 | let config = require(path.join(__dirname, '/config/config'));
8 | let log = require(path.join(__dirname, '/log'));
9 | let models = require(path.join(__dirname, '/app/models/'));
10 | let routes = require(path.join(__dirname, '/app/routes/'));
11 | let dbConnection = require(path.join(__dirname, '/db-connection'));
12 |
13 | dbConnection();
14 |
15 | let server = restify.createServer({
16 | name: config.app.name,
17 | log: log
18 | });
19 |
20 | server.use(restify.plugins.bodyParser());
21 | server.use(restify.plugins.queryParser());
22 | server.use(restify.plugins.gzipResponse());
23 | server.pre(restify.pre.sanitizePath());
24 | server.use(
25 | function crossOrigin(req, res, next) {
26 | res.header('Access-Control-Allow-Origin', '*');
27 | res.header('Access-Control-Allow-Headers', 'X-Requested-With');
28 | return next();
29 | }
30 | );
31 |
32 | /*jslint unparam:true*/
33 | // Default error handler. Personalize according to your needs.
34 | server.on('uncaughtException', function (req, res, route, err) {
35 | log.info('******* Begin Error *******\n%s\n*******\n%s\n******* End Error *******', route, err.stack);
36 | if (!res.headersSent) {
37 | return res.send(500, {ok: false});
38 | }
39 | res.write('\n');
40 | res.end();
41 | });
42 |
43 | // server.on('after', restify.plugins.auditLogger(
44 | // {
45 | // log: log
46 | // })
47 | // );
48 |
49 | models();
50 | routes(server);
51 |
52 | server.get('/', function (req, res, next) {
53 | res.send(config.app.name);
54 | return next();
55 | });
56 |
57 | server.listen(config.app.port, function () {
58 | log.info('Application %s listening at %s:%s', config.app.name, config.app.address, config.app.port);
59 | });
60 |
--------------------------------------------------------------------------------
/generators/app/templates/app/models/index.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let fs = require('fs');
5 | let path = require('path');
6 |
7 | module.exports = function (server) {
8 | fs.readdirSync(path.join(__dirname, '.')).forEach(function (file) {
9 | if (file.substr(-3, 3) === '.js' && file !== 'index.js') {
10 | require('./' + file.replace('.js', ''))(server);
11 | }
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/generators/app/templates/app/models/widget.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let mongoose = require('mongoose');
5 | require('mongoose-currency').loadType(mongoose);
6 | let uniqueValidator = require('mongoose-unique-validator');
7 | let Schema = mongoose.Schema;
8 | let Currency = mongoose.Types.Currency;
9 |
10 | let widgetSchema = new Schema({
11 | product_id: {type: String, required: true, index: true, unique: true},
12 | name: {type: String, required: true},
13 | color: {
14 | type: String, required: true,
15 | enum: ['Red', 'Blue', 'Yellow', 'Green', 'Orange', 'Purple', 'White', 'Black']
16 | },
17 | size: {
18 | type: String, required: true,
19 | enum: ['Huge', 'Big', 'Medium', 'Small', 'Tiny']
20 | },
21 | price: {type: Currency, required: true},
22 | inventory: {type: Number, required: true, min: 0}
23 | });
24 |
25 | widgetSchema.set('timestamps', true); // include timestamps in docs
26 |
27 | // apply the mongoose unique validator plugin to widgetSchema
28 | widgetSchema.plugin(uniqueValidator);
29 |
30 | // use mongoose currency to transform price
31 | widgetSchema.set('toJSON', {
32 | virtuals: true,
33 | transform: function (doc, ret, options) {
34 | ret.price = Number(ret.price / 100).toFixed(2);
35 | delete ret.__v; // hide
36 | delete ret._id; // hide
37 | }
38 | });
39 |
40 | let Widget = mongoose.model('Widget', widgetSchema);
41 |
42 | module.exports = Widget;
43 |
--------------------------------------------------------------------------------
/generators/app/templates/app/routes/index.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let fs = require('fs');
5 |
6 | module.exports = function (server) {
7 | fs.readdirSync('./app/routes').forEach(function (file) {
8 | if (file.substr(-3, 3) === '.js' && file !== 'index.js') {
9 | require('./' + file.replace('.js', ''))(server);
10 | }
11 | });
12 | };
13 |
--------------------------------------------------------------------------------
/generators/app/templates/app/routes/utilities.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let path = require('path');
5 | let config = require(path.join(__dirname, '../../config/config'));
6 |
7 | let PATH = '/utils';
8 | let VERSION = '1.0.0';
9 |
10 | module.exports = function (server) {
11 | let ping = (req, res, next) => {
12 | res.send(200, 'true');
13 | return next();
14 | };
15 | server.get({path: PATH + '/ping', version: VERSION}, ping);
16 | server.get({path: PATH + '/health', version: VERSION}, health);
17 | server.get({path: PATH + '/info', version: VERSION}, information);
18 | server.get({path: PATH + '/config', version: VERSION}, configuration);
19 | server.get({path: PATH + '/env', version: VERSION}, environment);
20 |
21 |
22 | function health(req, res, next) {
23 | res.json(200, {status: 'UP'});
24 | return next();
25 | }
26 |
27 | // Can be found with env URI
28 | function information(req, res, next) {
29 | let app_info = {
30 | name: process.env.npm_package_name,
31 | version: process.env.npm_package_version,
32 | description: process.env.npm_package_description,
33 | author: process.env.npm_package_author
34 | };
35 | res.send(200, app_info);
36 | return next();
37 | }
38 |
39 | function configuration(req, res, next) {
40 | res.send(200, config);
41 | return next();
42 | }
43 |
44 | function environment(req, res, next) {
45 | res.send(200, process.env);
46 | return next();
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/generators/app/templates/app/routes/widget.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | let mongoose = require('mongoose');
5 | let path = require('path');
6 | let config = require(path.join(__dirname, '../../config/config'));
7 | let Widget = mongoose.model('Widget');
8 |
9 | let PATH = '/widgets';
10 | let VERSION = '1.0.0';
11 |
12 | module.exports = function (server) {
13 | server.get({path: PATH, version: VERSION}, findDocuments);
14 | server.get({path: PATH + '/:product_id', version: VERSION}, findOneDocument);
15 | server.post({path: PATH, version: VERSION}, createDocument);
16 | server.put({path: PATH, version: VERSION}, updateDocument);
17 | server.del({path: PATH + '/:product_id', version: VERSION}, deleteDocument);
18 |
19 | function findDocuments(req, res, next) {
20 | // http://mongoosejs.com/docs/api.html#model_Model.find
21 | let conditions = {};
22 | let projection = {};
23 | let options = {};
24 |
25 | Widget.find(conditions, projection, options).sort({'name': 1}).exec(function (error, widgets) {
26 | if (error) {
27 | return next(error);
28 | } else {
29 | res.header('X-Total-Count', widgets.length);
30 | res.send(200, widgets);
31 | return next();
32 | }
33 | });
34 | }
35 |
36 | function findOneDocument(req, res, next) {
37 | // http://mongoosejs.com/docs/api.html#model_Model.findOne
38 | let conditions = {'product_id': req.params.product_id};
39 | let projection = {};
40 | let options = {};
41 |
42 | Widget.findOne(conditions, projection, options, function (error, widget) {
43 | if (error) {
44 | return next(error);
45 | } else {
46 | res.send(200, widget);
47 | return next();
48 | }
49 | });
50 | }
51 |
52 | function createDocument(req, res, next) {
53 | let widget = new Widget({
54 | product_id: req.body.product_id,
55 | name: req.body.name,
56 | color: req.body.color,
57 | size: req.body.size,
58 | price: req.body.price,
59 | inventory: req.body.inventory
60 | });
61 |
62 | // http://mongoosejs.com/docs/api.html#model_Model-save
63 | widget.save(function (error, widget, numAffected) {
64 | if (error) {
65 | return next(error);
66 | } else {
67 | res.send(204);
68 | return next();
69 | }
70 | });
71 | }
72 |
73 | function updateDocument(req, res, next) {
74 | // http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
75 | let conditions = {'product_id': req.body.product_id};
76 | let update = {
77 | 'name': req.body.name,
78 | 'color': req.body.color,
79 | 'size': req.body.size,
80 | 'price': req.body.price,
81 | 'inventory': req.body.inventory
82 | };
83 | let options = {runValidators: true, context: 'query'};
84 |
85 | Widget.findOneAndUpdate(conditions, update, options, function (error) {
86 | if (error) {
87 | return next(error);
88 | } else {
89 | res.send(204);
90 | return next();
91 | }
92 | });
93 | }
94 |
95 | function deleteDocument(req, res, next) {
96 | // http://mongoosejs.com/docs/api.html#query_Query-remove
97 | let options = {'product_id': req.params.product_id};
98 |
99 | Widget.remove(options, function (error) {
100 | if (error) {
101 | return next(error);
102 | } else {
103 | res.send(204);
104 | return next();
105 | }
106 | });
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/generators/app/templates/config/config.js:
--------------------------------------------------------------------------------
1 | /* jslint node: true */
2 | 'use strict';
3 |
4 | let path = require('path');
5 |
6 | let rootPath = path.normalize(__dirname + '/..');
7 |
8 | let NODE_ENV = process.env.NODE_ENV || 'development';
9 | let NODE_HOST = process.env.NODE_HOST || '127.0.0.1';
10 | let NODE_PORT = process.env.NODE_PORT || 3000;
11 | let MONGO_HOST = process.env.MONGO_HOST || '127.0.0.1';
12 | let MONGO_PORT = process.env.MONGO_PORT || 27017;
13 | let LOG_LEVEL = process.env.LOG_LEVEL || 'info';
14 |
15 | let APP_NAME = 'node-restify-mongodb-';
16 |
17 | let config = {
18 | development: {
19 | root: rootPath,
20 | app: {
21 | name: APP_NAME + NODE_ENV,
22 | address: NODE_HOST,
23 | port: NODE_PORT
24 | },
25 | db: {
26 | host: MONGO_HOST,
27 | port: MONGO_PORT,
28 | name: APP_NAME + NODE_ENV
29 | },
30 | log: {
31 | name: APP_NAME + NODE_ENV,
32 | level: LOG_LEVEL
33 | }
34 | },
35 | test: {
36 | root: rootPath,
37 | app: {
38 | name: APP_NAME + NODE_ENV,
39 | address: NODE_HOST,
40 | port: NODE_PORT
41 | },
42 | db: {
43 | host: MONGO_HOST,
44 | port: MONGO_PORT,
45 | name: APP_NAME + NODE_ENV
46 | },
47 | log: {
48 | name: APP_NAME + NODE_ENV,
49 | level: LOG_LEVEL
50 | }
51 | },
52 | production: {
53 | root: rootPath,
54 | app: {
55 | name: APP_NAME + NODE_ENV,
56 | address: NODE_HOST,
57 | port: NODE_PORT
58 | },
59 | db: {
60 | host: MONGO_HOST,
61 | port: MONGO_PORT,
62 | name: APP_NAME + NODE_ENV
63 | },
64 | log: {
65 | name: APP_NAME + NODE_ENV,
66 | level: LOG_LEVEL
67 | }
68 | }
69 | };
70 |
71 | module.exports = config[NODE_ENV];
72 |
--------------------------------------------------------------------------------
/generators/app/templates/data/widgets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "product_id": "RTHGP1FCGN",
4 | "name": "Reflupper",
5 | "color": "Red",
6 | "size": "Large",
7 | "price": "$12.95",
8 | "inventory": 25
9 | },
10 | {
11 | "product_id": "SVHXPAWEOD",
12 | "name": "Voonex",
13 | "color": "Green",
14 | "size": "Medium",
15 | "price": "$10.99",
16 | "inventory": 50
17 | },
18 | {
19 | "product_id": "GKO1SFX04M",
20 | "name": "Jukelox",
21 | "color": "Blue",
22 | "size": "Small",
23 | "price": "$25.49",
24 | "inventory": 75
25 | },
26 | {
27 | "product_id": "3YIRGZ6TDW",
28 | "name": "Groopster",
29 | "color": "Yellow",
30 | "size": "Large",
31 | "price": "$99.95",
32 | "inventory": 100
33 | },
34 | {
35 | "product_id": "4OZNPBMIDR",
36 | "name": "Fapster",
37 | "color": "Orange",
38 | "size": "Medium",
39 | "price": "29.99",
40 | "inventory": 5
41 | },
42 | {
43 | "product_id": "6T2HC5MIZ9",
44 | "name": "Chaintwist",
45 | "color": "Purple",
46 | "size": "Tiny",
47 | "price": "$99.95",
48 | "inventory": 15
49 | },
50 | {
51 | "product_id": "ERZ1RMJFR3",
52 | "name": "Glozzom",
53 | "color": "Red",
54 | "size": "Huge",
55 | "price": "$199.98",
56 | "inventory": 35
57 | },
58 | {
59 | "product_id": "0BVCLPDZ42",
60 | "name": "Chaintwist",
61 | "color": "Blue",
62 | "size": "Medium",
63 | "price": "$89.95",
64 | "inventory": 55
65 | },
66 | {
67 | "product_id": "N43WV5234S",
68 | "name": "Zapster",
69 | "color": "Green",
70 | "size": "Tiny",
71 | "price": "$17.49",
72 | "inventory": 65
73 | },
74 | {
75 | "product_id": "N212QZOD9B",
76 | "name": "Pentwist",
77 | "color": "Yellow",
78 | "size": "Huge",
79 | "price": "$159.98",
80 | "inventory": 95
81 | }
82 | ]
83 |
--------------------------------------------------------------------------------
/generators/app/templates/db-connection.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | var mongoose = require('mongoose');
5 | mongoose.Promise = require('bluebird');
6 | var path = require('path');
7 |
8 | var config = require(path.join(__dirname, '/config/config'));
9 | var log = require(path.join(__dirname, 'log'));
10 |
11 | module.exports = function () {
12 | var uri = ''.concat('mongodb://', config.db.host, ':', config.db.port, '/', config.db.name);
13 | var options = { useMongoClient: true, promiseLibrary: require('bluebird') };
14 | mongoose.connect(uri);
15 | // mongoose.connect(uri, options);
16 | // var db = mongoose.createConnection(uri, options);
17 |
18 | // db.on('connected', function () {
19 | // log.info('Mongodb connection open to ' + db_url);
20 | // });
21 | // db.on('error', function () {
22 | // throw new Error('unable to connect to database at ' + db_url);
23 | // });
24 | // db.on('disconnected', function () {
25 | // log.info('Mongodb connection disconnected');
26 | // });
27 | // process.on('SIGINT', function () {
28 | // db.close(function () {
29 | // log.info('Mongodb connection disconnected through app termination');
30 | // process.exit(0);
31 | // });
32 | // });
33 | };
34 |
--------------------------------------------------------------------------------
/generators/app/templates/log.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | var bunyan = require('bunyan');
5 | var path = require('path');
6 |
7 | var config = require(path.join(__dirname, '/config/config'));
8 |
9 | var log = bunyan.createLogger({
10 | name: config.log.name,
11 | level: config.log.level,
12 | stream: process.stdout,
13 | serializers: bunyan.stdSerializers
14 | });
15 |
16 | module.exports = log;
17 |
--------------------------------------------------------------------------------
/generators/app/templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-restify-mongodb",
3 | "version": "0.0.0",
4 | "description": "Basic RESTful microservice, based on Node, Restify, and MongoDB",
5 | "author": "Author Name",
6 | "license": "",
7 | "repository": "",
8 | "main": "app.js",
9 | "scripts": {
10 | "start": "node app.js | ./node_modules/bunyan/bin/bunyan",
11 | "test": "grunt test"
12 | },
13 | "dependencies": {
14 | "bluebird": "^3.x",
15 | "bunyan": "^1.x",
16 | "mongoose": "^4.x",
17 | "mongoose-currency": "^0.x",
18 | "mongoose-unique-validator": "^1.x",
19 | "restify": "^6.x"
20 | },
21 | "devDependencies": {
22 | "grunt": "^1.x",
23 | "grunt-exec": "^3.x",
24 | "grunt-mocha-istanbul": "^5.x",
25 | "grunt-mongoimport": "^0.x",
26 | "istanbul": "1.1.0-alpha.1",
27 | "jasmine-node": "^1.x",
28 | "jshint": "^2.x",
29 | "mocha": "^4.x",
30 | "request": "^2.x",
31 | "should": "^13.x"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/generators/app/templates/postman/Widgets.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "variables": [],
3 | "info": {
4 | "name": "Widgets",
5 | "_postman_id": "b0554bde-d0fe-448d-3203-4bbb95e54c96",
6 | "description": "",
7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
8 | },
9 | "item": [
10 | {
11 | "name": "/widgets",
12 | "request": {
13 | "url": "http://{{hostname}}:{{port}}/widgets",
14 | "method": "GET",
15 | "header": [
16 | {
17 | "key": "Accept",
18 | "value": "application/json",
19 | "description": ""
20 | }
21 | ],
22 | "body": {
23 | "mode": "formdata",
24 | "formdata": []
25 | },
26 | "description": ""
27 | },
28 | "response": []
29 | },
30 | {
31 | "name": "/widgets/N212QZOD9B",
32 | "request": {
33 | "url": "http://{{hostname}}:{{port}}/widgets/N212QZOD9B",
34 | "method": "GET",
35 | "header": [
36 | {
37 | "key": "Accept",
38 | "value": "application/json",
39 | "description": ""
40 | }
41 | ],
42 | "body": {
43 | "mode": "formdata",
44 | "formdata": []
45 | },
46 | "description": ""
47 | },
48 | "response": []
49 | },
50 | {
51 | "name": "/widgets/N212QZOD9B",
52 | "request": {
53 | "url": "http://{{hostname}}:{{port}}/widgets/N212QZOD9B",
54 | "method": "DELETE",
55 | "header": [
56 | {
57 | "key": "Accept",
58 | "value": "application/json",
59 | "description": ""
60 | }
61 | ],
62 | "body": {
63 | "mode": "formdata",
64 | "formdata": []
65 | },
66 | "description": ""
67 | },
68 | "response": []
69 | },
70 | {
71 | "name": "/widgets",
72 | "request": {
73 | "url": "http://{{hostname}}:{{port}}/widgets",
74 | "method": "POST",
75 | "header": [
76 | {
77 | "key": "Accept",
78 | "value": "application/json",
79 | "description": ""
80 | },
81 | {
82 | "key": "Content-Type",
83 | "value": "application/json",
84 | "description": ""
85 | }
86 | ],
87 | "body": {
88 | "mode": "raw",
89 | "raw": "{\n\t\"product_id\": \"N212QZOD9B\",\n\t\"name\": \"Pentwist\",\n\t\"color\": \"Yellow\",\n\t\"size\": \"Huge\",\n\t\"price\": \"$159.98\",\n\t\"inventory\": 95\n}"
90 | },
91 | "description": ""
92 | },
93 | "response": []
94 | },
95 | {
96 | "name": "/widgets",
97 | "request": {
98 | "url": "http://{{hostname}}:{{port}}/widgets",
99 | "method": "PUT",
100 | "header": [
101 | {
102 | "key": "Accept",
103 | "value": "application/json",
104 | "description": ""
105 | },
106 | {
107 | "key": "Content-Type",
108 | "value": "application/json",
109 | "description": ""
110 | }
111 | ],
112 | "body": {
113 | "mode": "raw",
114 | "raw": "{\n\t\"product_id\": \"N212QZOD9B\",\n\t\"name\": \"Pentwist\",\n\t\"color\": \"Yellow\",\n\t\"size\": \"Huge\",\n\t\"price\": \"$169.98\",\n\t\"inventory\": 91\n}"
115 | },
116 | "description": ""
117 | },
118 | "response": []
119 | },
120 | {
121 | "name": "/utils/ping",
122 | "request": {
123 | "url": "http://{{hostname}}:{{port}}/utils/ping",
124 | "method": "GET",
125 | "header": [
126 | {
127 | "key": "Accept",
128 | "value": "text/plain",
129 | "description": ""
130 | }
131 | ],
132 | "body": {
133 | "mode": "formdata",
134 | "formdata": []
135 | },
136 | "description": ""
137 | },
138 | "response": []
139 | },
140 | {
141 | "name": "/utils/health",
142 | "request": {
143 | "url": "http://{{hostname}}:{{port}}/utils/health",
144 | "method": "GET",
145 | "header": [
146 | {
147 | "key": "Accept",
148 | "value": "application/json",
149 | "description": ""
150 | }
151 | ],
152 | "body": {
153 | "mode": "formdata",
154 | "formdata": []
155 | },
156 | "description": ""
157 | },
158 | "response": []
159 | },
160 | {
161 | "name": "/utils/info",
162 | "request": {
163 | "url": "http://{{hostname}}:{{port}}/utils/info",
164 | "method": "GET",
165 | "header": [
166 | {
167 | "key": "Accept",
168 | "value": "application/json",
169 | "description": ""
170 | }
171 | ],
172 | "body": {
173 | "mode": "formdata",
174 | "formdata": []
175 | },
176 | "description": ""
177 | },
178 | "response": []
179 | },
180 | {
181 | "name": "/utils/config",
182 | "request": {
183 | "url": "http://{{hostname}}:{{port}}/utils/config",
184 | "method": "GET",
185 | "header": [
186 | {
187 | "key": "Accept",
188 | "value": "application/json",
189 | "description": ""
190 | }
191 | ],
192 | "body": {
193 | "mode": "formdata",
194 | "formdata": []
195 | },
196 | "description": ""
197 | },
198 | "response": []
199 | },
200 | {
201 | "name": "/utils/env",
202 | "request": {
203 | "url": "http://{{hostname}}:{{port}}/utils/env",
204 | "method": "GET",
205 | "header": [
206 | {
207 | "key": "Accept",
208 | "value": "application/json",
209 | "description": ""
210 | }
211 | ],
212 | "body": {
213 | "mode": "formdata",
214 | "formdata": []
215 | },
216 | "description": ""
217 | },
218 | "response": []
219 | }
220 | ]
221 | }
--------------------------------------------------------------------------------
/generators/app/templates/postman/localhost-restify.postman_environment.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "c1707fb1-1ecf-c120-8a2b-49e544b7a615",
3 | "name": "localhost-restify",
4 | "values": [
5 | {
6 | "key": "hostname",
7 | "value": "localhost",
8 | "type": "text",
9 | "enabled": true,
10 | "hovered": false
11 | },
12 | {
13 | "key": "port",
14 | "value": "3000",
15 | "type": "text",
16 | "enabled": true,
17 | "hovered": false
18 | }
19 | ],
20 | "team": null,
21 | "timestamp": 1466485702100,
22 | "synced": false,
23 | "syncedFilename": "",
24 | "isDeleted": false
25 | }
--------------------------------------------------------------------------------
/generators/app/templates/spec/utils_spec.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | //////////// VARIABLES ///////////////////
5 |
6 | let request = require('request');
7 | let should = require('should');
8 | let path = require('path');
9 |
10 | let config = require(path.join(__dirname, '../config/config'));
11 | let base_url = ''.concat('http://', config.app.address, ':', config.app.port);
12 |
13 |
14 | //////////// TESTS ///////////////////////
15 |
16 | describe('Utility URIs', function () {
17 | describe('GET /utils/ping', function () {
18 | let url = base_url + '/utils/ping';
19 |
20 | let options = {
21 | method: 'GET',
22 | url: url,
23 | headers: {
24 | accept: 'text/plain'
25 | }
26 | };
27 |
28 | it('should respond with a status code of 200', function (done) {
29 | request(options, function (error, response, body) {
30 | response.statusCode.should.be.exactly(200);
31 | done();
32 | });
33 | });
34 |
35 | it('should respond with \'true\'', function (done) {
36 | request(options, function (error, response, body) {
37 | body.should.be.exactly('true');
38 | done();
39 | });
40 | });
41 | });
42 |
43 | describe('GET /utils/health', function () {
44 | let url = base_url + '/utils/health';
45 |
46 | let options = {
47 | method: 'GET',
48 | url: url,
49 | headers: {
50 | accept: 'application/json'
51 | }
52 | };
53 |
54 | it('should respond with a status code of 200', function (done) {
55 | request(options, function (error, response, body) {
56 | response.statusCode.should.be.exactly(200);
57 | done();
58 | });
59 | });
60 |
61 | it('should respond with a \'status\' of \'UP\'', function (done) {
62 | request(options, function (error, response, body) {
63 | let status = JSON.parse(body);
64 | status.should.have.a.property('status', 'UP');
65 | done();
66 | });
67 | });
68 | });
69 |
70 | describe('GET /utils/info', function () {
71 | let url = base_url + '/utils/info';
72 |
73 | let options = {
74 | method: 'GET',
75 | url: url,
76 | headers: {
77 | accept: 'application/json'
78 | }
79 | };
80 |
81 | it('should respond with a status code of 200', function (done) {
82 | request(options, function (error, response, body) {
83 | response.statusCode.should.be.exactly(200);
84 | done();
85 | });
86 | });
87 |
88 | it('should respond with exactly (1) \'info\' object containing (3) properties', function (done) {
89 | request(options, function (error, response, body) {
90 | let info = JSON.parse(body);
91 | Object.keys(info).should.have.a.lengthOf(3);
92 | done();
93 | });
94 | });
95 | });
96 |
97 | describe('GET /utils/config', function () {
98 | let url = base_url + '/utils/config';
99 |
100 | let options = {
101 | method: 'GET',
102 | url: url,
103 | headers: {
104 | accept: 'application/json'
105 | }
106 | };
107 |
108 | it('should respond with a status code of 200', function (done) {
109 | request(options, function (error, response, body) {
110 | response.statusCode.should.be.exactly(200);
111 | done();
112 | });
113 | });
114 |
115 | it('should respond with exactly (1) non-null \'config\' object', function (done) {
116 | request(options, function (error, response, body) {
117 | let config = JSON.parse(body);
118 | config.should.be.an.instanceof(Object).and.not.null;
119 | done();
120 | });
121 | });
122 | });
123 |
124 | describe('GET /utils/env', function () {
125 | let url = base_url + '/utils/env';
126 |
127 | let options = {
128 | method: 'GET',
129 | url: url,
130 | headers: {
131 | accept: 'application/json'
132 | }
133 | };
134 |
135 | it('should respond with a status code of 200', function (done) {
136 | request(options, function (error, response, body) {
137 | response.statusCode.should.be.exactly(200);
138 | done();
139 | });
140 | });
141 |
142 | it('should respond with exactly (1) non-null \'env\' object', function (done) {
143 | request(options, function (error, response, body) {
144 | let env = JSON.parse(body);
145 | env.should.be.an.instanceof(Object).and.not.null;
146 | done();
147 | });
148 | });
149 | });
150 | });
151 |
--------------------------------------------------------------------------------
/generators/app/templates/spec/widget_spec.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | 'use strict';
3 |
4 | //////////// VARIABLES ///////////////////
5 |
6 | let request = require('request');
7 | let mongoose = require('mongoose');
8 | let should = require('should');
9 | let path = require('path');
10 |
11 | let config = require(path.join(__dirname, '../config/config'));
12 | let base_url = ''.concat('http://', config.app.address, ':', config.app.port);
13 |
14 | let dbConnection = require(path.join(__dirname, '../db-connection'));
15 | dbConnection();
16 |
17 | let widget_model = require(path.join(__dirname, '../app/models/widget'));
18 | widget_model();
19 | let Widget = mongoose.model('Widget');
20 |
21 | let log = require(path.join(__dirname, '../log'));
22 |
23 |
24 | //////////// HELPER FUNCTIONS ////////////
25 |
26 | function saveWidget(widget) {
27 | widget.save(function (error) {
28 | if (error) {
29 | log.error(error);
30 | }
31 | });
32 | }
33 |
34 | function removeWidgets(options) {
35 | Widget.remove(options, function (error) {
36 | if (error) {
37 | log.error(error);
38 | }
39 | });
40 | }
41 |
42 |
43 | //////////// TESTS ///////////////////////
44 |
45 | // find all widgets
46 | describe('Widget URIs', function () {
47 | describe('GET /widgets', function () {
48 | beforeEach(function () {
49 | removeWidgets({});
50 |
51 | saveWidget(new Widget({
52 | product_id: 'YMIYW2VROS',
53 | name: 'TestWidget_YMIYW2VROS',
54 | color: 'Black',
55 | size: 'Huge',
56 | price: '$99.99',
57 | inventory: 96
58 | }));
59 |
60 | saveWidget(new Widget({
61 | product_id: 'FGBRYL6XSF',
62 | name: 'TestWidget_FGBRYL6XSF',
63 | color: 'Red',
64 | size: 'Huge',
65 | price: '$127.49',
66 | inventory: 1205
67 | }));
68 |
69 | saveWidget(new Widget({
70 | product_id: '5H7HW8Y1E2',
71 | name: 'TestWidget_5H7HW8Y1E2',
72 | color: 'Black',
73 | size: 'Tiny',
74 | price: '$1.49',
75 | inventory: 0
76 | }));
77 | });
78 |
79 | afterEach(function () {
80 | removeWidgets({});
81 | });
82 |
83 | let url = base_url + '/widgets';
84 |
85 | let options = {
86 | method: 'GET',
87 | url: url,
88 | headers: {
89 | accept: 'application/json'
90 | }
91 | };
92 |
93 | it('should respond with a status code of 200', function (done) {
94 | request(options, function (error, response, body) {
95 | response.statusCode.should.be.exactly(200);
96 | done();
97 | });
98 | });
99 |
100 | it('should respond with exactly (3) widget objects in an array', function (done) {
101 | request(options, function (error, response, body) {
102 | let widget = JSON.parse(body);
103 | widget.should.be.an.instanceof(Array).and.have.a.lengthOf(3);
104 | done();
105 | });
106 | });
107 |
108 | it('should respond with an \'x-total-count\' header containing a value of \'3\'', function (done) {
109 | request(options, function (error, response, body) {
110 | response.headers.should.have.a.property('x-total-count', '3');
111 | done();
112 | });
113 | });
114 | });
115 |
116 | // find one widget
117 | describe('GET /widgets/:product_id', function () {
118 | beforeEach(function () {
119 | removeWidgets({});
120 |
121 | saveWidget(new Widget({
122 | product_id: '4YFZH127BX',
123 | name: 'TestWidget_4YFZH127BX',
124 | color: 'Orange',
125 | size: 'Small',
126 | price: '$19.93',
127 | inventory: 13
128 | }));
129 |
130 | saveWidget(new Widget({
131 | product_id: '0EJLZK6BK8',
132 | name: 'TestWidget_0EJLZK6BK8',
133 | color: 'Red',
134 | size: 'Huge',
135 | price: '$137.49',
136 | inventory: 46
137 | }));
138 | });
139 |
140 | afterEach(function () {
141 | removeWidgets({});
142 | });
143 |
144 | let url1 = base_url + '/widgets/4YFZH127BX';
145 |
146 | let options1 = {
147 | method: 'GET',
148 | url: url1,
149 | headers: {
150 | accept: 'application/json'
151 | }
152 | };
153 |
154 | it('should respond with a status code of 200', function (done) {
155 | request(options1, function (error, response, body) {
156 | response.statusCode.should.be.exactly(200);
157 | done();
158 | });
159 | });
160 |
161 | it('should respond with exactly (1) widget object', function (done) {
162 | request(options1, function (error, response, body) {
163 | let widget = JSON.parse(body);
164 | widget.should.be.an.instanceof(Object);
165 | done();
166 | });
167 | });
168 |
169 | it('should respond with the value of \'TestWidget_4YFZH127BX\' for \'name\' key', function (done) {
170 | request(options1, function (error, response, body) {
171 | let widget = JSON.parse(body);
172 | widget.should.have.a.property('name', 'TestWidget_4YFZH127BX');
173 | done();
174 | });
175 | });
176 |
177 | let url2 = base_url + '/widgets/BADPRODUCT';
178 |
179 | let options2 = {
180 | method: 'GET',
181 | url: url2,
182 | headers: {
183 | accept: 'application/json'
184 | }
185 | };
186 |
187 | it('should respond with \'null\' when the \'product_id\' is not found', function (done) {
188 | request(options2, function (error, response, body) {
189 | body.should.be.null;
190 | done();
191 | });
192 | });
193 | });
194 |
195 | // create new widget
196 | describe('POST /widgets', function () {
197 | beforeEach(function () {
198 | removeWidgets({});
199 | });
200 | afterEach(function () {
201 | removeWidgets({});
202 | });
203 |
204 | let widget = {
205 | product_id: 'DC3NHTGNAY',
206 | name: 'TestWidget_DC3NHTGNAY',
207 | color: 'Green',
208 | size: 'Big',
209 | price: '$79.92',
210 | inventory: 27
211 | };
212 |
213 | let url = base_url + '/widgets';
214 |
215 | let options = {
216 | method: 'POST',
217 | url: url,
218 | headers: {
219 | accept: 'application/json'
220 | },
221 | body: widget,
222 | json: true
223 | };
224 |
225 | it('should respond with a status code of 204', function (done) {
226 | request(options, function (error, response, body) {
227 | response.statusCode.should.be.exactly(204);
228 | done();
229 | });
230 | });
231 | });
232 |
233 | // create new widget
234 | describe('POST /widgets', function () {
235 | beforeEach(function () {
236 | removeWidgets({});
237 | });
238 | afterEach(function () {
239 | removeWidgets({});
240 | });
241 |
242 | let widget = { // no 'name' key
243 | product_id: 'DC3NHTGNAY',
244 | color: 'Green',
245 | size: 'Big',
246 | price: '$79.92',
247 | inventory: 27
248 | };
249 |
250 | let url = base_url + '/widgets';
251 |
252 | let options = {
253 | method: 'POST',
254 | url: url,
255 | headers: {
256 | accept: 'application/json'
257 | },
258 | body: widget,
259 | json: true
260 | };
261 |
262 | it('should respond with a status code of 500 when missing \'name\' property', function (done) {
263 | request(options, function (error, response, body) {
264 | response.statusCode.should.be.exactly(500);
265 | done();
266 | });
267 | });
268 |
269 | it('should respond with an error message: \'Widget validation failed\' when missing \'name\' property', function (done) {
270 | request(options, function (error, response, body) {
271 | body.should.have.a.property('_message', 'Widget validation failed');
272 | done();
273 | });
274 | });
275 | });
276 |
277 | // update one widget
278 | describe('PUT /widgets', function () {
279 | beforeEach(function () {
280 | removeWidgets({});
281 |
282 | saveWidget(new Widget({
283 | product_id: 'ZC7DV7BSPE',
284 | name: 'TestWidget_ZC7DV7BSPE',
285 | color: 'Blue',
286 | size: 'Small',
287 | price: '$9.92',
288 | inventory: 27
289 | }));
290 | });
291 |
292 | afterEach(function () {
293 | removeWidgets({});
294 | });
295 |
296 | let widget = { // modified inventory level
297 | product_id: 'ZC7DV7BSPE',
298 | name: 'TestWidget_ZC7DV7BSPE',
299 | color: 'Blue',
300 | size: 'Small',
301 | price: '$9.92',
302 | inventory: 21
303 | };
304 |
305 | let url = base_url + '/widgets';
306 |
307 | let options = {
308 | method: 'PUT',
309 | url: url,
310 | headers: {
311 | accept: 'application/json'
312 | },
313 | body: widget,
314 | json: true
315 | };
316 |
317 | it('should respond with a status code of 204', function (done) {
318 | request(options, function (error, response, body) {
319 | response.statusCode.should.be.exactly(204);
320 | done();
321 | });
322 | });
323 |
324 | it('should respond with no response body', function (done) {
325 | request(options, function (error, response, body) {
326 | response.should.not.have.a.property('body');
327 | done();
328 | });
329 | });
330 | });
331 |
332 | // update and confirm one widget
333 | describe('PUT /widgets', function () {
334 | beforeEach(function () {
335 | removeWidgets({});
336 |
337 | saveWidget(new Widget({
338 | product_id: 'ZC7DV7BSPE',
339 | name: 'TestWidget_ZC7DV7BSPE',
340 | color: 'Blue',
341 | size: 'Small',
342 | price: '$9.92',
343 | inventory: 27
344 | }));
345 |
346 | let widget = { // modified inventory level
347 | product_id: 'ZC7DV7BSPE',
348 | name: 'TestWidget_ZC7DV7BSPE',
349 | color: 'Blue',
350 | size: 'Small',
351 | price: '$9.92',
352 | inventory: 21
353 | };
354 |
355 | let url1 = base_url + '/widgets';
356 |
357 | let options1 = {
358 | method: 'PUT',
359 | url: url1,
360 | headers: {
361 | accept: 'application/json'
362 | },
363 | body: widget,
364 | json: true
365 | };
366 |
367 | request(options1, function (error, response, body) {
368 | });
369 | });
370 |
371 | afterEach(function () {
372 | removeWidgets({});
373 | });
374 |
375 | let url2 = base_url + '/widgets/ZC7DV7BSPE';
376 |
377 | let options2 = {
378 | method: 'GET',
379 | url: url2,
380 | headers: {
381 | accept: 'application/json'
382 | }
383 | };
384 |
385 | it('should respond with new value of \'21\' for \'inventory\' key', function (done) {
386 | request(options2, function (error, response, body) {
387 | let widget = JSON.parse(body);
388 | widget.should.have.a.property('inventory', 21);
389 | done();
390 | });
391 | });
392 | });
393 |
394 | // delete one widget
395 | describe('DELETE /widgets/:product_id', function () {
396 | beforeEach(function () {
397 | removeWidgets({});
398 |
399 | saveWidget(new Widget({
400 | product_id: '3NDO87DF3C',
401 | name: 'TestWidget_3NDO87DF3C',
402 | color: 'Green',
403 | size: 'Small',
404 | price: '$71.95',
405 | inventory: 653
406 | }));
407 | });
408 |
409 | afterEach(function () {
410 | removeWidgets({});
411 | });
412 |
413 | let url = base_url + '/widgets/3NDO87DF3C';
414 |
415 | let options = {
416 | method: 'DELETE',
417 | url: url,
418 | headers: {
419 | accept: 'application/json'
420 | }
421 | };
422 |
423 | it('should respond with a status code of 204', function (done) {
424 | request(options, function (error, response, body) {
425 | response.statusCode.should.be.exactly(204);
426 | done();
427 | });
428 | });
429 |
430 | it('should respond with an empty response body', function (done) {
431 | request(options, function (error, response, body) {
432 | body.should.be.exactly('');
433 | done();
434 | });
435 | });
436 | });
437 | });
438 |
439 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var gulp = require('gulp');
4 | var eslint = require('gulp-eslint');
5 | var excludeGitignore = require('gulp-exclude-gitignore');
6 | var mocha = require('gulp-mocha');
7 | var istanbul = require('gulp-istanbul');
8 | var nsp = require('gulp-nsp');
9 | var plumber = require('gulp-plumber');
10 | var coveralls = require('gulp-coveralls');
11 |
12 | gulp.task('static', function () {
13 | return gulp.src('**/*.js')
14 | .pipe(excludeGitignore())
15 | .pipe(eslint())
16 | .pipe(eslint.format())
17 | .pipe(eslint.failAfterError());
18 | });
19 |
20 | gulp.task('nsp', function (cb) {
21 | nsp({package: path.resolve('package.json')}, cb);
22 | });
23 |
24 | gulp.task('pre-test', function () {
25 | return gulp.src('generators/**/*.js')
26 | .pipe(excludeGitignore())
27 | .pipe(istanbul({
28 | includeUntested: true
29 | }))
30 | .pipe(istanbul.hookRequire());
31 | });
32 |
33 | gulp.task('test', ['pre-test'], function (cb) {
34 | var mochaErr;
35 |
36 | gulp.src('test/**/*.js')
37 | .pipe(plumber())
38 | .pipe(mocha({reporter: 'spec'}))
39 | .on('error', function (err) {
40 | mochaErr = err;
41 | })
42 | .pipe(istanbul.writeReports())
43 | .on('end', function () {
44 | cb(mochaErr);
45 | });
46 | });
47 |
48 | gulp.task('watch', function () {
49 | gulp.watch(['generators/**/*.js', 'test/**'], ['test']);
50 | });
51 |
52 | gulp.task('coveralls', ['test'], function () {
53 | if (!process.env.CI) {
54 | return;
55 | }
56 |
57 | return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
58 | .pipe(coveralls());
59 | });
60 |
61 | gulp.task('prepublish', ['nsp']);
62 | gulp.task('default', ['static', 'test', 'coveralls']);
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-node-restify-mongodb",
3 | "version": "0.3.0",
4 | "description": "Scaffolds a basic RESTful microservice, based on Node, Restify, and MongoDB",
5 | "homepage": "https://github.com/garystafford/generator-node-restify-mongodb",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Gary A. Stafford",
9 | "email": "garystafford@rochester.rr.com",
10 | "url": "http://www.programmaticponderings.com"
11 | },
12 | "repository": "garystafford/generator-node-restify-mongodb",
13 | "scripts": {
14 | "prepublish": "gulp prepublish",
15 | "test": "gulp test"
16 | },
17 | "files": [
18 | "generators"
19 | ],
20 | "main": "generators/index.js",
21 | "keywords": [
22 | "api",
23 | "jasmine",
24 | "microservice",
25 | "mongodb",
26 | "node",
27 | "restful",
28 | "RESTful",
29 | "restify",
30 | "yeoman-generator"
31 | ],
32 | "dependencies": {
33 | "chalk": "^2.3.0",
34 | "mkdirp": "^0.5.1",
35 | "yeoman-generator": "^2.0.1",
36 | "yosay": "^2.0.1"
37 | },
38 | "devDependencies": {
39 | "eslint": "^4.10.0",
40 | "eslint-config-xo-space": "^0.17.0",
41 | "gulp": "^3.9.1",
42 | "gulp-coveralls": "^0.1.4",
43 | "gulp-eslint": "^4.0.0",
44 | "gulp-exclude-gitignore": "^1.2.0",
45 | "gulp-istanbul": "^1.1.2",
46 | "gulp-line-ending-corrector": "^1.0.1",
47 | "gulp-mocha": "^4.3.1",
48 | "gulp-nsp": "^2.4.2",
49 | "gulp-plumber": "^1.1.0",
50 | "yeoman-assert": "^3.1.0",
51 | "yeoman-test": "^1.7.0"
52 | },
53 | "eslintConfig": {
54 | "extends": "xo-space",
55 | "env": {
56 | "mocha": true
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garystafford/generator-node-restify-mongodb/89bf8449e834f5e94d3c97b50c00c567b09f52d5/preview.png
--------------------------------------------------------------------------------
/test/app.js:
--------------------------------------------------------------------------------
1 | /* global describe, beforeEach, it */
2 | 'use strict';
3 |
4 | var path = require('path');
5 | var assert = require('yeoman-assert');
6 | var helpers = require('yeoman-test');
7 |
8 | const expectedFiles = {
9 | app: [
10 | 'app.js',
11 | 'app/models/index.js',
12 | 'app/models/widget.js',
13 | 'app/routes/index.js',
14 | 'app/routes/widget.js',
15 | 'app/routes/utilities.js'
16 | ],
17 | config: [
18 | 'config/config.js'
19 | ],
20 | data: [
21 | 'data/widgets.json'
22 | ],
23 | spec: [
24 | 'spec/utils_spec.js',
25 | 'spec/widget_spec.js'
26 | ],
27 | postman: [
28 | 'postman/localhost-restify.postman_environment.json',
29 | 'postman/Widgets.postman_collection.json'
30 | ],
31 | grunt: [
32 | 'Gruntfile.js'
33 | ],
34 | projectfiles: [
35 | 'package.json',
36 | 'db-connection.js',
37 | 'log.js',
38 | '.gitignore',
39 | '.editorconfig'
40 | ]
41 | };
42 |
43 | describe('generator-node-restify-mongodb:app', function () {
44 | before(function () {
45 | return helpers.run(path.join(__dirname, '../generators/app'))
46 | .toPromise();
47 | });
48 |
49 | it('can be imported without blowing up', function () {
50 | var app = require('../generators/app');
51 | assert(app !== undefined);
52 | });
53 |
54 | it('creates expected default files', function () {
55 | assert.file(expectedFiles.app);
56 | assert.file(expectedFiles.config);
57 | assert.file(expectedFiles.data);
58 | assert.file(expectedFiles.spec);
59 | assert.file(expectedFiles.postman);
60 | assert.file(expectedFiles.grunt);
61 | assert.file(expectedFiles.projectfiles);
62 | });
63 | })
64 | ;
65 |
--------------------------------------------------------------------------------