├── templates └── app │ ├── web │ ├── img │ │ └── .gitkeep │ ├── js │ │ └── main.ejs │ └── css │ │ └── main.css.ejs │ ├── runtime │ └── .gitignore │ ├── locales │ └── en.ejs │ ├── configs │ ├── view-filters.ejs │ ├── database.ejs │ ├── view-locals.ejs │ ├── auth.ejs │ ├── routes.ejs │ └── main.ejs │ ├── controllers │ ├── site.ejs │ └── auth.ejs │ ├── views │ └── ejs │ │ ├── error │ │ ├── 403.ejs │ │ ├── 401.ejs │ │ ├── 404.ejs │ │ └── error.ejs │ │ ├── layouts │ │ ├── error.ejs │ │ └── main.ejs │ │ └── site │ │ ├── index.ejs │ │ └── login.ejs │ ├── models │ └── user.ejs │ ├── package.ejs │ └── app.ejs ├── images ├── green.jpg ├── violet.jpg ├── green-auth.jpg └── red-noauth.jpg ├── .gitignore ├── package.json ├── README.md └── bin └── koa-mvc /templates/app/web/img/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/app/runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /templates/app/locales/en.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "HELLO_WORLD": "Hello World!" 3 | } -------------------------------------------------------------------------------- /images/green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gusnips/node-koa-mvc/HEAD/images/green.jpg -------------------------------------------------------------------------------- /images/violet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gusnips/node-koa-mvc/HEAD/images/violet.jpg -------------------------------------------------------------------------------- /templates/app/configs/view-filters.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //view filters 3 | module.exports={} -------------------------------------------------------------------------------- /images/green-auth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gusnips/node-koa-mvc/HEAD/images/green-auth.jpg -------------------------------------------------------------------------------- /images/red-noauth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gusnips/node-koa-mvc/HEAD/images/red-noauth.jpg -------------------------------------------------------------------------------- /templates/app/controllers/site.ejs: -------------------------------------------------------------------------------- 1 | "use strict" 2 | //Site controller 3 | module.exports={ 4 | index: function *(){ 5 | yield this.render('site/index') 6 | }, 7 | doContact: function *(){ 8 | this.body={success: true} 9 | } 10 | } -------------------------------------------------------------------------------- /templates/app/configs/database.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var Waterline= require('waterline') 3 | var orm= new Waterline() 4 | module.exports=function(app, config, callback){ 5 | var user= require('../models/user'); 6 | orm.loadCollection(user); 7 | 8 | orm.initialize(config, callback) 9 | return orm 10 | } -------------------------------------------------------------------------------- /templates/app/configs/view-locals.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //view locals 3 | module.exports={ 4 | name: '<%=: name %>', 5 | title: '<%=: name|capitalize %>', 6 | description: '', 7 | <% if(program.session){ -%> 8 | flash: function(){ 9 | return this.flash; 10 | }, 11 | <% } -%> 12 | <% if(program.auth){ -%> 13 | user: function(){ 14 | return this.request.user; 15 | }, 16 | isAuthenticated: function(){ 17 | return this.isAuthenticated() 18 | }, 19 | <% } -%> 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | .tmp 28 | -------------------------------------------------------------------------------- /templates/app/views/ejs/error/403.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

403

5 |

OOPS! ACCESS FORBIDDEN!

6 |

7 | <%= request.url %>
8 | Read, Execute and Write access forbidden. 9 |

navigate back to Home 10 |

11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /templates/app/views/ejs/error/401.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

401

5 |

OOPS! ACCESS UNAUTHORIZED!

6 |

7 | <%= request.url %>
8 | The request requires user authentication. 9 |

navigate back to Home 10 |

11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /templates/app/views/ejs/error/404.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

404

5 |

OOPS! PAGE NOT FOUND!

6 |

7 | <%= request.url %>
8 | Sorry we failed to locate this page or resource. 9 |

navigate back to Home 10 |

11 |
12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /templates/app/views/ejs/error/error.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

<%=status%>

5 |

<%-error%>

6 |

7 | Sorry an error has occurred, please try refreshing the page or 8 |

navigate back to Home 9 |

