diff --git a/app/index.js b/app/index.js index 94160457..b6f7cd4d 100644 --- a/app/index.js +++ b/app/index.js @@ -2,6 +2,7 @@ var generators = require('yeoman-generator'); var path = require('path'); +var crypto = require('crypto'); var _ = require('lodash'); module.exports = generators.Base.extend({ @@ -10,26 +11,137 @@ module.exports = generators.Base.extend({ this.props = { name: process.cwd().split(path.sep).pop() }; - this.dotfiles = ['editorconfig', 'gitignore', 'jshintrc', 'npmignore', 'travis.yml']; - this.files = ['package.json', 'src/index.js', 'test/index.test.js', 'LICENSE', 'README.md']; }, prompting: function () { var done = this.async(); - var prompts = [{ - name: 'name', - message: 'Project name', - when: !this.pkg.name, - default: this.props.name - }, { - name: 'repository', - message: 'The GitHub repository URL (e.g. feathersjs/feathers-myplugin)', - default: 'feathersjs/' + this.props.name - }, { - name: 'description', - message: 'Description', - when: !this.pkg.description - }]; + var prompts = [ + { + name: 'name', + message: 'Project name', + when: !this.pkg.name, + default: this.props.name + }, + { + name: 'description', + message: 'Description', + when: !this.pkg.description + }, + { + type: 'checkbox', + name: 'providers', + message: 'What type of API are you making?', + choices: [ + { + name: 'REST', + value: 'rest', + checked: true + }, + { + name: 'Realtime via Socket.io', + value: 'socket.io', + checked: true + }, + { + name: 'Realtime via Primus', + value: 'primus', + } + ] + }, + { + type: 'list', + name: 'cors', + message: 'CORS configuration', + choices: [ + { + name: 'Enabled for all domains', + value: 'enabled', + checked: true + }, + { + name: 'Enabled for whitelisted domains', + value: 'whitelisted' + }, + { + name: 'Disabled', + value: false + } + ] + }, + { + type: 'input', + name: 'corsWhitelist', + message: 'Comma-separated domains for CORS whitelist. Include http(s)', + when: function(props){ + return props.cors === 'whitelisted'; + } + }, + { + type: 'list', + name: 'database', + message: 'What database do you primarily want to use?', + choices: [ + { + name: 'I will choose my own', + checked: true + }, + { + name: 'Memory', + value: 'memory' + }, + { + name: 'MongoDB', + value: 'mongodb' + }, + { + name: 'MySQL', + value: 'mysql' + }, + { + name: 'MariaDB', + value: 'mariadb' + }, + { + name: 'NeDB', + value: 'nedb' + }, + { + name: 'PostgreSQL', + value: 'postgres' + }, + { + name: 'SQLite', + value: 'sqlite' + }, + { + name: 'SQL Server', + value: 'mssql' + } + ] + }, + { + type: 'checkbox', + name: 'authentication', + message: 'What authentication methods would you like to support?', + choices: [ + { + name: 'local', + checked: true + } + // name: 'basic' + // }, { + + // }, { + // name: 'google' + // }, { + // name: 'facebook' + // }, { + // name: 'twitter' + // }, { + // name: 'github' + ] + } + ]; this.prompt(prompts, function (props) { this.props = _.extend(this.props, props); @@ -39,27 +151,177 @@ module.exports = generators.Base.extend({ }, writing: function () { - this.dotfiles.forEach(function(file) { - this.fs.copyTpl( - this.templatePath(file), - this.destinationPath('.' + file), - this.props - ); - }.bind(this)); + this.props.secret = crypto.randomBytes(64).toString('base64'); + this.props.corsWhitelist = this.props.corsWhitelist && this.props.corsWhitelist.split(','); + var dependencies = [ + 'feathers@2.0.0-pre.2', + 'feathers-hooks@1.0.0-pre.1', + 'feathers-configuration', + 'serve-favicon', + 'compression', + 'winston', + 'babel-core', + 'babel-preset-es2015' + ]; - this.files.forEach(function(file) { - this.fs.copyTpl( - this.templatePath(file), - this.destinationPath(file), - this.props - ); - }.bind(this)); + if (this.props.providers.indexOf('rest') !== -1) { + dependencies.push('body-parser'); + dependencies.push('feathers-rest'); + } + + if (this.props.providers.indexOf('socket.io') !== -1) { + dependencies.push('feathers-socketio'); + } + + if (this.props.providers.indexOf('primus') !== -1) { + dependencies.push('feathers-primus'); + } + + if (this.props.authentication.length) { + dependencies.push('feathers-authentication'); + } + + if (this.props.cors) { + dependencies.push('cors'); + } + + switch(this.props.database) { + case 'memory': + dependencies.push('feathers-memory'); + this.fs.copyTpl( + this.templatePath('services/memory-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'mongodb': + dependencies.push('mongoose'); + dependencies.push('feathers-mongoose'); + this.fs.copyTpl( + this.templatePath('models/mongoose-user.js'), + this.destinationPath('server/models', 'user.js'), + this.props + ); + this.fs.copyTpl( + this.templatePath('services/mongoose-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'mysql': + case 'mariadb': + dependencies.push('mysql'); + dependencies.push('sequelize'); + dependencies.push('feathers-sequelize'); + this.fs.copyTpl( + this.templatePath('models/sequelize-user.js'), + this.destinationPath('server/models', 'user.js'), + this.props + ); + this.fs.copyTpl( + this.templatePath('services/sequelize-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'nedb': + dependencies.push('nedb'); + dependencies.push('feathers-nedb'); + this.fs.copyTpl( + this.templatePath('services/nedb-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'postgres': + dependencies.push('pg'); + dependencies.push('pg-hstore'); + dependencies.push('sequelize'); + dependencies.push('feathers-sequelize'); + this.fs.copyTpl( + this.templatePath('models/sequelize-user.js'), + this.destinationPath('server/models', 'user.js'), + this.props + ); + this.fs.copyTpl( + this.templatePath('services/sequelize-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'sqlite': + dependencies.push('sqlite3'); + dependencies.push('sequelize'); + dependencies.push('feathers-sequelize'); + this.fs.copyTpl( + this.templatePath('models/sequelize-user.js'), + this.destinationPath('server/models', 'user.js'), + this.props + ); + this.fs.copyTpl( + this.templatePath('services/sequelize-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + case 'mssql': + dependencies.push('tedious'); + dependencies.push('sequelize'); + dependencies.push('feathers-sequelize'); + this.fs.copyTpl( + this.templatePath('models/sequelize-user.js'), + this.destinationPath('server/models', 'user.js'), + this.props + ); + this.fs.copyTpl( + this.templatePath('services/sequelize-user.js'), + this.destinationPath('server/services', 'user.js'), + this.props + ); + break; + } + + this.fs.copy(this.templatePath('static'), this.destinationPath()); + this.fs.copy(this.templatePath('static/.*'), this.destinationPath()); + + this.fs.copyTpl( + this.templatePath('app.js'), + this.destinationPath('server', 'app.js'), + this.props + ); + + this.fs.copyTpl( + this.templatePath('middleware.js'), + this.destinationPath('server/middleware', 'index.js'), + this.props + ); + + this.fs.copyTpl( + this.templatePath('config.default.json'), + this.destinationPath('config', 'default.json'), + this.props + ); + + this.fs.copyTpl( + this.templatePath('config.production.json'), + this.destinationPath('config', 'production.json'), + this.props + ); + + this.fs.copyTpl( + this.templatePath('package.json'), + this.destinationPath('package.json'), + this.props + ); + + this.log(this.props); + + this.npmInstall(dependencies, { save: true }); this.npmInstall([ - 'babel', 'jshint', 'mocha', - 'feathers' + 'request' ], { saveDev: true}); } }); diff --git a/app/templates/app.js b/app/templates/app.js new file mode 100644 index 00000000..0b0b551f --- /dev/null +++ b/app/templates/app.js @@ -0,0 +1,29 @@ +import { join } from 'path'; +import feathers from 'feathers'; +import configuration from 'feathers-configuration'; +import hooks from 'feathers-hooks';<% if (providers.indexOf('rest') !== -1) { %> +import rest from 'feathers-rest'; +import bodyParser from 'body-parser'; +<% } %><% if (providers.indexOf('socket.io') !== -1) { %>import socketio from 'feathers-socketio';<% } %><% if (providers.indexOf('primus') !== -1) { %>import primus from 'feathers-primus';<% } %> +<% if (authentication.length) { %>import feathersAuth from 'feathers-authentication';<% } %> +import middleware from './middleware'; +import services from './services'; +import myHooks from './hooks'; + +let app = feathers(); + +app.configure(configuration(join(__dirname, '..'))) + .configure(hooks())<% if(providers.indexOf('rest') !== -1) { %> + .use(bodyParser.json()) + .use(bodyParser.urlencoded({ extended: true })) + .configure(rest())<% } %><% if (providers.indexOf('socket.io') !== -1) { %> + .configure(socketio())<% } %><% if(providers.indexOf('primus') !== -1) { %> + .configure(primus({ + transformer: 'sockjs' + }))<% } %><% if (authentication.length) { %> + .configure(feathersAuth(app.get('auth').local))<% } %> + .configure(services) + .configure(myHooks) + .configure(middleware); + +export default app; diff --git a/app/templates/authentication.js b/app/templates/authentication.js new file mode 100644 index 00000000..a6db8131 --- /dev/null +++ b/app/templates/authentication.js @@ -0,0 +1,3 @@ +var feathersAuth = require('feathers-authentication').default; + +export default feathersAuth(); diff --git a/app/templates/config.default.json b/app/templates/config.default.json new file mode 100644 index 00000000..d845a039 --- /dev/null +++ b/app/templates/config.default.json @@ -0,0 +1,23 @@ +{ + "host": "localhost", + "port": 3030,<% if(database === 'mongodb'){ %> + "mongodb": "mongodb://localhost:27017/feathers",<% } else if(database === 'mariadb'){ %> + "mariadb": "mariadb://root:@localhost:3306/feathers",<% } else if(database === 'mssql'){ %> + "mssql": "mssql://root:@localhost:1433/feathers",<% } else if(database === 'mysql'){ %> + "mysql": "mysql://root:@localhost:3306/feathers",<% } else if(database === 'postgres'){ %> + "postgres": "postgres://postgres:@localhost:5432/feathers",<% } else if(database === 'nedb'){ %> + "nedb": "../data/",<% } else if(database === 'sqlite'){ %> + "sqlite": "../data/feathers.sqlite",<% } %> + "public": "../public/"<% if(authentication.length){ %>, + "auth": {<% if(authentication.indexOf('local' > -1)){ %> + "local": { + "secret": "<%= secret %>", + "loginEndpoint": "/login", + "userEndpoint": "/users", + "usernameField": "email" + }<% } %> + }<% } %><% if (cors === 'whitelisted') { %>, + "corsWhitelist": [<% corsWhitelist.forEach(function(domain, index, list){ %> + "<%= domain.trim() %>"<% if(index + 1 !== list.length){ %>,<% } %><% }); %> + ]<% } %> +} diff --git a/app/templates/config.production.json b/app/templates/config.production.json new file mode 100644 index 00000000..083df44c --- /dev/null +++ b/app/templates/config.production.json @@ -0,0 +1,23 @@ +{ + "host": "<%=name%>-app.feathersjs.com", + "port": 80,<% if(database === 'mongodb'){ %> + "mongodb": "DATABASE_URL",<% } else if(database === 'mariadb'){ %> + "mariadb": "DATABASE_URL",<% } else if(database === 'mssql'){ %> + "mssql": "DATABASE_URL",<% } else if(database === 'mysql'){ %> + "mysql": "DATABASE_URL",<% } else if(database === 'postgres'){ %> + "postgres": "DATABASE_URL",<% } else if(database === 'nedb'){ %> + "nedb": "NEDB_BASE_PATH",<% } else if(database === 'sqlite'){ %> + "sqlite": "SQLITE_DB",<% } %> + "public": "../public/"<% if(authentication.length){ %>, + "auth": {<% if(authentication.indexOf('local' > -1)){ %> + "local": { + "secret": "FEATHERS_AUTH_SECRET", + "loginEndpoint": "/login", + "userEndpoint": "/users", + "usernameField": "email" + }<% } %> + }<% } %><% if (cors === 'whitelisted') { %>, + "corsWhitelist": [<% corsWhitelist.forEach(function(domain, index, list){ %> + "<%= domain.trim() %>"<% if(index + 1 !== list.length){ %>,<% } %><% }); %> + ]<% } %> +} diff --git a/app/templates/middleware.js b/app/templates/middleware.js new file mode 100644 index 00000000..28a2b081 --- /dev/null +++ b/app/templates/middleware.js @@ -0,0 +1,29 @@ +<% if (cors) { %>import cors from 'cors';<% } %> +import { join } from 'path'; +import { static as serveStatic } from 'feathers'; +import favicon from 'serve-favicon'; +import compress from 'compression'; +import missing from './not-found-handler'; +import error from './error-handler'; +import logger from './logger'; + +export default function() { + const app = this; + <% if (cors === 'whitelisted') { %> + let whitelist = app.get('corsWhitelist'); + let corsOptions = { + origin: function(origin, callback){ + var originIsWhitelisted = whitelist.indexOf(origin) !== -1; + callback(null, originIsWhitelisted); + } + };<% } %> + + app.use(compress())<% if (cors) { %> + .options('*', cors(<% if (cors === 'whitelisted') { %>corsOptions<% } %>)) + .use(cors(<% if (cors === 'whitelisted') { %>corsOptions<% } %>))<% } %> + .use(favicon( join(app.get('public'), 'favicon.ico') )) + .use('/', serveStatic(app.get('public'))) + .use(missing()) + .use(logger(app)) + .use(error(app)); +} diff --git a/app/templates/models/mongoose-user.js b/app/templates/models/mongoose-user.js new file mode 100644 index 00000000..98478586 --- /dev/null +++ b/app/templates/models/mongoose-user.js @@ -0,0 +1,13 @@ +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +let UserSchema = new Schema({ + email: {type: String, required: true, index: true}, + password: {type: String, required: true}, + createdAt: {type: Date, 'default': Date.now}, + updatedAt: {type: Date, 'default': Date.now} +}); + +let UserModel = mongoose.model('User', UserSchema); + +export default UserModel; \ No newline at end of file diff --git a/app/templates/models/sequelize-user.js b/app/templates/models/sequelize-user.js new file mode 100644 index 00000000..83ad8815 --- /dev/null +++ b/app/templates/models/sequelize-user.js @@ -0,0 +1,24 @@ +import Sequelize from 'sequelize'; + +export default function(sequelize) { + let User = sequelize.define('user', { + email: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: { msg: 'Must provide a valid email' } + } + }, + password: { + type: Sequelize.STRING, + allowNull: false + } + }, { + freezeTableName: true + }); + + User.sync({ force: true }); + + return User; +} \ No newline at end of file diff --git a/app/templates/npmignore b/app/templates/npmignore deleted file mode 100644 index 3b851a1f..00000000 --- a/app/templates/npmignore +++ /dev/null @@ -1,4 +0,0 @@ -.idea/ -src/ -test/ -!lib/ \ No newline at end of file diff --git a/app/templates/package.json b/app/templates/package.json index 03ca0481..4c75a80d 100644 --- a/app/templates/package.json +++ b/app/templates/package.json @@ -2,48 +2,23 @@ "name": "<%= name %>", "description": "<%= description %>", "version": "0.0.0", - "homepage": "https://github.com/<%= repository %>", + "homepage": "", "main": "lib/", "keywords": [ - "feathers", - "feathers-plugin" + "feathers" ], - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/<%= repository %>/blob/master/LICENSE" - } - ], - "repository": { - "type": "git", - "url": "git://github.com/<%= repository %>.git" - }, - "author": { - "name": "Feathers contributors", - "email": "hello@feathersjs.com", - "url": "https://feathersjs.com" - }, + "license": "MIT", + "repository": {}, + "author": {}, "contributors": [], - "bugs": { - "url": "https://github.com/<%= repository %>/issues" - }, + "bugs": {}, "engines": { "node": ">= 0.12.0" }, "scripts": { - "prepublish": "npm run compile", - "publish": "git push origin && git push origin --tags", - "release:patch": "npm version patch && npm publish", - "release:minor": "npm version minor && npm publish", - "release:major": "npm version major && npm publish", - "compile": "rm -rf lib/ && babel -d lib/ src/", + "start": "node server/", "jshint": "jshint src/. test/. --config", - "mocha": "mocha test/ --compilers js:babel/register", + "mocha": "mocha test/ --compilers js:babel-core/register", "test": "npm run jshint && npm run mocha" - }, - "directories": { - "lib": "lib" - }, - "dependencies": {}, - "devDependencies": {} + } } diff --git a/app/templates/services/memory-user.js b/app/templates/services/memory-user.js new file mode 100644 index 00000000..f8388681 --- /dev/null +++ b/app/templates/services/memory-user.js @@ -0,0 +1,15 @@ +import hooks from '../hooks'; +import service from 'feathers-memory'; + +export default function(){ + const app = this; + + let options = { + paginate: { + default: 5, + max: 25 + } + }; + + app.use('/v1/users', service(options)); +} \ No newline at end of file diff --git a/app/templates/services/mongoose-user.js b/app/templates/services/mongoose-user.js new file mode 100644 index 00000000..42b7df7b --- /dev/null +++ b/app/templates/services/mongoose-user.js @@ -0,0 +1,38 @@ +import hooks from '../hooks'; +import mongoose from 'mongoose'; +import service from 'feathers-mongoose'; +import User from '../models/user'; + +mongoose.Promise = global.Promise; + +export default function(){ + const app = this; + + mongoose.connect(app.get('mongodb')); + + let options = { + Model: User, + paginate: { + default: 5, + max: 25 + } + }; + + app.use('/v1/users', service(options)); + + // const service = this.service('v1/users'); + + /* * * Before hooks * * */ + // service.before({ + // all: [hooks.requireAuthForPrivate()], + // before: [hooks.setUserID()] + // }); + + // /* * * After hooks * * */ + // service.after({ + // all: [hooks.removeSomeField()] + // }); + + // /* * * Set up event filters * * */ + // service.created = service.updated = service.patched = service.removed = events.requireAuthForPrivate; +} \ No newline at end of file diff --git a/app/templates/services/nedb-user.js b/app/templates/services/nedb-user.js new file mode 100644 index 00000000..872a95f2 --- /dev/null +++ b/app/templates/services/nedb-user.js @@ -0,0 +1,24 @@ +import { join } from 'path'; +import hooks from '../hooks'; +import NeDB from 'nedb'; +import service from 'feathers-nedb'; + + +export default function(){ + const app = this; + + const db = new NeDB({ + filename: join(app.get('nedb'), 'users.db'), + autoload: true + }); + + let options = { + paginate: { + Model: db, + default: 5, + max: 25 + } + }; + + app.use('/v1/users', service(options)); +} \ No newline at end of file diff --git a/app/templates/services/sequelize-user.js b/app/templates/services/sequelize-user.js new file mode 100644 index 00000000..5599cf2c --- /dev/null +++ b/app/templates/services/sequelize-user.js @@ -0,0 +1,54 @@ +import hooks from '../hooks'; +import Sequelize from 'sequelize'; +import service from 'feathers-sequelize'; +import User from '../models/user'; + +export default function(){ + const app = this; + <% if (database === 'sqlite') { %> + const sequelize = new Sequelize('feathers', null, null, { + dialect: 'sqlite', + storage: app.get('sqlite'), + logging: false + });<% } else if (database === 'mssql') { %> + const sequelize = new Sequelize('feathers', { + dialect: '<%= database %>', + host: 'localhost', + port: 1433, + logging: false, + dialectOptions: { + instanceName: 'feathers' + } + });<% } else if (database) { %> + const sequelize = new Sequelize(app.get('<%= database %>'), { + dialect: '<%= database %>', + logging: false + }); + <% } %> + + let options = { + Model: User(sequelize), + paginate: { + default: 5, + max: 25 + } + }; + + app.use('/v1/users', service(options)); + + // const service = this.service('v1/users'); + + /* * * Before hooks * * */ + // service.before({ + // all: [hooks.requireAuthForPrivate()], + // before: [hooks.setUserID()] + // }); + + // /* * * After hooks * * */ + // service.after({ + // all: [hooks.removeSomeField()] + // }); + + // /* * * Set up event filters * * */ + // service.created = service.updated = service.patched = service.removed = events.requireAuthForPrivate; +} \ No newline at end of file diff --git a/app/templates/src/hooks/index.js b/app/templates/src/hooks/index.js deleted file mode 100644 index 10324356..00000000 --- a/app/templates/src/hooks/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function() { - return function() { - // TODO - }; -} diff --git a/app/templates/src/index.js b/app/templates/src/index.js deleted file mode 100644 index 2098dc24..00000000 --- a/app/templates/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import feathers from 'feathers'; - -const app = feathers() - .configure(services()) - .configura(hooks()); diff --git a/app/templates/src/services/index.js b/app/templates/src/services/index.js deleted file mode 100644 index 7a884fdd..00000000 --- a/app/templates/src/services/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import todos from './todos'; - -export default function() { - return function() { - this.use('/todos', todos); - }; -} diff --git a/app/templates/static/.babelrc b/app/templates/static/.babelrc new file mode 100644 index 00000000..59cc8579 --- /dev/null +++ b/app/templates/static/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [ "es2015" ] +} diff --git a/app/templates/editorconfig b/app/templates/static/.editorconfig similarity index 100% rename from app/templates/editorconfig rename to app/templates/static/.editorconfig diff --git a/app/templates/gitignore b/app/templates/static/.gitignore similarity index 100% rename from app/templates/gitignore rename to app/templates/static/.gitignore diff --git a/app/templates/jshintrc b/app/templates/static/.jshintrc similarity index 96% rename from app/templates/jshintrc rename to app/templates/static/.jshintrc index 741f1e44..c39bb0cb 100644 --- a/app/templates/jshintrc +++ b/app/templates/static/.jshintrc @@ -18,7 +18,6 @@ "trailing": true, "smarttabs": true, "white": false, - "node": true, "globals": { "it": true, "describe": true, diff --git a/app/templates/LICENSE b/app/templates/static/LICENSE similarity index 100% rename from app/templates/LICENSE rename to app/templates/static/LICENSE diff --git a/app/templates/README.md b/app/templates/static/README.md similarity index 100% rename from app/templates/README.md rename to app/templates/static/README.md diff --git a/app/templates/static/public/404.html b/app/templates/static/public/404.html new file mode 100644 index 00000000..e72b7669 --- /dev/null +++ b/app/templates/static/public/404.html @@ -0,0 +1,8 @@ + + + Page Not Found + + + 404: Page Not Found + + \ No newline at end of file diff --git a/app/templates/static/public/500.html b/app/templates/static/public/500.html new file mode 100644 index 00000000..8387d06b --- /dev/null +++ b/app/templates/static/public/500.html @@ -0,0 +1,8 @@ + + + Internal Server Error + + + 500: Internal Server Error + + \ No newline at end of file diff --git a/app/templates/static/public/favicon.ico b/app/templates/static/public/favicon.ico new file mode 100644 index 00000000..7ed25a60 Binary files /dev/null and b/app/templates/static/public/favicon.ico differ diff --git a/app/templates/static/public/index.html b/app/templates/static/public/index.html new file mode 100644 index 00000000..d8009256 --- /dev/null +++ b/app/templates/static/public/index.html @@ -0,0 +1,8 @@ + + + Welcome to Feathers + + + Welcome to Feathers + + \ No newline at end of file diff --git a/app/templates/static/server/hooks/index.js b/app/templates/static/server/hooks/index.js new file mode 100644 index 00000000..19f19f11 --- /dev/null +++ b/app/templates/static/server/hooks/index.js @@ -0,0 +1,4 @@ +// jshint unused:false +export default function() { + const app = this; +} diff --git a/app/templates/static/server/index.js b/app/templates/static/server/index.js new file mode 100644 index 00000000..89feb256 --- /dev/null +++ b/app/templates/static/server/index.js @@ -0,0 +1,9 @@ +require('babel-core/register'); + +var app = require('./app').default; +var port = app.get('port'); +var server = app.listen(port); + +server.on('listening', function() { + console.log(`Feathers application started on ${app.get('host')}:${port}`); +}); diff --git a/app/templates/static/server/middleware/error-handler.js b/app/templates/static/server/middleware/error-handler.js new file mode 100644 index 00000000..0895ae2d --- /dev/null +++ b/app/templates/static/server/middleware/error-handler.js @@ -0,0 +1,45 @@ +// jshint unused:false +import path from 'path'; +import errors from 'feathers-errors'; + +export default function(app) { + return function(error, req, res, next) { + if (typeof error === 'string') { + error = new errors.GeneralError(error); + } + else if ( !(error instanceof errors.FeathersError) ) { + let oldError = error; + error = new errors.GeneralError(oldError.message, {errors: oldError.errors}); + + if (oldError.stack) { + error.stack = oldError.stack; + } + } + + const code = parseInt(error.code, 10) !== NaN ? parseInt(error.code, 10) : 500; + + // Don't show stack trace if it is a 404 error + if (code === 404) { + error.stack = null; + } + + res.status(code); + + res.format({ + 'text/html': function() { + const file = code === 404 ? '404.html' : '500.html'; + res.sendFile(path.join(app.get('public'), file)); + }, + + 'application/json': function () { + let output = Object.assign({}, error.toJSON()); + + if (app.settings.env === 'production') { + delete output.stack; + } + + res.json(output); + } + }); + }; +} diff --git a/app/templates/static/server/middleware/logger.js b/app/templates/static/server/middleware/logger.js new file mode 100644 index 00000000..a7526884 --- /dev/null +++ b/app/templates/static/server/middleware/logger.js @@ -0,0 +1,22 @@ +import winston from 'winston'; + +export default function(app) { + // Add a logger to our app object for convenience + app.logger = winston; + + return function(error, req, res, next) { + if (error) { + const message = `${error.code ? `(${error.code}) ` : '' }Route: ${req.url} - ${error.message}`; + + if (error.code === 404) { + winston.info(message); + } + else { + winston.error(message); + winston.info(error.stack); + } + } + + next(error); + }; +}; \ No newline at end of file diff --git a/app/templates/static/server/middleware/not-found-handler.js b/app/templates/static/server/middleware/not-found-handler.js new file mode 100644 index 00000000..cef71f99 --- /dev/null +++ b/app/templates/static/server/middleware/not-found-handler.js @@ -0,0 +1,8 @@ +import errors from 'feathers-errors'; + +export default function() { + return function(req, res, next) { + next(new errors.NotFound('Page not found')); + } +}; + diff --git a/app/templates/static/server/services/index.js b/app/templates/static/server/services/index.js new file mode 100644 index 00000000..4299e34a --- /dev/null +++ b/app/templates/static/server/services/index.js @@ -0,0 +1,8 @@ +// jshint unused:false +import userService from './user'; + +export default function() { + const app = this; + + app.configure(userService); +} diff --git a/app/templates/static/test/app.test.js b/app/templates/static/test/app.test.js new file mode 100644 index 00000000..047f4053 --- /dev/null +++ b/app/templates/static/test/app.test.js @@ -0,0 +1,45 @@ +import assert from 'assert'; +import request from 'request'; +import app from '../server/app'; + +describe('Feathers application tests', () => { + before(function(done) { + this.server = app.listen(3030); + this.server.once('listening', () => done()); + }); + + after(function(done) { + this.server.close(done); + }); + + it('starts and shows the index page', done => { + request('http://localhost:3030', (err, res, body) => { + assert.ok(body.indexOf('') !== -1); + done(err); + }); + }); + + describe('404', () => { + it('shows a 404 HTML page', done => { + request('http://localhost:3030/path/to/nowhere', (err, res, body) => { + assert.equal(res.statusCode, 404); + assert.ok(body.indexOf('') !== -1); + done(err); + }); + }); + + it('shows a 404 JSON error with stack trace', done => { + request({ + url: 'http://localhost:3030/path/to/nowhere', + json: true + }, (err, res, body) => { + assert.equal(res.statusCode, 404); + assert.equal(body.code, 404); + assert.equal(body.message, 'Page not found'); + assert.equal(body.name, 'NotFound'); + assert.equal(typeof body.stack, 'string'); + done(err); + }); + }); + }); +}); diff --git a/app/templates/test/index.test.js b/app/templates/test/index.test.js deleted file mode 100644 index 5ad17b58..00000000 --- a/app/templates/test/index.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import assert from 'assert'; -import plugin from '../src'; - -describe('<%= name %>', () => { - it('basic functionality', done => { - assert.equal(typeof plugin, 'function', 'It worked'); - done(); - }); -}); diff --git a/app/templates/travis.yml b/app/templates/travis.yml deleted file mode 100644 index b7b1e281..00000000 --- a/app/templates/travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: node_js -node_js: - - 'node' - - 'iojs' diff --git a/hook/templates/hook.js b/hook/templates/hook.js new file mode 100644 index 00000000..8e824dc9 --- /dev/null +++ b/hook/templates/hook.js @@ -0,0 +1,4 @@ +export default function(hook, next) { + hook.data.ran = true; + next(); +} diff --git a/hook/templates/hook.test.js b/hook/templates/hook.test.js new file mode 100644 index 00000000..e69de29b diff --git a/package.json b/package.json index b1ef217d..a93da727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generator-feathers", - "description": "A Yeoman generator for a FEathers application", + "description": "A Yeoman generator for a Feathers application", "version": "0.0.0", "homepage": "https://github.com/feathersjs/generator-feathers", "main": "lib/", @@ -45,7 +45,10 @@ "directories": { "lib": "lib" }, - "dependencies": {}, + "dependencies": { + "lodash": "^3.10.1", + "yeoman-generator": "^0.21.1" + }, "devDependencies": { "babel": "^5.8.29", "feathers": "^1.1.1", diff --git a/app/templates/src/services/todos.js b/service/templates/service.js similarity index 100% rename from app/templates/src/services/todos.js rename to service/templates/service.js diff --git a/service/templates/service.test.js b/service/templates/service.test.js new file mode 100644 index 00000000..e69de29b