├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── index.js └── templates │ ├── Gruntfile.js │ ├── README.md.erb │ ├── _app.js │ ├── _bower.json │ ├── _generator.json │ ├── _package.json │ ├── bowerrc │ ├── editorconfig │ ├── gitignore │ ├── jshintrc │ ├── models │ └── _index.js │ └── public │ ├── _index.html │ ├── css │ └── app.css │ ├── js │ ├── _app.js │ └── home │ │ └── _home-controller.js │ └── views │ └── home │ └── _home.html ├── entity ├── index.js └── templates │ ├── _generator.json │ ├── models │ └── _entity.js │ ├── public │ ├── js │ │ └── entity │ │ │ ├── _entity-controller.js │ │ │ ├── _entity-router.js │ │ │ └── _entity-service.js │ └── views │ │ └── entity │ │ └── _entities.html │ └── routes │ └── _entities.js ├── package.json └── test ├── test-creation.js └── test-load.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 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 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.8' 5 | before_install: 6 | - currentfolder=${PWD##*/} 7 | - if [ "$currentfolder" != 'generator-angular-express-sequelize' ]; then cd .. && eval "mv $currentfolder generator-angular-express-sequelize" && cd generator-angular-express-sequelize; fi 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Robert Yokota 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Angular-Express-Sequelize generator 2 | 3 | A [Yeoman](http://yeoman.io) generator for [AngularJS](http://angularjs.org) and [Express](http://expressjs.com) with [Sequelize](http://sequelizejs.com). 4 | 5 | Express is a Javascript-based micro-framework. For AngularJS integration with other micro-frameworks, see https://github.com/rayokota/MicroFrameworkRosettaStone. 6 | 7 | ## Installation 8 | 9 | Install [Git](http://git-scm.com) and [node.js](http://nodejs.org). The development mode also requires [SQLite](http://www.sqlite.org). 10 | 11 | Install Yeoman: 12 | 13 | npm install -g yo 14 | 15 | Install the Angular-Express-Sequelize generator: 16 | 17 | npm install -g generator-angular-express-sequelize 18 | 19 | The above prerequisites can be installed to a VM using the [Angular-Express-Sequelize provisioner](https://github.com/rayokota/provision-angular-express-sequelize). 20 | 21 | ## Creating an Express service 22 | 23 | In a new directory, generate the service: 24 | 25 | yo angular-express-sequelize 26 | 27 | Run the service: 28 | 29 | node app.js 30 | 31 | Your service will run at [http://localhost:3000](http://localhost:3000). 32 | 33 | 34 | ## Creating a persistent entity 35 | 36 | Generate the entity: 37 | 38 | yo angular-express-sequelize:entity [myentity] 39 | 40 | You will be asked to specify attributes for the entity, where each attribute has the following: 41 | 42 | - a name 43 | - a type (String, Integer, Float, Boolean, Date, Enum) 44 | - for a String attribute, an optional minimum and maximum length 45 | - for a numeric attribute, an optional minimum and maximum value 46 | - for a Date attribute, an optional constraint to either past values or future values 47 | - for an Enum attribute, a list of enumerated values 48 | - whether the attribute is required 49 | 50 | Files that are regenerated will appear as conflicts. Allow the generator to overwrite these files as long as no custom changes have been made. 51 | 52 | Run the service: 53 | 54 | node app.js 55 | 56 | A client-side AngularJS application will now be available by running 57 | 58 | grunt server 59 | 60 | The Grunt server will run at [http://localhost:9000](http://localhost:9000). It will proxy REST requests to the Express service running at [http://localhost:3000](http://localhost:3000). 61 | 62 | At this point you should be able to navigate to a page to manage your persistent entities. 63 | 64 | The Grunt server supports hot reloading of client-side HTML/CSS/Javascript file changes. 65 | 66 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'), 3 | path = require('path'), 4 | yeoman = require('yeoman-generator'), 5 | _ = require('lodash'), 6 | _s = require('underscore.string'), 7 | pluralize = require('pluralize'), 8 | asciify = require('asciify'); 9 | 10 | var AngularExpressSequelizeGenerator = module.exports = function AngularExpressSequelizeGenerator(args, options, config) { 11 | yeoman.generators.Base.apply(this, arguments); 12 | 13 | this.on('end', function () { 14 | this.installDependencies({ skipInstall: options['skip-install'] }); 15 | }); 16 | 17 | this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json'))); 18 | }; 19 | 20 | util.inherits(AngularExpressSequelizeGenerator, yeoman.generators.Base); 21 | 22 | AngularExpressSequelizeGenerator.prototype.askFor = function askFor() { 23 | 24 | var cb = this.async(); 25 | 26 | console.log('\n' + 27 | '+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' + 28 | '|a|n|g|u|l|a|r| |e|x|p|r|e|s|s| |s|e|q|u|e|l|i|z|e| |g|e|n|e|r|a|t|o|r|\n' + 29 | '+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' + 30 | '\n'); 31 | 32 | var prompts = [{ 33 | type: 'input', 34 | name: 'baseName', 35 | message: 'What is the name of your application?', 36 | default: 'myapp' 37 | }]; 38 | 39 | this.prompt(prompts, function (props) { 40 | this.baseName = props.baseName; 41 | 42 | cb(); 43 | }.bind(this)); 44 | }; 45 | 46 | AngularExpressSequelizeGenerator.prototype.app = function app() { 47 | 48 | this.entities = []; 49 | this.resources = []; 50 | this.generatorConfig = { 51 | "baseName": this.baseName, 52 | "entities": this.entities, 53 | "resources": this.resources 54 | }; 55 | this.generatorConfigStr = JSON.stringify(this.generatorConfig, null, '\t'); 56 | 57 | this.template('_generator.json', 'generator.json'); 58 | this.template('_package.json', 'package.json'); 59 | this.template('_bower.json', 'bower.json'); 60 | this.template('bowerrc', '.bowerrc'); 61 | this.template('Gruntfile.js', 'Gruntfile.js'); 62 | this.copy('gitignore', '.gitignore'); 63 | 64 | var modelsDir = 'models/' 65 | var publicDir = 'public/' 66 | var routesDir = 'routes/' 67 | var viewsDir = 'views/' 68 | this.mkdir(modelsDir); 69 | this.mkdir(publicDir); 70 | this.mkdir(routesDir); 71 | this.mkdir(viewsDir); 72 | 73 | this.template('_app.js', 'app.js'); 74 | this.template('models/_index.js', modelsDir + 'index.js'); 75 | 76 | var publicCssDir = publicDir + 'css/'; 77 | var publicJsDir = publicDir + 'js/'; 78 | var publicViewDir = publicDir + 'views/'; 79 | this.mkdir(publicCssDir); 80 | this.mkdir(publicJsDir); 81 | this.mkdir(publicViewDir); 82 | this.template('public/_index.html', publicDir + 'index.html'); 83 | this.copy('public/css/app.css', publicCssDir + 'app.css'); 84 | this.template('public/js/_app.js', publicJsDir + 'app.js'); 85 | this.template('public/js/home/_home-controller.js', publicJsDir + 'home/home-controller.js'); 86 | this.template('public/views/home/_home.html', publicViewDir + 'home/home.html'); 87 | }; 88 | 89 | AngularExpressSequelizeGenerator.prototype.projectfiles = function projectfiles() { 90 | this.copy('editorconfig', '.editorconfig'); 91 | this.copy('jshintrc', '.jshintrc'); 92 | }; 93 | -------------------------------------------------------------------------------- /app/templates/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest; 4 | 5 | module.exports = function (grunt) { 6 | require('load-grunt-tasks')(grunt); 7 | require('time-grunt')(grunt); 8 | 9 | grunt.initConfig({ 10 | yeoman: { 11 | // configurable paths 12 | app: require('./bower.json').appPath || 'public', 13 | dist: 'public' 14 | }, 15 | sync: { 16 | dist: { 17 | files: [{ 18 | cwd: '<%%= yeoman.app %>', 19 | dest: '<%%= yeoman.dist %>', 20 | src: '**' 21 | }] 22 | } 23 | }, 24 | watch: { 25 | options: { 26 | livereload: 35729 27 | }, 28 | src: { 29 | files: [ 30 | '<%%= yeoman.app %>/*.html', 31 | '<%%= yeoman.app %>/css/**/*', 32 | '<%%= yeoman.app %>/js/**/*', 33 | '<%%= yeoman.app %>/views/**/*' 34 | ], 35 | //tasks: ['sync:dist'] 36 | } 37 | }, 38 | connect: { 39 | proxies: [ 40 | { 41 | context: '/<%= baseName %>', 42 | host: 'localhost', 43 | port: 3000, 44 | https: false, 45 | changeOrigin: false 46 | } 47 | ], 48 | options: { 49 | port: 9000, 50 | // Change this to '0.0.0.0' to access the server from outside. 51 | hostname: 'localhost', 52 | livereload: 35729 53 | }, 54 | livereload: { 55 | options: { 56 | open: true, 57 | base: [ 58 | '<%%= yeoman.app %>' 59 | ], 60 | middleware: function (connect) { 61 | return [ 62 | proxySnippet, 63 | connect.static(require('path').resolve('public')) 64 | ]; 65 | } 66 | } 67 | }, 68 | /* 69 | dist: { 70 | options: { 71 | base: '<%%= yeoman.dist %>' 72 | } 73 | } 74 | */ 75 | }, 76 | // Put files not handled in other tasks here 77 | copy: { 78 | dist: { 79 | files: [{ 80 | expand: true, 81 | dot: true, 82 | cwd: '<%%= yeoman.app %>', 83 | dest: '<%%= yeoman.dist %>', 84 | src: '**' 85 | }] 86 | }, 87 | }, 88 | // Test settings 89 | karma: { 90 | unit: { 91 | configFile: 'test/config/karma.conf.js', 92 | singleRun: true 93 | } 94 | }, 95 | bowercopy: { 96 | options: { 97 | destPrefix: '<%%= yeoman.app %>' 98 | }, 99 | test: { 100 | files: { 101 | 'test/lib/angular-mocks': 'angular-mocks', 102 | 'test/lib/angular-scenario': 'angular-scenario' 103 | } 104 | } 105 | } 106 | }); 107 | 108 | grunt.registerTask('server', function (target) { 109 | grunt.task.run([ 110 | //'copy:dist', 111 | 'configureProxies', 112 | 'connect:livereload', 113 | 'watch' 114 | ]); 115 | }); 116 | }; 117 | -------------------------------------------------------------------------------- /app/templates/README.md.erb: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Blah blah 4 | 5 | -------------------------------------------------------------------------------- /app/templates/_app.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , bodyParser = require('body-parser') 3 | , errorHandler = require('errorhandler') 4 | , methodOverride = require('method-override') 5 | , morgan = require('morgan') 6 | , http = require('http') 7 | , path = require('path') 8 | , db = require('./models') 9 | <% _.each(entities, function (entity) { %> 10 | , <%= pluralize(entity.name) %> = require('./routes/<%= pluralize(entity.name) %>')<% }); %> 11 | 12 | var app = express() 13 | 14 | // all environments 15 | app.set('port', process.env.PORT || 3000) 16 | app.set('views', __dirname + '/views') 17 | app.set('view engine', 'jade') 18 | app.use(morgan('dev')) 19 | app.use(bodyParser()) 20 | app.use(methodOverride()) 21 | app.use(express.static(path.join(__dirname, 'public'))) 22 | 23 | // development only 24 | if ('development' === app.get('env')) { 25 | app.use(errorHandler()) 26 | } 27 | 28 | <% _.each(entities, function (entity) { %> 29 | app.get('/<%= baseName %>/<%= pluralize(entity.name) %>', <%= pluralize(entity.name) %>.findAll) 30 | app.get('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.find) 31 | app.post('/<%= baseName %>/<%= pluralize(entity.name) %>', <%= pluralize(entity.name) %>.create) 32 | app.put('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.update) 33 | app.del('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.destroy) 34 | <% }); %> 35 | 36 | db 37 | .sequelize 38 | .sync() 39 | .complete(function(err) { 40 | if (err) { 41 | throw err 42 | } else { 43 | http.createServer(app).listen(app.get('port'), function() { 44 | console.log('Express server listening on port ' + app.get('port')) 45 | }) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.camelize(baseName) %>", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular-bootstrap": "~0.10.0", 6 | "angular-resource": "~1.2.13", 7 | "angular-route": "~1.2.13", 8 | "angular-ui-date": "~0.0.3", 9 | "angular": "~1.2.13", 10 | "bootstrap-css": "~3.0.0", 11 | "jquery": "~2.1.0", 12 | "lodash": "~2.4.1" 13 | }, 14 | "devDependencies": { 15 | "angular-mocks": "~1.2.13", 16 | "angular-scenario": "~1.2.13" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/templates/_generator.json: -------------------------------------------------------------------------------- 1 | <%= generatorConfigStr %> 2 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(baseName) %>", 3 | "version": "0.0.0", 4 | "description": "Description for <%= baseName %>", 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.1.1", 10 | "body-parser": "~1.0.0", 11 | "errorhandler": "~1.0.0", 12 | "method-override": "~1.0.0", 13 | "morgan": "~1.0.0", 14 | "sequelize": "~2.0.0-beta.2", 15 | "sqlite3": "~2.1.5", 16 | "lodash": "~2.4.1" 17 | }, 18 | "devDependencies": { 19 | "grunt": "~0.4.2", 20 | "grunt-autoprefixer": "~0.4.2", 21 | "grunt-bowercopy": "~0.4.1", 22 | "grunt-bower-install": "~0.7.0", 23 | "grunt-concurrent": "~0.4.2", 24 | "grunt-connect-proxy": "~0.2.0", 25 | "grunt-contrib-clean": "~0.5.0", 26 | "grunt-contrib-concat": "~0.3.0", 27 | "grunt-contrib-connect": "~0.5.0", 28 | "grunt-contrib-copy": "~0.4.1", 29 | "grunt-contrib-cssmin": "~0.7.0", 30 | "grunt-contrib-htmlmin": "~0.1.3", 31 | "grunt-contrib-imagemin": "~0.4.0", 32 | "grunt-contrib-jshint": "~0.7.2", 33 | "grunt-contrib-uglify": "~0.2.7", 34 | "grunt-contrib-watch": "~0.5.3", 35 | "grunt-karma": "~0.6.2", 36 | "grunt-modernizr": "~0.4.1", 37 | "grunt-ngmin": "~0.0.3", 38 | "grunt-rev": "~0.1.0", 39 | "grunt-svgmin": "~0.3.0", 40 | "grunt-sync": "~0.0.5", 41 | "grunt-usemin": "~2.0.2", 42 | "load-grunt-tasks": "~0.2.0", 43 | "time-grunt": "0.2.3", 44 | "karma" : "~0.10.8", 45 | "karma-junit-reporter" : "~0.1.0", 46 | "karma-jasmine" : "~0.1.0", 47 | "karma-ng-scenario" : "~0.1.0" 48 | }, 49 | "engines": { 50 | "node": ">=0.8.15" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/templates/bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/lib", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 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 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~ 4 | node_modules 5 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /app/templates/models/_index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , Sequelize = require('sequelize') 4 | , lodash = require('lodash') 5 | , sequelize = new Sequelize('sequelize_test', 'root', null, { 6 | dialect: "sqlite", // or 'sqlite', 'postgres', 'mariadb' 7 | storage: "/tmp/my.db", 8 | }) 9 | , db = {} 10 | 11 | fs 12 | .readdirSync(__dirname) 13 | .filter(function(file) { 14 | return ((file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) == '.js')) 15 | }) 16 | .forEach(function(file) { 17 | var model = sequelize.import(path.join(__dirname, file)) 18 | db[model.name] = model 19 | }) 20 | 21 | Object.keys(db).forEach(function(modelName) { 22 | if (db[modelName].hasOwnProperty('associate')) { 23 | db[modelName].associate(db) 24 | } 25 | }) 26 | 27 | module.exports = lodash.extend({ 28 | sequelize: sequelize, 29 | Sequelize: Sequelize 30 | }, db) 31 | -------------------------------------------------------------------------------- /app/templates/public/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= _.capitalize(baseName) %> 8 | 9 | 10 | 11 | 12 | 13 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | <% _.each(entities, function (entity) { %> 52 | 53 | 54 | 55 | <% }); %> 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/templates/public/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 60px; 3 | padding: 10px; 4 | background-color: #ECF0F1; 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/public/js/_app.js: -------------------------------------------------------------------------------- 1 | // Declare app level module which depends on filters, and services 2 | angular.module('<%= baseName %>', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ui.date']) 3 | .config(['$routeProvider', function ($routeProvider) { 4 | $routeProvider 5 | .when('/', { 6 | templateUrl: 'views/home/home.html', 7 | controller: 'HomeController'}) 8 | .otherwise({redirectTo: '/'}); 9 | }]); 10 | -------------------------------------------------------------------------------- /app/templates/public/js/home/_home-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= baseName %>') 2 | .controller('HomeController', ['$scope', function ($scope) { 3 | }]); 4 | -------------------------------------------------------------------------------- /app/templates/public/views/home/_home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Welcome to <%= _.capitalize(baseName) %>!

5 |

This is your homepage, ready for editing

6 | 7 |

8 | If you like this Angular-Express-Sequelize generator, please give us a star at  Github! 9 |

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /entity/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'), 3 | yeoman = require('yeoman-generator'), 4 | fs = require('fs'), 5 | _ = require('lodash'), 6 | _s = require('underscore.string'), 7 | pluralize = require('pluralize'); 8 | 9 | var EntityGenerator = module.exports = function EntityGenerator(args, options, config) { 10 | // By calling `NamedBase` here, we get the argument to the subgenerator call 11 | // as `this.name`. 12 | yeoman.generators.NamedBase.apply(this, arguments); 13 | 14 | console.log('You called the entity subgenerator with the argument ' + this.name + '.'); 15 | 16 | fs.readFile('generator.json', 'utf8', function (err, data) { 17 | if (err) { 18 | console.log('Error: ' + err); 19 | return; 20 | } 21 | this.generatorConfig = JSON.parse(data); 22 | }.bind(this)); 23 | }; 24 | 25 | util.inherits(EntityGenerator, yeoman.generators.NamedBase); 26 | 27 | EntityGenerator.prototype.askFor = function askFor() { 28 | var cb = this.async(); 29 | 30 | console.log('\nPlease specify an attribute:'); 31 | 32 | var prompts = [{ 33 | type: 'input', 34 | name: 'attrName', 35 | message: 'What is the name of the attribute?', 36 | default: 'myattr' 37 | }, 38 | { 39 | type: 'list', 40 | name: 'attrType', 41 | message: 'What is the type of the attribute?', 42 | choices: ['String', 'Integer', 'Float', 'Boolean', 'Date', 'Enum'], 43 | default: 'String' 44 | }, 45 | { 46 | when: function (props) { return (/String/).test(props.attrType); }, 47 | type: 'input', 48 | name: 'minLength', 49 | message: 'Enter the minimum length for the String attribute, or hit enter:', 50 | validate: function (input) { 51 | if (input && isNaN(input)) { 52 | return "Please enter a number."; 53 | } 54 | return true; 55 | } 56 | }, 57 | { 58 | when: function (props) { return (/String/).test(props.attrType); }, 59 | type: 'input', 60 | name: 'maxLength', 61 | message: 'Enter the maximum length for the String attribute, or hit enter:', 62 | validate: function (input) { 63 | if (input && isNaN(input)) { 64 | return "Please enter a number."; 65 | } 66 | return true; 67 | } 68 | }, 69 | { 70 | when: function (props) { return (/Integer|Float/).test(props.attrType); }, 71 | type: 'input', 72 | name: 'min', 73 | message: 'Enter the minimum value for the numeric attribute, or hit enter:', 74 | validate: function (input) { 75 | if (input && isNaN(input)) { 76 | return "Please enter a number."; 77 | } 78 | return true; 79 | } 80 | }, 81 | { 82 | when: function (props) { return (/Integer|Float/).test(props.attrType); }, 83 | type: 'input', 84 | name: 'max', 85 | message: 'Enter the maximum value for the numeric attribute, or hit enter:', 86 | validate: function (input) { 87 | if (input && isNaN(input)) { 88 | return "Please enter a number."; 89 | } 90 | return true; 91 | } 92 | }, 93 | { 94 | when: function (props) { return (/Date/).test(props.attrType); }, 95 | type: 'list', 96 | name: 'dateConstraint', 97 | message: 'Constrain the date as follows:', 98 | choices: ['None', 'Past dates only', 'Future dates only'], 99 | filter: function (input) { 100 | if (/Past/.test(input)) return 'Past'; 101 | if (/Future/.test(input)) return 'Future'; 102 | return ''; 103 | }, 104 | default: 'None' 105 | }, 106 | { 107 | when: function (props) { return (/Enum/).test(props.attrType); }, 108 | type: 'input', 109 | name: 'enumValues', 110 | message: 'Enter an enumeration of values, separated by commas' 111 | }, 112 | { 113 | type: 'confirm', 114 | name: 'required', 115 | message: 'Is the attribute required to have a value?', 116 | default: true 117 | }, 118 | { 119 | type: 'confirm', 120 | name: 'again', 121 | message: 'Would you like to enter another attribute or reenter a previous attribute?', 122 | default: true 123 | }]; 124 | 125 | this.prompt(prompts, function (props) { 126 | this.attrs = this.attrs || []; 127 | var attrType = props.attrType; 128 | this.attrs = _.reject(this.attrs, function (attr) { return attr.attrName === props.attrName; }); 129 | this.attrs.push({ 130 | attrName: props.attrName, 131 | attrType: attrType, 132 | minLength: props.minLength, 133 | maxLength: props.maxLength, 134 | min: props.min, 135 | max: props.max, 136 | dateConstraint: props.dateConstraint, 137 | enumValues: props.enumValues ? props.enumValues.split(',') : [], 138 | required: props.required 139 | }); 140 | 141 | if (props.again) { 142 | this.askFor(); 143 | } else { 144 | cb(); 145 | } 146 | }.bind(this)); 147 | }; 148 | 149 | EntityGenerator.prototype.files = function files() { 150 | 151 | this.baseName = this.generatorConfig.baseName; 152 | this.packageName = this.generatorConfig.packageName; 153 | this.entities = this.generatorConfig.entities; 154 | this.entities = _.reject(this.entities, function (entity) { return entity.name === this.name; }.bind(this)); 155 | this.entities.push({ name: this.name, attrs: this.attrs}); 156 | this.pluralize = pluralize; 157 | this.generatorConfig.entities = this.entities; 158 | this.generatorConfigStr = JSON.stringify(this.generatorConfig, null, '\t'); 159 | 160 | this.template('_generator.json', 'generator.json'); 161 | this.template('../../app/templates/_app.js', 'app.js'); 162 | this.template('models/_entity.js', 'models/' + this.name + '.js'); 163 | this.template('routes/_entities.js', 'routes/' + pluralize(this.name) + '.js'); 164 | 165 | var publicDir = 'public/'; 166 | var publicCssDir = publicDir + 'css/'; 167 | var publicJsDir = publicDir + 'js/'; 168 | var publicViewDir = publicDir + 'views/'; 169 | var publicEntityJsDir = publicJsDir + this.name + '/'; 170 | var publicEntityViewDir = publicViewDir + this.name + '/'; 171 | this.mkdir(publicEntityJsDir); 172 | this.mkdir(publicEntityViewDir); 173 | this.template('../../app/templates/public/_index.html', publicDir + 'index.html'); 174 | this.template('public/js/entity/_entity-controller.js', publicEntityJsDir + this.name + '-controller.js'); 175 | this.template('public/js/entity/_entity-router.js', publicEntityJsDir + this.name + '-router.js'); 176 | this.template('public/js/entity/_entity-service.js', publicEntityJsDir + this.name + '-service.js'); 177 | this.template('public/views/entity/_entities.html', publicEntityViewDir + pluralize(this.name) + '.html'); 178 | }; 179 | -------------------------------------------------------------------------------- /entity/templates/_generator.json: -------------------------------------------------------------------------------- 1 | <%= generatorConfigStr %> 2 | -------------------------------------------------------------------------------- /entity/templates/models/_entity.js: -------------------------------------------------------------------------------- 1 | module.exports = function(sequelize, DataTypes) { 2 | var <%= _.capitalize(name) %> = sequelize.define('<%= _.capitalize(name) %>', { 3 | <% _.each(attrs, function (attr) { %> 4 | <%= attr.attrName %>: { 5 | type: DataTypes.<%= attr.attrType.toUpperCase() %><% if (attr.attrType == 'Enum') { %>(<% var delim = ''; _.each(attr.enumValues, function (value) { %><%= delim %>'<%= value %>'<% delim = ', '; }) %>)<% }; %>, 6 | validate: { 7 | notNull: <%= attr.required %>, 8 | <% if (attr.maxLength) { if (attr.minLength) { %>len: [<%= attr.minLength %>, <%= attr.maxLength %>],<% } else { %>len: [0, <%= attr.maxLength %>],<% } };%> 9 | <% if (attr.min) { %>min: <%= attr.min %>,<% };%> 10 | <% if (attr.max) { %>max: <%= attr.max %>,<% };%> 11 | }, 12 | <% if (attr.attrType == 'Date') { %>get: function() { 13 | var value = this.getDataValue('<%= attr.attrName %>') 14 | return value ? value.toISOString().substring(0, 10) : value 15 | }<% }; %> 16 | }, 17 | <% }); %> 18 | }) 19 | 20 | return <%= _.capitalize(name) %> 21 | } 22 | -------------------------------------------------------------------------------- /entity/templates/public/js/entity/_entity-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('<%= baseName %>') 4 | .controller('<%= _.capitalize(name) %>Controller', ['$scope', '$modal', 'resolved<%= _.capitalize(name) %>', '<%= _.capitalize(name) %>', 5 | function ($scope, $modal, resolved<%= _.capitalize(name) %>, <%= _.capitalize(name) %>) { 6 | 7 | $scope.<%= pluralize(name) %> = resolved<%= _.capitalize(name) %>; 8 | 9 | $scope.create = function () { 10 | $scope.clear(); 11 | $scope.open(); 12 | }; 13 | 14 | $scope.update = function (id) { 15 | $scope.<%= name %> = <%= _.capitalize(name) %>.get({id: id}); 16 | $scope.open(id); 17 | }; 18 | 19 | $scope.delete = function (id) { 20 | <%= _.capitalize(name) %>.delete({id: id}, 21 | function () { 22 | $scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query(); 23 | }); 24 | }; 25 | 26 | $scope.save = function (id) { 27 | if (id) { 28 | <%= _.capitalize(name) %>.update({id: id}, $scope.<%= name %>, 29 | function () { 30 | $scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query(); 31 | $scope.clear(); 32 | }); 33 | } else { 34 | <%= _.capitalize(name) %>.save($scope.<%= name %>, 35 | function () { 36 | $scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query(); 37 | $scope.clear(); 38 | }); 39 | } 40 | }; 41 | 42 | $scope.clear = function () { 43 | $scope.<%= name %> = { 44 | <% _.each(attrs, function (attr) { %> 45 | "<%= attr.attrName %>": "", 46 | <% }); %> 47 | "id": "" 48 | }; 49 | }; 50 | 51 | $scope.open = function (id) { 52 | var <%= name %>Save = $modal.open({ 53 | templateUrl: '<%= name %>-save.html', 54 | controller: '<%= _.capitalize(name) %>SaveController', 55 | resolve: { 56 | <%= name %>: function () { 57 | return $scope.<%= name %>; 58 | } 59 | } 60 | }); 61 | 62 | <%= name %>Save.result.then(function (entity) { 63 | $scope.<%= name %> = entity; 64 | $scope.save(id); 65 | }); 66 | }; 67 | }]) 68 | .controller('<%= _.capitalize(name) %>SaveController', ['$scope', '$modalInstance', '<%= name %>', 69 | function ($scope, $modalInstance, <%= name %>) { 70 | $scope.<%= name %> = <%= name %>; 71 | 72 | <% _.each(attrs, function (attr) { if (attr.attrType === 'Date') { %> 73 | $scope.<%= attr.attrName %>DateOptions = { 74 | dateFormat: 'yy-mm-dd', 75 | <% if (attr.dateConstraint === 'Past') { %>maxDate: -1<% } %> 76 | <% if (attr.dateConstraint === 'Future') { %>minDate: 1<% } %> 77 | };<% }}); %> 78 | 79 | $scope.ok = function () { 80 | $modalInstance.close($scope.<%= name %>); 81 | }; 82 | 83 | $scope.cancel = function () { 84 | $modalInstance.dismiss('cancel'); 85 | }; 86 | }]); 87 | -------------------------------------------------------------------------------- /entity/templates/public/js/entity/_entity-router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('<%= baseName %>') 4 | .config(['$routeProvider', function ($routeProvider) { 5 | $routeProvider 6 | .when('/<%= pluralize(name) %>', { 7 | templateUrl: 'views/<%= name %>/<%= pluralize(name) %>.html', 8 | controller: '<%= _.capitalize(name) %>Controller', 9 | resolve:{ 10 | resolved<%= _.capitalize(name) %>: ['<%= _.capitalize(name) %>', function (<%= _.capitalize(name) %>) { 11 | return <%= _.capitalize(name) %>.query(); 12 | }] 13 | } 14 | }) 15 | }]); 16 | -------------------------------------------------------------------------------- /entity/templates/public/js/entity/_entity-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('<%= baseName %>') 4 | .factory('<%= _.capitalize(name) %>', ['$resource', function ($resource) { 5 | return $resource('<%= baseName %>/<%= pluralize(name) %>/:id', {}, { 6 | 'query': { method: 'GET', isArray: true}, 7 | 'get': { method: 'GET'}, 8 | 'update': { method: 'PUT'} 9 | }); 10 | }]); 11 | -------------------------------------------------------------------------------- /entity/templates/public/views/entity/_entities.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

<%= _.capitalize(pluralize(name)) %>

4 | 5 | 64 | 65 | 68 | 69 |
70 | 71 | 72 | 73 | 74 | <% _.each(attrs, function (attr) { %> 75 | 76 | <% }); %> 77 | 78 | 79 | 80 | 81 | 82 | <% _.each(attrs, function (attr) { %> 83 | 84 | <% }); %> 85 | 97 | 98 | 99 |
ID<%= attr.attrName %>
{{<%= name %>.id}}{{<%= name %>.<%= attr.attrName %> <% if (attr.attrType === 'Date') { %> | date:'yyyy-MM-dd'<% } %>}} 86 | 91 | 96 |
100 |
101 |
102 | -------------------------------------------------------------------------------- /entity/templates/routes/_entities.js: -------------------------------------------------------------------------------- 1 | var db = require('../models') 2 | 3 | exports.findAll = function(req, res) { 4 | db.<%= _.capitalize(name) %>.findAll().success(function(entities) { 5 | res.json(entities) 6 | }) 7 | } 8 | 9 | exports.find = function(req, res) { 10 | db.<%= _.capitalize(name) %>.find({ where: { id: req.param('id') } }).success(function(entity) { 11 | if (entity) { 12 | res.json(entity) 13 | } else { 14 | res.send(404) 15 | } 16 | }) 17 | } 18 | 19 | exports.create = function(req, res) { 20 | db.<%= _.capitalize(name) %>.create(req.body).success(function(entity) { 21 | res.statusCode = 201 22 | res.json(entity) 23 | }) 24 | } 25 | 26 | exports.update = function(req, res) { 27 | db.<%= _.capitalize(name) %>.find({ where: { id: req.param('id') } }).success(function(entity) { 28 | if (entity) { 29 | entity.updateAttributes(req.body).success(function(entity) { 30 | res.json(entity) 31 | }) 32 | } else { 33 | res.send(404) 34 | } 35 | }) 36 | } 37 | 38 | exports.destroy = function(req, res) { 39 | db.<%= _.capitalize(name) %>.find({ where: { id: req.param('id') } }).success(function(entity) { 40 | if (entity) { 41 | entity.destroy().success(function() { 42 | res.send(204) 43 | }) 44 | } else { 45 | res.send(404) 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-angular-express-sequelize", 3 | "version": "0.1.13", 4 | "description": "A Yeoman generator for AngularJS + Express + Sequelize", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "Node.js", 8 | "Express", 9 | "Sequelize", 10 | "AngularJS", 11 | "Bootstrap" 12 | ], 13 | "homepage": "https://github.com/rayokota/generator-angular-express-sequelize", 14 | "bugs": "https://github.com/rayokota/generator-angular-express-sequelize/issues", 15 | "author": { 16 | "name": "Robert Yokota", 17 | "email": "", 18 | "url": "https://github.com/rayokota" 19 | }, 20 | "main": "app/index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/rayokota/generator-angular-express-sequelize.git" 24 | }, 25 | "scripts": { 26 | "test": "mocha" 27 | }, 28 | "dependencies": { 29 | "yeoman-generator": "~0.14.0", 30 | "URIjs": "~1.11.2", 31 | "lodash": "~2.4.1", 32 | "underscore.string": "~2.3.3", 33 | "pluralize": "~0.0.6", 34 | "asciify": "~1.3.3" 35 | }, 36 | "devDependencies": { 37 | "mocha": "~1.14.0" 38 | }, 39 | "peerDependencies": { 40 | "yo": ">=1.0.0" 41 | }, 42 | "engines": { 43 | "node": ">=0.8.0", 44 | "npm": ">=1.2.10" 45 | }, 46 | "licenses": [ 47 | { 48 | "type": "MIT" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /test/test-creation.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it*/ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var helpers = require('yeoman-generator').test; 6 | 7 | 8 | describe('angular-express-sequelize generator', function () { 9 | beforeEach(function (done) { 10 | helpers.testDirectory(path.join(__dirname, 'temp'), function (err) { 11 | if (err) { 12 | return done(err); 13 | } 14 | 15 | this.app = helpers.createGenerator('angular-express-sequelize:app', [ 16 | '../../app' 17 | ]); 18 | done(); 19 | }.bind(this)); 20 | }); 21 | 22 | it('creates expected files', function (done) { 23 | var expected = [ 24 | // add files you expect to exist here. 25 | '.jshintrc', 26 | '.editorconfig' 27 | ]; 28 | 29 | helpers.mockPrompt(this.app, { 30 | 'someOption': true 31 | }); 32 | this.app.options['skip-install'] = true; 33 | this.app.run({}, function () { 34 | helpers.assertFiles(expected); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/test-load.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it*/ 2 | 'use strict'; 3 | 4 | var assert = require('assert'); 5 | 6 | describe('angular-express-sequelize generator', function () { 7 | it('can be imported without blowing up', function () { 8 | var app = require('../app'); 9 | assert(app !== undefined); 10 | }); 11 | }); 12 | --------------------------------------------------------------------------------