10 | <% if(env == 'development'){%> 11 |
12 |           
13 | 	    <%-stack%>
14 |           
15 |       	
16 | <% } %> 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /templates/app/controllers/auth.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //Auth controller - holds authentication logic 3 | module.exports={ 4 | login: function *(){ 5 | yield this.render('site/login') 6 | }, 7 | logout: function *(){ 8 | <% if(program.session){%> 9 | this.flash.success='See you soon!' 10 | <% } %> 11 | this.redirect('/') 12 | }, 13 | doLogin: function *(){ 14 | this.body={success: true} 15 | }, 16 | register: function *(){ 17 | yield this.render('site/login') 18 | }, 19 | doRegister: function *(){ 20 | var self=this 21 | var attributes= this.request.body 22 | var UserModel= this.models.user 23 | UserModel.create(attributes).exec(function(){ 24 | self.body={success: true} 25 | }); 26 | }, 27 | authSuccess: function(){ 28 | this.redirect('/') 29 | } 30 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-mvc", 3 | "description": "Koa MVC generator", 4 | "version": "0.0.5", 5 | "author": "Gustavo Salomé Silva ", 6 | "dependencies": { 7 | "commander": "^1.3.2", 8 | "mkdirp": "^0.3.5", 9 | "ejs": "*" 10 | }, 11 | "keywords": [ 12 | "koa", 13 | "bootstrap", 14 | "seed", 15 | "mvc", 16 | "seed", 17 | "boilerplate" 18 | ], 19 | "main": "bin/koa-mvc", 20 | "bin": { 21 | "koa-mvc": "./bin/koa-mvc" 22 | }, 23 | "engines": { 24 | "node": ">= 0.11.9" 25 | }, 26 | "readmeFilename": "README.md", 27 | "homepage": "https://github.com/gusnips/node-koa-mvc", 28 | "repository": { 29 | "type": "git", 30 | "url": "git@github.com:gusnips/node-koa-mvc.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/gusnips/node-koa-mvc/issues" 34 | }, 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /templates/app/web/js/main.ejs: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $messages=$('.messages .alert'); 3 | var showMessage= function(message, type){ 4 | type=type || 'info'; 5 | var $message=$messages.filter('.alert-'+type); 6 | $messages.hide(); 7 | $message.show().find('span').html(message); 8 | $('html, body').animate({ 9 | scrollTop: $message.offset().top 10 | }, 500); 11 | $('.modal').modal('hide'); 12 | }; 13 | $('form').on('submit',function(e){ 14 | var $this=$(this); 15 | $.post($this.attr('action'),$this.serialize(),function(result){ 16 | if(result.success) 17 | showMessage(result.message || 'Success!', 'success'); 18 | else 19 | showMessage(result.message || 'Error!', 'danger'); 20 | }).error(function(xhr, text, error){ 21 | showMessage(xhr.responseText || text, 'danger'); 22 | }); 23 | return false; 24 | }); 25 | $('form input').tooltip({ 26 | placement: 'top', 27 | trigger: 'focus', 28 | title: function (){ 29 | return $(this).attr('placeholder'); 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /templates/app/views/ejs/layouts/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%=(status + ' Error')+ ' - ' + title%> 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |
17 | 18 | <%- body %> 19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/app/models/user.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //https://github.com/balderdashy/waterline-docs 3 | var Waterline = require('waterline'); 4 | var bcrypt = require('bcrypt'); 5 | var User = Waterline.Collection.extend({ 6 | identity: 'user', 7 | connection: 'default', 8 | attributes: { 9 | email: { 10 | type: 'string', 11 | required: true, 12 | email: true 13 | }, 14 | password: { 15 | type: 'string', 16 | password: true, 17 | minLength: 6, 18 | maxLength: 21 19 | }, 20 | passwordConfirmation: function(){ 21 | return this.passwordConfirmation; 22 | } 23 | }, 24 | //custom validation rules 25 | types: { 26 | password: function(password) { 27 | return password === this.passwordConfirmation; 28 | } 29 | }, 30 | beforeCreate: function(values, next) { 31 | bcrypt.hash(values.password, 10, function(err, hash) { 32 | if(err) 33 | return next(err); 34 | values.password = hash; 35 | next(); 36 | }); 37 | }, 38 | comparePassword: function *(candidatePassword) { 39 | return yield bcrypt.compare(candidatePassword, this.password); 40 | }, 41 | matchUser: function *(email, password) { 42 | var user = yield this.findOne({ 'email': email.toLowerCase() }).exec(); 43 | if (!user) 44 | throw new Error('User not found'); 45 | 46 | if (yield user.comparePassword(password)) 47 | return user; 48 | 49 | throw new Error('Password does not match'); 50 | } 51 | }); 52 | 53 | module.exports= User; -------------------------------------------------------------------------------- /templates/app/package.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%=name%>", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "Generated with koa-mvc", 6 | "dependencies": { 7 | "co": "3.x.x", 8 | "thunkify": "2.x.x", 9 | "koa": "0.x.x", 10 | "koa-bodyparser": "1.x.x", 11 | "koa-static": "1.x.x", 12 | "koa-router": "3.x.x", 13 | <% if(program.session){ -%> 14 | "koa-generic-session": "1.x.x", 15 | "koa-flash": "0.0.x", 16 | <% } -%> 17 | <% if(program.template==='ejs'){ -%> 18 | "koa-ejs": "1.x.x", 19 | "koa-error-ejs": "0.0.x", 20 | <% } else if(program.template==='swig'){ -%> 21 | "koa-swig": "0.x.x", 22 | "koa-error": "1.x.x", 23 | <% } -%> 24 | <% if(program.i18n){ -%> 25 | "koa-i18n": "0.x.x", 26 | "koa-locale": "0.x.x", 27 | <% } -%> 28 | <% if(program.css==='less'){ -%> 29 | "koa-less": "0.0.x", 30 | <% } else if(program.css==='stylus'){ -%> 31 | "koa-stylus": "0.0.x", 32 | <% } -%> 33 | <% if(program.auth){ -%> 34 | "koa-passport": "0.x.x", 35 | "passport-local": "1.x.x", 36 | <% if(program.auth.facebook){ -%> 37 | "passport-facebook": "1.x.x", 38 | <% } -%> 39 | <% if(program.auth.google){ -%> 40 | "passport-google": "0.x.x", 41 | <% } -%> 42 | <% if(program.auth.github){ -%> 43 | "passport-github": "0.x.x", 44 | <% } -%> 45 | <% if(program.auth.bitbucket){ -%> 46 | "passport-bitbucket": "0.x.x", 47 | <% } -%> 48 | <% } -%> 49 | "waterline": "0.10.*", 50 | "sails-<%=program.db%>": "0.10.*", 51 | "bcrypt": "rvagg/node.bcrypt.js#nan" 52 | }, 53 | "devDependencies": { 54 | "debug": "1.x.x" 55 | }, 56 | "engines": { 57 | "node": ">= 0.11.9" 58 | }, 59 | "scripts": { 60 | "start": "node --harmony app", 61 | "test": "echo \"Error: no test specified\" && exit 1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /templates/app/app.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var config= require('./configs/main') 3 | var app= require('koa')() 4 | 5 | app.name= config.name 6 | app.keys= config.keys 7 | app.env= config.env 8 | 9 | if(config.env==='development') 10 | var debug = require('debug')('<%=name%>'); 11 | 12 | app.use(require('koa-bodyparser')(config.bodyparser)) 13 | <% if(program.css==='less'){ -%> 14 | app.use(require('koa-less')(config.static.directory)) 15 | <% } else if(program.css==='stylus'){ -%> 16 | app.use(require('koa-stylus')(config.static.directory)) 17 | <% } -%> 18 | <% if(program.i18n){ -%> 19 | require('koa-locale')(app) 20 | app.use(require('koa-i18n')(app, config.i18n)) 21 | <% } -%> 22 | app.use(require('koa-static')(config.static.directory, config.static)) 23 | 24 | <% if(program.session){ -%> 25 | app.use(require('koa-generic-session')(config.session)) 26 | app.use(require('koa-flash')(config.flash)) 27 | <% } -%> 28 | 29 | <% if(program.template==='ejs'){ -%> 30 | require('koa-ejs')(app, config.view) 31 | app.use(require('koa-error-ejs')(config.error)) 32 | <% } else if(program.template==='swig'){ -%> 33 | require('koa-swig')(app, config.view) 34 | app.use(require('koa-error')(config.error)) 35 | <% } -%> 36 | 37 | <% if(program.auth){ -%> 38 | var passport=require('./configs/auth')(app, config.auth) 39 | app.use(passport.initialize()) 40 | <% if(program.session){ -%> 41 | app.use(passport.session()) 42 | <% } -%> 43 | <% } -%> 44 | 45 | <% if(program.auth){ -%> 46 | app.use(require('./configs/routes')(app, passport)); 47 | <% } else { -%> 48 | app.use(require('./configs/routes')(app)); 49 | <% } -%> 50 | require('./configs/database')(app, config.database, function(err, ontology){ 51 | if(err) 52 | throw err 53 | app.context.models=ontology.collections 54 | console.log('database adapter initialized') 55 | }) 56 | 57 | if (!module.parent) { 58 | app.listen(config.port || 3000, function(){ 59 | console.log('Server running on port '+config.port || 3000) 60 | }) 61 | } else 62 | module.exports=app 63 | -------------------------------------------------------------------------------- /templates/app/configs/auth.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var passport = require('koa-passport') 3 | 4 | module.exports=function(app, config){ 5 | <% if(program.session){ -%> 6 | passport.serializeUser(function(user, done) { 7 | done(null, user.id) 8 | }) 9 | 10 | passport.deserializeUser(function(id, done) { 11 | let user={ id: 1, username: 'admin' } 12 | done(null, user) 13 | }) 14 | <% } -%> 15 | 16 | var LocalStrategy = require('passport-local').Strategy 17 | passport.use(new LocalStrategy(function(username, password, done) { 18 | let user={ id: 1, username: 'admin' }; 19 | if (username === 'admin' && password === 'admin') { 20 | done(null, user) 21 | } else { 22 | done(null, false) 23 | } 24 | })) 25 | 26 | <% if(program.auth.facebook){ -%> 27 | var FacebookStrategy = require('passport-facebook').Strategy 28 | passport.use(new FacebookStrategy(config.facebook, function (token, tokenSecret, profile, done) { 29 | let user=profile 30 | done(null, user) 31 | })) 32 | <% } -%> 33 | 34 | <% if(program.auth.google){ -%> 35 | var GoogleStrategy = require('passport-google').Strategy 36 | passport.use(new GoogleStrategy(config.google, function(identifier, profile, done) { 37 | let user=profile 38 | done(null, user) 39 | })) 40 | <% } -%> 41 | 42 | <% if(program.auth.github){ -%> 43 | var GithubStrategy = require('passport-github').Strategy; 44 | passport.use(new GithubStrategy(config.github, function(accessToken, refreshToken, profile, done) { 45 | let user = profile 46 | return done(null, user) 47 | })) 48 | <% } -%> 49 | 50 | <% if(program.auth.bitbucket){ -%> 51 | var BitbucketStrategy = require('passport-bitbucket').Strategy; 52 | passport.use(new BitbucketStrategy(config.bitbucket, function(token, tokenSecret, profile, done) { 53 | let user = profile 54 | return done(null, user) 55 | })) 56 | <% } -%> 57 | 58 | return passport 59 | } -------------------------------------------------------------------------------- /templates/app/configs/routes.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | //https://github.com/alexmingoia/koa-router 3 | var Router= require('koa-router') 4 | var secured = function *(next) { 5 | if (this.isAuthenticated()) { 6 | yield next 7 | } else { 8 | this.status = 401 9 | } 10 | } 11 | 12 | module.exports=function(app<% if(program.auth){%>, passport<% }%>){ 13 | var router= new Router(); 14 | ///// Site 15 | var siteController=require('../controllers/site') 16 | var authController=require('../controllers/auth') 17 | 18 | //main 19 | router.get('/',siteController.index) 20 | //contact 21 | router.post('/contact', siteController.doContact) 22 | 23 | <% if(program.auth){ -%> 24 | //register 25 | router.get('/register', authController.register) 26 | router.post('/register', authController.doRegister) 27 | 28 | //auth 29 | router.get('/login', authController.login) 30 | router.post('/login', authController.doLogin) 31 | router.all('/logout', authController.logout) 32 | 33 | <% if(program.auth.facebook){ -%> 34 | router.get('/auth/facebook',passport.authenticate('facebook')) 35 | router.get('/auth/facebook/callback', 36 | passport.authenticate('facebook', { failureRedirect: '/login?error=facebook' }), 37 | authController.authSuccess 38 | ) 39 | <% } -%> 40 | <% if(program.auth.google){ -%> 41 | router.get('/auth/google',passport.authenticate('google')) 42 | router.get('/auth/google/callback', 43 | passport.authenticate('google', { failureRedirect: '/login?error=google' }), 44 | authController.authSuccess 45 | ) 46 | <% } -%> 47 | <% if(program.auth.github){ -%> 48 | router.get('/auth/github',passport.authenticate('github')) 49 | router.get('/auth/github/callback', 50 | passport.authenticate('github', { failureRedirect: '/login?error=github' }), 51 | authController.authSuccess 52 | ) 53 | <% } -%> 54 | <% if(program.auth.bitbucket){ -%> 55 | router.get('/auth/bitbucket',passport.authenticate('bitbucket')) 56 | router.get('/auth/bitbucket/callback', 57 | passport.authenticate('bitbucket', { failureRedirect: '/login?error=bitbucket' }), 58 | authController.authSuccess 59 | ) 60 | <% } -%> 61 | <% } -%> 62 | return router.middleware(); 63 | } 64 | -------------------------------------------------------------------------------- /templates/app/configs/main.ejs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path=require('path') 3 | 4 | var env=process.env.NODE_ENV || 'development' 5 | var port=process.env.PORT || 3000 6 | var host= 'http://localhost'+(port!=80 ? ':'+port : ''); 7 | 8 | var DEBUG=env !== 'production' 9 | 10 | module.exports={ 11 | //http://koajs.com/#application 12 | name:"<%=name%>", 13 | keys: ['<%=key%>'], 14 | env: env, 15 | port: port, 16 | //https://github.com/koajs/static#options 17 | static: { 18 | directory: path.resolve(__dirname,'../web') 19 | }, 20 | //https://github.com/koajs/body-parser#options 21 | bodyparser: {}, 22 | //https://github.com/koajs/generic-session#options 23 | <% if(program.session){ -%> 24 | session: { 25 | cookie: { 26 | maxAge: 1000 * 60 * 60 * 24//24 hours 27 | } 28 | }, 29 | <% } -%> 30 | <% if(program.auth){ -%> 31 | //https://github.com/rkusa/koa-passport 32 | auth: { 33 | <% if(program.auth.facebook){ -%> 34 | //https://github.com/jaredhanson/passport-facebook 35 | facebook:{ 36 | clientID: 'your-client-id', 37 | clientSecret: 'your-secret', 38 | callbackURL: host + '/auth/facebook/callback' 39 | }, 40 | <% } -%> 41 | <% if(program.auth.google){ -%> 42 | //https://github.com/jaredhanson/passport-google 43 | google: { 44 | returnURL: host + '/auth/google/callback', 45 | realm: host 46 | }, 47 | <% } -%> 48 | <% if(program.auth.bitbucket){ -%> 49 | //https://github.com/jaredhanson/passport-bitbucket 50 | bitbucket: { 51 | consumerKey: 'your-client-id', 52 | consumerSecret: 'your-secret', 53 | callbackURL: host + '/auth/bitbucket/callback' 54 | }, 55 | <% } -%> 56 | <% if(program.auth.github){ -%> 57 | //https://github.com/jaredhanson/passport-github 58 | github: { 59 | clientID: 'your-client-id', 60 | clientSecret: 'your-secret', 61 | callbackURL: host + '/auth/github/callback' 62 | }, 63 | <% } -%> 64 | }, 65 | <% } -%> 66 | <% if(program.template==='ejs'){ -%> 67 | //https://github.com/koajs/ejs 68 | <% } else if(program.template==='swig'){ -%> 69 | //https://github.com/fundon/koa-swig 70 | <% } -%> 71 | view: { 72 | root: path.resolve(__dirname, '../views'), 73 | cache: DEBUG ? false : 'memory', 74 | locals: require('./view-locals'), 75 | filters: require('./view-filters'), 76 | <% if(program.template==='ejs'){ -%> 77 | layout: 'layouts/main', 78 | <% } -%> 79 | }, 80 | <% if(program.i18n){ -%> 81 | //https://github.com/fundon/koa-locale#usage 82 | locale: {}, 83 | //https://github.com/fundon/koa-i18n 84 | i18n: { 85 | directory: path.resolve(__dirname, '../locales'), 86 | defaultLocale: 'en', //When you pass in an array of locales the first locale is automatically set as the defaultLocale. 87 | locales: ['en'], 88 | query: false, 89 | subdomain: true, 90 | cookie: false, 91 | header: false 92 | }, 93 | <% } -%> 94 | //https://github.com/balderdashy/waterline 95 | //https://github.com/balderdashy/waterline-docs#supported-adapters 96 | database: { 97 | // Setup Adapters 98 | // Creates named adapters that have been required 99 | adapters: { 100 | 'default': require('sails-<%=program.db%>'), 101 | }, 102 | // Build Connections Config 103 | // Setup connections using the named adapter configs 104 | connections: { 105 | 'default': { 106 | adapter: 'default', 107 | <% if(['mysql','postgresql','mongodb'].indexOf(program.db)!==-1){ -%> 108 | host: 'localhost', 109 | user: 'root', 110 | password: '', 111 | database: '<%=name%>' 112 | <% } -%> 113 | } 114 | }, 115 | defaults: { 116 | migrate: 'alter' 117 | } 118 | 119 | }, 120 | <% if(program.template==='ejs'){ -%> 121 | //https://github.com/gusnips/koa-error-ejs 122 | <% } else if(program.template==='swig'){ -%> 123 | //https://github.com/koajs/error 124 | <% } -%> 125 | error: { 126 | <% if(program.template==='swig'){ -%> 127 | template: 'layouts/error', 128 | <% } else if(program.template==='ejs'){ -%> 129 | view: 'error/error', 130 | layout: 'layouts/error', 131 | custom: { 132 | 401: 'error/401', 133 | 403: 'error/403', 134 | 404: 'error/404', 135 | }, 136 | <% } -%> 137 | }, 138 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Koa MVC 2 | 3 | Opinionated and minimalist MVC generator for [koa](http://koajs.com). Inspired by [express-generator](https://github.com/expressjs/generator). 4 | 5 | ## Quick Start 6 | 7 | The quickest way to get started with a koa app is to utilize the executable `koa-mvc` to generate an application as shown below: 8 | 9 | Create the app: 10 | 11 | ```sh 12 | npm install -g koa-mvc 13 | koa-mvc /projects/foo && cd /projects/foo 14 | ``` 15 | 16 | Install dependencies: 17 | 18 | ```sh 19 | npm install 20 | ``` 21 | 22 | Rock and Roll 23 | 24 | ```sh 25 | node --harmony --harmony_proxies app 26 | ``` 27 | 28 | ## Options 29 | 30 | - `-t, --theme [name]` switch application theme to [white|green|violet|red]. Defaults to red. 31 | - `-d, --db [engine]` change database to one of [mysql|postgresql|mongo|redis|memory|disk]. Defaults to disk. 32 | - `-c, --css [engine]` add stylesheet support [less|stylus|plain]. Defaults to plain css. 33 | - `-a, --auth [type]` add [type] authentication support [facebook|google|github|bitbucket|local]. Defaults to local only. 34 | Use ":" as separator for multiples (f.x. local:facebook:google). Set to `false` to disable (like `koa-mvc ... -a false`). 35 | - `-e, --template [engine]` change template engine. Support [ejs] only for now. Defaults to ejs. 36 | - `-i, --i18n` add i18n support. Translations used by [i18n-node-2](https://github.com/jeresig/i18n-node-2) ported to [koa-i18n](https://github.com/fundon/koa-i18n) 37 | - `-S, --no-session` remove [session](https://github.com/koajs/generic-session) support. Session is enabled by default. 38 | - `-f, --force` force on non-empty directory 39 | 40 | Example using layout green, mysql as db, facebook and google authentication: 41 | 42 | ```sh 43 | koa-mvc ~/www/test-koa-mvc -t green --db mysql -a local:facebook:google 44 | cd ~/www/test-koa-mvc 45 | npm install 46 | node --harmony --harmony_proxies app 47 | ``` 48 | 49 | Go to [http://localhost:3000](http://localhost:3000) and have fun hacking 50 | 51 | 52 | ## Enough talk, show me something 53 | 54 | green with facebook auth 55 | `koa-mvc myapp -t green -a facebook:github:google`: 56 | 57 | ![koa-mvc green app print screen](https://github.com/gusnips/node-koa-mvc/raw/master/images/green-auth.jpg "koa-mvc green app print screen") 58 | ![koa-mvc green app login print screen](https://github.com/gusnips/node-koa-mvc/raw/master/images/green.jpg "koa-mvc green app login print screen") 59 | 60 | ## Structure 61 | 62 | + `configs` routes, general app stuff, authorization 63 | + - `main` application configuration 64 | + - `auth` authorization and authentication config 65 | + - `database` load models and initialize the database here 66 | + - `routes` load controllers and define the routes 67 | + `controllers` plain objects to use its methods as routes 68 | + - `site` 69 | + - `auth` authentication and authorization methods 70 | + `locales` if i18n option is set 71 | + `models` uses [Waterline ORM](https://github.com/balderdashy/waterline/) 72 | + `views` templates 73 | + - `error` error views (401,403,404,error) 74 | + - `layouts` layouts folder (main, error) 75 | + - `site` application views (index, login) 76 | + `web` public folder 77 | + - `css` 78 | + - `js` 79 | 80 | ## Uses 81 | 82 | + [koa](http://koajs.com) (obviously) 83 | + [ejs](https://github.com/koajs/ejs) EJS as view engine 84 | + [waterline](https://github.com/balderdashy/waterline) as ORM 85 | 86 | Also 87 | 88 | + [koa-bodyparser](https://github.com/koajs/body-parser) to handle post data easier 89 | + [koa-static](https://github.com/koajs/static) exposes assets (js e css) 90 | + [koa-router](https://github.com/alexmingoia/koa-router) you know, for routes 91 | 92 | Optional 93 | + [koa-generic-session](https://github.com/koajs/generic-session) if has session support 94 | + [koa-flash](https://github.com/rickharrison/koa-flash) for session flash messages 95 | + [koa-passport](https://github.com/rkusa/koa-passport) for authentication, also passport extensions for each kind of auth 96 | + [koa-locale](https://github.com/fundon/koa-locale) if option i18n is set 97 | + [koa-i18n](https://github.com/fundon/koa-i18n) if option i18n is set 98 | + [koa-less](https://github.com/chosecz/koa-less) if you choose less as css engine 99 | + [koa-stylus](https://github.com/yosssi/koa-stylus) if you choose stylus as css engine 100 | 101 | ## Roadmap 102 | 103 | + Finish authentication 104 | + add swig template engine 105 | + better i18n support (actually use it in the views to translate) 106 | + convert css to less (works if there is raw css but it's not ideal) 107 | + add tests 108 | 109 | Also 110 | 111 | + Generate controller command 112 | + Generate model command 113 | + Generate CRUD command 114 | 115 | 116 | ## Notes 117 | 118 | Koa currently requires node 0.11.x for the --harmony --harmony_proxies flags which exposes generators and proxies to your script. If you're running an earlier version of node you may install [n](https://github.com/visionmedia/n), a node version manager to quickly install 0.11.x: 119 | 120 | ``` 121 | $ npm install -g n 122 | $ n 0.11.13 123 | ``` 124 | -------------------------------------------------------------------------------- /templates/app/views/ejs/site/index.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |

<%=title%>

7 |

Wrap it up in 1 one line, if you can.

8 |

9 | Here put a little teaser so people keep interested in what you have to say
10 | What is that anyway? Are you building something cool? Tell here! 11 |

12 |

13 | <%=title%> thing is currently in active development and will be released this summer.
14 | In the meantime, please register to our waiting list to get notified first once something is launched. 15 |

16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 |

Powered by {My Actual Mailer Here}. Unsubscribe at any time.

28 |
29 |
30 |
31 |
32 |
33 | <%=title%> Screenshot 34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 |
47 |
48 |
49 |

What it's all about

50 |

51 | Now feel free to explain you product here using more lines and images like below. 52 |

53 |
54 |
55 |
56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 | Sample image 64 |
65 |
66 |

Theme Violet does look good too!

67 |

68 | What you have to ask yourself is "do I feel lucky". Well, do ya' punk? Ever notice how sometimes 69 | you come across somebody you shouldn't have f**ked with? Well, I'm that guy. Dyin' ain't much of a livin', boy. 70 |

71 |

72 | Here. Put that in your report! And "I may have found a way out of here. you want a guarantee, buy a toaster. 73 | are you feeling lucky punk dyin' ain't much of a livin', boy. what you have to ask yourself is, Do I feel lucky. 74 | Well do ya' punk? You see, in this world there's two kinds of people, my friend: those with loaded guns and those who dig. you dig. 75 |

76 |
77 |
78 | 79 |
80 |
81 | Sample image 82 |
83 |
84 |

Yes, I love cool backgrounds, like the one used here

85 |

86 | There are 2 more color themes inside this template. You'll just need to uncomment 87 | few lines in the CSS file to activate any of them. 88 |

89 |

Now, continue with our lipsum :) 90 | This is the ak-47 assault rifle, The preferred weapon of your enemy; and it makes a distinctive sound when 91 | fired at you, so remember it. When a naked man's chasing a woman through an alley with a butcher knife and a hard-on, I figure he's not out collecting for the red cross. well, do you have anything to say for yourself? 92 |

93 | 94 |
95 |
96 |
97 |
98 | 99 |
100 |
101 |
102 |
103 |

Need help?

104 |

First of all, thanks for using koa-mvc! I'm glad it could be of your use.

105 |

106 | If you'd like to report a bug or suggest an improvement, post an issue on GitHub 107 |

108 |
109 |
110 |

Got your interest?

111 |

Drop Us A Line!

112 |
113 |
114 | 115 | 116 |
117 |
118 | 119 | 120 |
121 |
122 | 123 |
124 | 125 |
126 |
127 |
128 |
129 |
130 | -------------------------------------------------------------------------------- /templates/app/views/ejs/site/login.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Join us!

6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

Already a member? Login

15 |
16 | <%% if(program.auth.local){ -%%> 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 | <%% } -%%> 33 |
34 |

Social Sign In

35 |
36 |
37 |
38 |
    39 | <%% if(program.auth.facebook){ -%%> 40 |
  • 41 | <%% } -%%> 42 | <%% if(program.auth.google){ -%%> 43 |
  • 44 | <%% } -%%> 45 | <%% if(program.auth.github){ -%%> 46 |
  • 47 | <%% } -%%> 48 | <%% if(program.auth.bitbucket){ -%%> 49 |
  • 50 | <%% } -%%> 51 |
52 |
53 |
54 |
55 |
56 |
57 |

Not a member yet? Sign Up

58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 |
75 | 76 |
77 |
78 |
79 |

By clicking on "Sign Up", you agree to the Terms of Service and the Privacy Policy.

80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | -------------------------------------------------------------------------------- /templates/app/views/ejs/layouts/main.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%=title%> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 64 | <%% if(program.session){ -%%> 65 |
66 |
67 |
68 |
style="display:none" <% } %>> 69 | x 70 | <%=flash.error %> 71 |
72 |
style="display:none" <% } %>> 73 | x 74 | <%=flash.warning %> 75 |
76 |
style="display:none" <% } %>> 77 | x 78 | <%=flash.info %> 79 |
80 |
style="display:none" <% } %>> 81 | x 82 | <%=flash.success %> 83 |
84 |
85 |
86 |
87 | <%% } -%%> 88 | 89 |
90 | <%- body %> 91 |
92 | <%% if(program.auth){ -%%> 93 | 94 | <% if(!isAuthenticated){ -%> 95 | 126 | <% } -%> 127 | <%% } -%%> 128 | 129 |

130 | Copyright © 2014, <%=title%>. 131 | Powered by: koa-mvc 132 | Design by: GetTemplate 133 |

134 |

135 | If you'd like to report a bug or suggest an improvement, post an issue on 136 | GitHub and I'll be happy to help! 137 |

138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /bin/koa-mvc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander') 4 | var mkdirp = require('mkdirp') 5 | var os = require('os') 6 | var fs = require('fs') 7 | var path = require('path') 8 | var renderer= require('ejs') 9 | 10 | var version = require('../package.json').version 11 | 12 | function authParser(values){ 13 | if(values==='false' || values==='null' || values=='0' || values==null) 14 | return false; 15 | var auths= { 16 | local: true 17 | }; 18 | values.length && values.split(':').map(function(value){ 19 | auths[value]= true; 20 | }); 21 | return auths 22 | } 23 | 24 | // CLI 25 | program 26 | .version(version) 27 | .usage('[options] [dir]') 28 | .option('-t, --theme [name]','switch application theme to [white|green|violet|red]. Defaults to red.','red') 29 | .option('-d, --db [engine]', 'change database to one of [mysql|postgresql|mongo|redis|memory|disk]. Defaults to disk.', 'disk') 30 | .option('-c, --css [engine]', 'add stylesheet support [less|stylus|plain]. Defaults to plain css.','plain') 31 | .option('-a, --auth [type]', 32 | 'add [type] authentication support [facebook|google|github|bitbucket|local]. Defaults to local only.\n'+ 33 | 'Use ":" as separator for multiples (f.x. local:facebook:google). Set to "false" (like `koa-mvc ... -a false`) to disable.', 34 | authParser, 35 | {local: true} 36 | ) 37 | .option('-e, --template [engine]', 'change template engine. Support [ejs] only for now. Defaults to ejs.','ejs') 38 | .option('-i, --i18n', 'add i18n support. Translations used by i18n-node-2 ported to koa-i18n ', Boolean, false) 39 | .option('-S, --no-session', 'remove session support. Session is enabled by default.', Boolean, true) 40 | .option('-f, --force', 'force on non-empty directory') 41 | .parse(process.argv); 42 | 43 | // Path 44 | var source_path=path.join(__dirname, '..', 'templates','app'); 45 | var destination_path = path.resolve(program.args.shift() || '.'); 46 | 47 | // App name 48 | var app_name = path.basename(destination_path); 49 | 50 | // Options 51 | //template engine 52 | if(['ejs'].indexOf(program.template)===-1) 53 | throw new Error('Invalid template engine "'+program.template+'" '); 54 | //db 55 | if(['mysql','postgresql','mongo','redis','memory','disk'].indexOf(program.db)===-1) 56 | throw new Error('Invalid db engine "'+program.db+'" '); 57 | //css engine 58 | if(['less','stylus','plain'].indexOf(program.css)===-1) 59 | throw new Error('Invalid css engine "'+program.css+'" '); 60 | 61 | // Generate application 62 | (function createApplication(path) { 63 | emptyDirectory(path, function(empty){ 64 | if (empty || program.force) { 65 | createApplicationAt(path); 66 | } else { 67 | program.confirm('destination "'+path+'" is not empty, continue? ', function(ok){ 68 | if (ok) { 69 | process.stdin.destroy(); 70 | createApplicationAt(path); 71 | } else { 72 | abort('aborting'); 73 | } 74 | }); 75 | } 76 | }); 77 | })(destination_path); 78 | 79 | /** 80 | * Create application at the given directory `path`. 81 | * 82 | * @param {String} path 83 | */ 84 | 85 | function createApplicationAt(path) { 86 | mkdirp(path, 0755, function(err){ 87 | if (err) throw err; 88 | console.log(); 89 | process.on('exit', function(){ 90 | console.log(); 91 | console.log(' install dependencies:'); 92 | console.log(' $ cd %s && npm install', path); 93 | console.log(); 94 | console.log(' run the app:'); 95 | console.log(' $ DEBUG=' + app_name + ' node --harmony --harmony_proxies app'); 96 | console.log(); 97 | }); 98 | 99 | //###config 100 | 101 | //###controllers 102 | mkdir('/controllers', function(){ 103 | copy_template('controllers/site.ejs','controllers/site.js'); 104 | if(program.auth){ 105 | render_template('controllers/auth.ejs','controllers/auth.js',{ 106 | program: program, 107 | name: app_name, 108 | }); 109 | } 110 | }); 111 | 112 | mkdir('/configs', function(){ 113 | render_template('/configs/main.ejs','/configs/main.js',{ 114 | name: app_name, 115 | program: program, 116 | key: require('crypto').randomBytes(20).toString('hex'), 117 | }); 118 | render_template('configs/database.ejs','configs/database.js'); 119 | render_template('configs/routes.ejs','configs/routes.js'); 120 | render_template('configs/view-locals.ejs','configs/view-locals.js'); 121 | render_template('configs/view-filters.ejs','configs/view-filters.js'); 122 | if(program.auth) 123 | render_template('/configs/auth.ejs','/configs/auth.js'); 124 | }); 125 | 126 | //###models 127 | mkdir('/models', function(){ 128 | render_template('models/user.ejs','models/user.js'); 129 | }); 130 | 131 | //###locales 132 | if(program.i18n){ 133 | mkdir('/locales', function(){ 134 | render_template('locales/en.ejs','locales/en.js'); 135 | }); 136 | } 137 | 138 | //###views 139 | mkdir('/views', function(){ 140 | var baseViewsPath='views/'+program.template; 141 | //changes open clone to be able to use ejs in templates already using ejs 142 | var template_options={open: '<%%', close: '%%>'}; 143 | mkdir('/views/site', function(){ 144 | render_template(baseViewsPath+'/site/index.ejs','views/site/index.html',template_options); 145 | if(program.auth) 146 | render_template(baseViewsPath+'/site/login.ejs','views/site/login.html',template_options); 147 | }); 148 | mkdir('/views/error', function(){ 149 | render_template(baseViewsPath+'/error/401.ejs', 'views/error/401.html',template_options); 150 | render_template(baseViewsPath+'/error/403.ejs', 'views/error/403.html',template_options); 151 | render_template(baseViewsPath+'/error/404.ejs', 'views/error/404.html',template_options); 152 | render_template(baseViewsPath+'/error/error.ejs', 'views/error/error.html',template_options); 153 | }); 154 | mkdir('/views/layouts', function(){ 155 | render_template(baseViewsPath+'/layouts/main.ejs', 'views/layouts/main.html',template_options); 156 | render_template(baseViewsPath+'/layouts/error.ejs', 'views/layouts/error.html',template_options); 157 | }); 158 | }); 159 | 160 | //###web 161 | mkdir('/web', function(){ 162 | mkdir('/web/js', function(){ 163 | render_template('/web/js/main.ejs', '/web/js/main.js'); 164 | }); 165 | mkdir('/web/img'); 166 | mkdir('/web/css', function(){ 167 | // CSS Engine support 168 | switch (program.css) { 169 | case 'less': 170 | //render_template('/web/css/main.less.ejs','/web/css/main.less'); 171 | //break; 172 | case 'stylus': 173 | //render_template('/web/css/main.styl.ejs','/web/css/main.styl'); 174 | //break; 175 | default: 176 | render_template('/web/css/main.css.ejs','/web/css/main.css'); 177 | } 178 | }); 179 | }); 180 | 181 | render_template('/package.ejs','/package.json',{},0755); 182 | render_template('/app.ejs', '/app.js',{},0755); 183 | 184 | }); 185 | } 186 | 187 | /** 188 | * Check if the given directory `path` is empty. 189 | * 190 | * @param {String} path 191 | * @param {Function} fn 192 | */ 193 | function copy_template(from, to) { 194 | if(!to) 195 | to=from; 196 | write(to, load_template(from)) 197 | } 198 | 199 | /** 200 | * Check if the given directory `path` is empty. 201 | * 202 | * @param {String} path 203 | * @param {Function} fn 204 | */ 205 | function render_template(from, to, options, mode){ 206 | if(!to) 207 | to=from; 208 | var default_options={program: program,name: app_name}; 209 | if(!options) 210 | options=default_options; 211 | else { 212 | for(x in default_options) 213 | options[x]=default_options[x]; 214 | } 215 | 216 | var str= renderer.render(load_template(from), options); 217 | return write(to, str, mode); 218 | } 219 | 220 | /** 221 | * Check if the given directory `path` is empty. 222 | * 223 | * @param {String} path 224 | * @param {Function} fn 225 | */ 226 | function load_template(name) { 227 | return fs.readFileSync(path.join(source_path, name), 'utf-8') 228 | } 229 | 230 | /** 231 | * echo str > path. 232 | * 233 | * @param {String} path 234 | * @param {String} str 235 | */ 236 | 237 | function write(file, str, mode, fn) { 238 | var writePath=path.join(destination_path, file); 239 | fs.writeFile(writePath, str, { mode: mode || 0666 },function(err){ 240 | if (err) throw err; 241 | console.log(' \x1b[36mcreate\x1b[0m : ' + writePath); 242 | fn && fn(); 243 | }); 244 | } 245 | 246 | /** 247 | * Mkdir -p. 248 | * 249 | * @param {String} path 250 | * @param {Function} fn 251 | */ 252 | 253 | function mkdir(name, fn) { 254 | var writePath=path.join(destination_path,name); 255 | mkdirp(writePath, 0755, function(err){ 256 | if (err) throw err; 257 | console.log(' \033[36mcreate\033[0m : ' + writePath); 258 | fn && fn(); 259 | }); 260 | } 261 | 262 | /** 263 | * 264 | * Check if the given directory `path` is empty. 265 | * @param {String} path 266 | * @param {Function} fn 267 | */ 268 | 269 | function emptyDirectory(path, fn) { 270 | fs.readdir(path, function(err, files){ 271 | if (err && 'ENOENT' != err.code) 272 | throw err; 273 | fn(!files || !files.length); 274 | }); 275 | } 276 | 277 | /** 278 | * Exit with the given `str`. 279 | * 280 | * @param {String} str 281 | */ 282 | 283 | function abort(str) { 284 | console.error(str); 285 | process.exit(1); 286 | } 287 | -------------------------------------------------------------------------------- /templates/app/web/css/main.css.ejs: -------------------------------------------------------------------------------- 1 | /********************************************************/ 2 | /* Fonts */ 3 | /********************************************************/ 4 | /*http://fonts.googleapis.com/css?family=Open+Sans:300,400,700*/ 5 | @font-face { 6 | font-family: 'Open Sans'; 7 | font-style: normal; 8 | font-weight: 300; 9 | src: local('Open Sans Light'), local('OpenSans-Light'), url(http://themes.googleusercontent.com/static/fonts/opensans/v8/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff) format('woff'); 10 | } 11 | @font-face { 12 | font-family: 'Open Sans'; 13 | font-style: normal; 14 | font-weight: 400; 15 | src: local('Open Sans'), local('OpenSans'), url(http://themes.googleusercontent.com/static/fonts/opensans/v8/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff'); 16 | } 17 | @font-face { 18 | font-family: 'Open Sans'; 19 | font-style: normal; 20 | font-weight: 700; 21 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(http://themes.googleusercontent.com/static/fonts/opensans/v8/k3k702ZOKiLJc3WVjuplzHhCUOGz7vYGh680lGh-uXM.woff) format('woff'); 22 | } 23 | /********************************************************/ 24 | /* Page background */ 25 | /********************************************************/ 26 | body, .content { 27 | background: #fff; 28 | color:#333; 29 | } 30 | 31 | /********************************************************/ 32 | /* Typography */ 33 | /********************************************************/ 34 | h1, h2, h3, h4, h5, h6 { 35 | font-family: "Open sans", Helvetica, Arial; 36 | font-weight: 300; 37 | } 38 | 39 | p, .jumbotron p { 40 | font-size: 15px; 41 | line-height: 1.6em; 42 | } 43 | .btn,.btn:visited{ 44 | border-color: #ccc; 45 | } 46 | 47 | /********************************************************/ 48 | /* Header */ 49 | /********************************************************/ 50 | 51 | 52 | .header { 53 | margin-bottom: 0; 54 | padding-bottom: 0px; 55 | } 56 | 57 | .header h1 { 58 | font-family: "Open sans", Arial; 59 | font-size: 40px; 60 | font-weight: bold; 61 | margin: 25px 0 10px 0; 62 | text-align: center; 63 | text-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); 64 | } 65 | .header h2 { 66 | font-family: Georgia; 67 | font-size: 17px; 68 | font-weight: normal; 69 | margin: 0 0 25px 0; 70 | text-align: center; 71 | text-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); 72 | } 73 | .header p { font-size: 15px; line-height: 1.2em;} 74 | .header .lead { 75 | font-family: "Open sans", Arial; 76 | line-height: 1.2em; 77 | font-size: 18px; 78 | font-weight: 300; 79 | text-align: center; 80 | text-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); 81 | } 82 | .header .small { 83 | font-size: 0.9em; 84 | line-height: 1.2em; 85 | text-align: center; 86 | text-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); 87 | } 88 | .header .text-muted { opacity: 0.5; } 89 | 90 | .header form { margin-bottom: 18px; } 91 | .header .btn-default { 92 | font-size:15px; 93 | padding: 12px 36px; 94 | } 95 | 96 | /********************************************************/ 97 | /* Illustration and its animation */ 98 | /********************************************************/ 99 | #illustration { z-index: 1; text-align: center; width:100%; margin-bottom:-130px;} 100 | #illustration img { 101 | max-width:100%; 102 | -webkit-transition: all 0.8s; 103 | -o-transition: all 0.8s; 104 | -moz-transition: all 0.8s; 105 | transition: all 0.8s; 106 | } 107 | #illustration:hover img{ margin-bottom:120px; margin-top:-100px; } 108 | 109 | 110 | 111 | /********************************************************/ 112 | /* Content */ 113 | /********************************************************/ 114 | 115 | .content { 116 | position: relative; 117 | z-index: 999; 118 | margin-bottom:40px; 119 | } 120 | 121 | .jumbotron { 122 | background:#f5f5f5; 123 | } 124 | 125 | .content .lead { font-family: Georgia; font-style: italic; color:#999; } 126 | 127 | .space-before { padding-top:20px; } 128 | .space-after { padding-bottom:20px; } 129 | 130 | .img-feature { 131 | border:15px solid #f5f5f5; 132 | margin:0 auto; 133 | -webkit-border-radius: 50% ; 134 | -moz-border-radius: 50% ; 135 | border-radius: 50%; 136 | padding:1px; 137 | -webkit-box-sizing: border-box; 138 | -moz-box-sizing: border-box; 139 | box-sizing: border-box; 140 | } 141 | 142 | /*nav*/ 143 | .navbar { 144 | background-color: rgba(0,0,0,0.3); 145 | border-color: rgba(255,255,255,0.3); 146 | padding: 0.3em 0; 147 | margin-bottom: -2px; 148 | } 149 | .navbar .navbar-brand { 150 | font-family: "Open sans", Helvetica, Arial; 151 | font-weight: bold; 152 | } 153 | .navbar ul.nav li a { 154 | padding-left: 0; 155 | padding-right: 0; 156 | margin: 0 1.5em; 157 | } 158 | .navbar .navbar-brand, .navbar ul.nav li a { 159 | color: #fff; 160 | } 161 | .navbar .navbar-brand:hover, .navbar ul.nav li a:hover { 162 | color: #e3e3e3; 163 | } 164 | .navbar ul.nav li .btn, .navbar ul.nav li button { 165 | padding: 5px 10px; 166 | margin: 0.7em 0 0 1em; 167 | } 168 | 169 | /********************************************************/ 170 | /* Forms */ 171 | /********************************************************/ 172 | 173 | .fa-at-sign:before { content: '@'; } 174 | 175 | .well h3 { 176 | padding-top: 0; 177 | margin-top: 0; 178 | margin-bottom: 20px; 179 | } 180 | 181 | .input-group-addon .fa-2x { 182 | width: 28px; 183 | min-width: 28px; 184 | display: inline-block; 185 | } 186 | 187 | .inline-block > li { 188 | position: relative; 189 | display: inline-block; 190 | margin: 0 10px 10px 0; 191 | } 192 | 193 | .input-group { 194 | margin-bottom: 20px; 195 | } 196 | 197 | /********************************************************/ 198 | /* Responsiveness */ 199 | /********************************************************/ 200 | 201 | @media(max-width: 481px){ 202 | #social-sign-in .fa-5x { 203 | font-size: 4.5em; 204 | } 205 | } 206 | 207 | @media(min-width: 482px){ 208 | .header h1 { font-size: 55px; } 209 | .header h2 { font-size: 30px; } 210 | .header p { font-size: 15px; } 211 | 212 | #illustration { margin-bottom:-215px;} 213 | #illustration:hover img{ margin-bottom:200px; margin-top:-180px; } 214 | } 215 | 216 | @media(max-width: 767px){ 217 | h1, h2, h3, h4 { text-align: center;} 218 | #social-sign-in .fa-5x { 219 | font-size: 7em; 220 | } 221 | } 222 | @media (min-width: 768px) { 223 | .header h1 { font-size: 55px; } 224 | .header h2 { font-size: 20px; } 225 | .header p { font-size: 15px; } 226 | .header .lead { font-size:22px; } 227 | 228 | #illustration { margin-bottom:-310px;} 229 | #illustration:hover img{ margin-bottom:300px; margin-top:-190px; } 230 | 231 | } 232 | @media(min-width: 992px){ 233 | .header h1 { font-size: 55px; margin-top: 25px; } 234 | .header h2 { margin-bottom: 40px;} 235 | .header p { font-size: 15px; } 236 | .space-before { padding-top:40px; } 237 | .space-after { padding-bottom:40px; } 238 | 239 | #illustration { margin-bottom:-380px;} 240 | #illustration:hover img{ margin-bottom:370px; margin-top:-260px; } 241 | } 242 | 243 | @media(min-width: 1199px){ 244 | #social-sign-in .fa-5x { 245 | font-size: 7em; 246 | } 247 | } 248 | 249 | /********************************************************/ 250 | /* Themes */ 251 | /********************************************************/ 252 | 253 | <% if(program.theme !== 'white'){ -%> 254 | .header h1{color: white;} 255 | .header h2{color: rgba(255,255,255,.9);} 256 | .header .text-muted {color:white;} 257 | .header .lead{color: rgba(255,255,255,.9);} 258 | .header .btn-default{ 259 | background: rgba(255,255,255,.06); 260 | border: 1px solid rgba(255,255,255,.8); 261 | color: rgba(255,255,255,.8); 262 | } 263 | .header .form-control { background: rgba(5,5,5,0.4); border: 0; color: white; font-size:15px;} 264 | .header .form-control::-webkit-input-placeholder { color: rgba(255,255,255,0.3); } 265 | .header .form-control:-moz-placeholder { color: rgba(255,255,255,0.3); } 266 | .header .form-control::-moz-placeholder { color: rgba(255,255,255,0.3); } 267 | .header .form-control:-ms-input-placeholder { color: rgba(255,255,255,0.3); } 268 | 269 | .btn,.btn:visited{ 270 | border-top: 1px solid rgba(255, 255, 255, 0.5); 271 | } 272 | <% } -%> 273 | 274 | <% if(program.theme === 'red'){ -%> 275 | a{ color: #CD503A; } 276 | a:hover, a:focus { color: #953726; } 277 | a:visited { color: #D87563; } 278 | .header { 279 | color: white; 280 | background: #A43222; 281 | background: -moz-linear-gradient(top, #A43222 0%, #CD503A 100%); 282 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#A43222), color-stop(100%,#CD503A)); 283 | background: -webkit-linear-gradient(top, #A43222 0%,#CD503A 100%); 284 | background: -o-linear-gradient(top, #A43222 0%,#CD503A 100%); 285 | background: -ms-linear-gradient(top, #A43222 0%,#CD503A 100%); 286 | background: linear-gradient(to bottom, #A43222 0%,#CD503A 100%); 287 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#A43222', endColorstr='#CD503A',GradientType=0 ); 288 | } 289 | 290 | .btn,.btn:visited{ 291 | color:#fff; 292 | background-color: #d9534f; 293 | border-color: #d43f3a; 294 | } 295 | .btn:hover, .btn:focus, .btn:active, .btn.active, .open .dropdown-toggle.btn,.navbar ul.nav li .btn:hover{ 296 | color: #fff; 297 | background-color: #d2322d; 298 | border-color: #ac2925; 299 | } 300 | 301 | <% } else if(program.theme === 'green'){ -%> 302 | 303 | h1, h2, h3, h4, h5, h6 { color: #00b0b0; } 304 | a{ color: #00b0b0; } 305 | a:hover, a:focus { color: #007777; } 306 | a:visited { color: #5A8B86; } 307 | 308 | .header { 309 | color: white; 310 | background: #005363; 311 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#005363), color-stop(100%,#187489)); 312 | background: -webkit-linear-gradient(top, #005363 0%,#187489 100%); 313 | background: -o-linear-gradient(top, #005363 0%,#187489 100%); 314 | background: -ms-linear-gradient(top, #005363 0%,#187489 100%); 315 | background: linear-gradient(to bottom, #005363 0%,#187489 100%); 316 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#005363', endColorstr='#187489',GradientType=0 ); 317 | } 318 | .btn,.btn:visited{ 319 | color:#fff; 320 | background-color: #187489; 321 | border-color: #005363; 322 | } 323 | .btn:hover, .btn:focus, .btn:active, .btn.active, .open .dropdown-toggle.btn,.navbar ul.nav li .btn:hover{ 324 | color: #fff; 325 | background-color: #005363; 326 | border-color: #5A8B86; 327 | } 328 | 329 | 330 | <% } else if(program.theme === 'violet'){ -%> 331 | 332 | h1, h2, h3, h4, h5, h6 { color: #5E7690; } 333 | a{ color: #5E7690; } 334 | a:hover, a:focus { color: #5D577B; } 335 | a:visited { color: #5A8B86; } 336 | 337 | .header { 338 | color: white; 339 | background: #313A5B; 340 | background: -moz-linear-gradient(top, #313A5B 0%, #5D577B 100%); 341 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#313A5B), color-stop(100%,#5D577B)); 342 | background: -webkit-linear-gradient(top, #313A5B 0%,#5D577B 100%); 343 | background: -o-linear-gradient(top, #313A5B 0%,#5D577B 100%); 344 | background: -ms-linear-gradient(top, #313A5B 0%,#5D577B 100%); 345 | background: linear-gradient(to bottom, #313A5B 0%,#5D577B 100%); 346 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#313A5B', endColorstr='#5D577B',GradientType=0 ); 347 | } 348 | .btn,.btn:visited{ 349 | color:#fff; 350 | background-color: #5D577B; 351 | border-color: #313A5B; 352 | } 353 | .btn:hover, .btn:focus, .btn:active, .btn.active, .open .dropdown-toggle.btn,.navbar ul.nav li .btn:hover{ 354 | color: #fff; 355 | background-color: #313A5B; 356 | border-color: #5A8B86; 357 | } 358 | <% } -%> 359 | /**/ 360 | --------------------------------------------------------------------------------