Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial structure

  • Loading branch information...
commit 9588236f8be501ecc4e0ccd253395271f4d3ea68 0 parents
@wick3d wick3d authored
Showing with 2,964 additions and 0 deletions.
  1. +12 −0 .gitmodules
  2. +2 −0  Capfile
  3. +24 −0 Jakefile
  4. +32 −0 README.md
  5. +210 −0 app-mongo.coffee
  6. +231 −0 app-sql.coffee
  7. +332 −0 bin/initproject.sh
  8. +1 −0  config.json
  9. +17 −0 config/app_config.yml
  10. +82 −0 config/deploy.rb
  11. +3 −0  config/deploy/production.rb
  12. +3 −0  config/deploy/staging.rb
  13. +9 −0 config/requirements-mongo.json
  14. +8 −0 config/requirements-sql.json
  15. +1 −0  database.sql
  16. +83 −0 helpers.coffee
  17. +108 −0 init_db-mongo.coffee
  18. +212 −0 init_db-sql.coffee
  19. +96 −0 lib/authorization-mongo.coffee
  20. +206 −0 lib/authorization-sql.coffee
  21. +68 −0 lib/pager.coffee
  22. +72 −0 lib/setup.coffee
  23. +221 −0 models.coffee
  24. +24 −0 package-mongo.json
  25. +23 −0 package-sql.json
  26. +8 −0 routes/admin/index.coffee
  27. +1 −0  static/WebSocketMain.swf
  28. 0  static/css/admin.css
  29. 0  static/css/layout.css
  30. 0  static/js/admin.js
  31. +1 −0  static/js/dd_belatedpng.js
  32. +1 −0  static/js/jquery-1.4.2.min.js
  33. +632 −0 static/js/jquery.form.js
  34. +50 −0 static/js/jquery.validate.min.js
  35. +1 −0  static/js/modernizr-1.5.min.js
  36. +1 −0  static/js/socket.io.min.js
  37. +26 −0 static/js/underscore.js
  38. +1 −0  support/Socket.IO
  39. +1 −0  support/Socket.IO-node
  40. +1 −0  support/dateformat
  41. +1 −0  support/html5-boilerplate
  42. +17 −0 view/404.jade
  43. +17 −0 view/500.jade
  44. +4 −0 view/_pager.jade
  45. +1 −0  view/admin/index.jade
  46. +57 −0 view/admin/layout.jade
  47. +10 −0 view/auth/login.jade
  48. +1 −0  view/index.jade
  49. +52 −0 view/layout.jade
12 .gitmodules
@@ -0,0 +1,12 @@
+[submodule "support/html5-boilerplate"]
+ path = support/html5-boilerplate
+ url = https://github.com/robrighter/html5-boilerplate.git
+[submodule "support/Socket.IO"]
+ path = support/Socket.IO
+ url = http://github.com/LearnBoost/Socket.IO.git
+[submodule "support/Socket.IO-node"]
+ path = support/Socket.IO-node
+ url = http://github.com/LearnBoost/Socket.IO-node.git
+[submodule "support/dateformat"]
+ path = support/dateformat
+ url = https://github.com/felixge/node-dateformat.git
2  Capfile
@@ -0,0 +1,2 @@
+load 'deploy' if respond_to?(:namespace)
+load 'config/deploy'
24 Jakefile
@@ -0,0 +1,24 @@
+desc('Check and install required packages');
+task('depends', [], function () {
+ var npm = require('npm'), cb_counter = 0, wait_for_all = function () {
+ if (--cb_counter === 0) complete();
+ };
+ var fs = require('fs');
+ npm.load({}, function (err) {
+ if (err) throw err;
+ npm.commands.ls(['installed'], true, function (err, packages) {
+ var file = fs.readFileSync('config/requirements.json'),
+ requirements = JSON.parse(file);
+ requirements.forEach(function (package) {
+ cb_counter += 1;
+ if (packages[package]) {
+ console.log('Package ' + package +
+ ' is already installed');
+ wait_for_all();
+ } else {
+ npm.commands.install([package], wait_for_all);
+ }
+ });
+ });
+ });
+}, true);
32 README.md
@@ -0,0 +1,32 @@
+# To create new project
+
+1. Clone as project name. (ie. "git clone git@github.com:jerryjj/infigo-node-boilerplate.git projectx")
+2. Go to project dir (ie. cd projectx)
+3. Run ./bin/initproject.sh --name=projectx --type=mysql
+3.1 --type can be "mysql","drizzle","mongo" defaults to "mysql"
+3.2 Additional arguments for initproject.sh
+* --deploy.port.staging default: 1200
+* --deploy.port.production default: 1300
+* --deploy.user default: root (ie. --deploy.user=linuxuser)
+* --deploy.host (ie. --deploy.host=projectx.com)
+* --git.remote (ie. --git.remote=git@github.com:jerryjj/projectx.git)
+
+# Deploy to server
+
+## Requirements
+
+### Locally
+- capistrano
+
+### Remotely
+- node
+- npm
+- jake
+
+If deploy.* and git.remote options were configured when initproject.sh was run, (or configured manually to config/deploy.rb)
+One can run
+1. cap staging deploy:setup
+2. cap staging deploy
+OR
+1. cap production deploy:setup
+2. cap production deploy
210 app-mongo.coffee
@@ -0,0 +1,210 @@
+###
+Setup Dependencies
+###
+require.paths.unshift __dirname
+s = require __dirname + "/lib/setup"
+s.ext __dirname + "/lib"
+s.ext __dirname + "/support"
+
+sys = require 'sys'
+connect = require 'connect'
+express = require 'express'
+CoffeeScript = require 'coffee-script'
+mongoose = require 'mongoose'
+mongoStore = require 'connect-mongodb'
+funk = require('funk')()
+models = require './models'
+dateFormat = require 'dateformat'
+
+port = process.env.PORT || 8081
+
+app = module.exports = express.createServer()
+
+app.helpers(require('./helpers').helpers(app, {}))
+app.dynamicHelpers(require('./helpers').dynamicHelpers)
+
+tmpcfg = require('yaml').eval(
+ require('fs')
+ .readFileSync('config/app_config.yml')
+ .toString('utf-8')
+)
+global.config = CoffeeScript.helpers.merge tmpcfg['common'], tmpcfg[app.settings.env]
+
+app.set 'db_type', global.config.db_type
+app.set 'db_user', global.config.db_user
+app.set 'db_pass', global.config.db_pass
+app.set 'db_name', global.config.db_name
+
+app.configure 'development', () ->
+ console.log "configure development"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'staging', () ->
+ console.log "configure staging"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'test', () ->
+ console.log "configure test"
+
+app.configure 'production', () ->
+ console.log "configure production"
+ app.use express.errorHandler()
+
+app.configure () ->
+ app.set 'views', __dirname + '/views'
+ app.set 'view engine', 'jade'
+ app.set 'view options', layout: 'layout.jade'
+ app.use express.favicon()
+ app.use connect.bodyParser()
+ app.use express.cookieParser()
+ app.use express.session(cookie: {maxAge: 2 * (24 * 60 * 60 * 1000)}, store: mongoStore(url: app.set('db-uri'), reapInterval: 60 * 1000), secret: global.config.cookie_secret)
+ app.use express.logger(format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time ms')
+ app.use express.methodOverride()
+ app.use connect.static(__dirname + '/static')
+ app.use app.router
+
+###
+Setup models
+###
+
+User = Group = Role = LoginToken = db = null
+
+models.defineModels mongoose, funk.add () ->
+ db = mongoose.connect app.set('db-uri')
+
+ app.User = User = mongoose.model 'User'
+ app.Group = Group = mongoose.model 'Group'
+ app.Role = Role = mongoose.model 'Role'
+ app.LoginToken = LoginToken = mongoose.model 'LoginToken'
+
+###
+setup the errors
+###
+
+app.error (err, req, res, next) ->
+ if err instanceof NotFound
+ res.render '404.jade',
+ status: 404
+ layout: false
+ locals:
+ title : '404 - Not Found'
+ else
+ next err
+
+if app.settings.env == 'production'
+ app.error (err, req, res) ->
+ res.render '500.jade',
+ status: 500
+ layout: false
+ locals:
+ error: err
+
+###
+Routes
+###
+
+app.authLocals = (req) ->
+ return {
+ hasUser: !(req.currentUser is null)
+ user: req.currentUser || {}
+ }
+
+app.commonLocals = (req, ext) ->
+ if not ext
+ ext = {}
+
+ common =
+ locals:
+ title : 'Project'
+ analyticssiteid: ''
+ auth: app.authLocals(req)
+ inAdmin: false
+
+ return CoffeeScript.helpers.merge common, ext
+
+app.adminCommonLocals = (req, ext) ->
+ if not ext
+ ext = {}
+
+ common = app.commonLocals(req,
+ layout: 'admin/layout.jade'
+ locals:
+ title : 'Project :: Admin'
+ inAdmin: true
+ )
+
+ app.commonLocals req, CoffeeScript.helpers.merge common, ext
+
+# Auth routes
+
+Auth = require 'authorization'
+
+## Login route
+app.get '/auth/login', (req, res) ->
+ res.render 'auth/login',
+ app.commonLocals(req,
+ locals:
+ auth:
+ hasUser: false
+ )
+
+## Process Login route
+app.post '/auth/login', (req, res) ->
+ url = require('url').parse req.url, true
+
+ redirect_to = '/'
+ if url.query && url.query.redirect_to
+ redirect_to = url.query.redirect_to
+
+ User.findOne username: req.body.user.username, (err, user) ->
+ if user && user.authenticate req.body.user.password
+ req.session.user_id = user.id
+ if req.body.remember_me
+ loginToken = new LoginToken username: user.username
+ loginToken.save () ->
+ res.cookie 'logintoken', loginToken.cookieValue, expires: new Date(Date.now() + 2 * 604800000), path: '/'
+ res.redirect redirect_to
+ else
+ res.redirect redirect_to
+ else
+ req.flash 'error', 'Incorrect credentials'
+ res.redirect '/auth/login?redirect_to=' + redirect_to
+
+## Logout route
+app.get '/auth/logout', Auth.requireLogin, (req, res) ->
+ if req.session
+ req.session.destroy () -> return
+ res.redirect '/'
+
+# Index route
+app.get '/', (req, res) ->
+ res.render 'index',
+ app.commonLocals req
+
+# Load additional routes here
+
+require('./routes/admin/index')(app)
+
+# A Route for Creating a 500 Error (Useful to keep around)
+app.get '/500', (req, res) ->
+ throw new Error 'An expected error'
+
+# The 404 Route (ALWAYS Keep this as the last route)
+app.get '/*', (req, res) ->
+ throw new NotFound
+
+NotFound = (msg) ->
+ this.name = 'NotFound'
+ Error.call this, msg
+ Error.captureStackTrace this, arguments.callee
+sys.inherits NotFound, Error
+
+if !module.parent
+ funk.parallel () ->
+ dialect.sync {interval: 60 * 1000}, () ->
+ app.listen port
+ console.log 'Express server listening on port %d, environment: %s', app.address().port, app.settings.env
+ console.log 'Using connect %s, Express %s', connect.version, express.version
+ console.log 'Http Listening on http://0.0.0.0:' + port
+ return
+ return
231 app-sql.coffee
@@ -0,0 +1,231 @@
+###
+Setup Dependencies
+###
+require.paths.unshift __dirname
+s = require __dirname + "/lib/setup"
+s.ext __dirname + "/lib"
+s.ext __dirname + "/support"
+
+sys = require 'sys'
+connect = require 'connect'
+express = require 'express'
+CoffeeScript = require 'coffee-script'
+funk = require('funk')()
+
+dateFormat = require 'dateformat'
+
+port = process.env.PORT || 8081
+
+app = module.exports = express.createServer()
+
+app.helpers(require('./helpers').helpers(app, {}))
+app.dynamicHelpers(require('./helpers').dynamicHelpers)
+
+tmpcfg = require('yaml').eval(
+ require('fs')
+ .readFileSync('config/app_config.yml')
+ .toString('utf-8')
+)
+global.config = CoffeeScript.helpers.merge tmpcfg['common'], tmpcfg[app.settings.env]
+
+app.set 'db_type', global.config.db_type
+app.set 'db_user', global.config.db_user
+app.set 'db_pass', global.config.db_pass
+app.set 'db_name', global.config.db_name
+
+app.configure 'development', () ->
+ console.log "configure development"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'staging', () ->
+ console.log "configure staging"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'test', () ->
+ console.log "configure test"
+
+app.configure 'production', () ->
+ console.log "configure production"
+ app.use express.errorHandler()
+
+db = require('db-mysql')[app.set('db_type')].Database
+app.sqlClient = new db(
+ hostname: app.set 'db_host'
+ port: app.set 'db_port'
+ database: app.set 'db_name'
+ user: app.set 'db_user'
+ password: app.set 'db_pass'
+).on('error', (error) ->
+ console.log 'ERROR connecting to SQL server: ' + error
+).on('ready', (server) ->
+ console.log 'MySQL connected to ' + server.hostname + ' (' + server.version + ')'
+)
+app.sqlClient.connect funk.add (err) ->
+ if err
+ return
+
+app.configure () ->
+ app.set 'views', __dirname + '/views'
+ app.set 'view engine', 'jade'
+ app.set 'view options', layout: 'layout.jade'
+ app.use express.favicon()
+ app.use connect.bodyParser()
+ app.use express.cookieParser()
+ app.use express.session(cookie: {maxAge: 2 * (24 * 60 * 60 * 1000)}, secret: global.config.cookie_secret)
+ app.use express.logger(format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time ms')
+ app.use express.methodOverride()
+ app.use connect.static(__dirname + '/static')
+ app.use app.router
+
+###
+setup the errors
+###
+
+app.error (err, req, res, next) ->
+ if err instanceof NotFound
+ res.render '404.jade',
+ status: 404
+ layout: false
+ locals:
+ title : '404 - Not Found'
+ else
+ next err
+
+if app.settings.env == 'production'
+ app.error (err, req, res) ->
+ res.render '500.jade',
+ status: 500
+ layout: false
+ locals:
+ error: err
+
+###
+Routes
+###
+
+###
+ Authentication related
+###
+
+app.authLocals = (req) ->
+ return {
+ hasUser: !(req.currentUser is null)
+ user: req.currentUser || {}
+ }
+
+app.commonLocals = (req, ext) ->
+ if not ext
+ ext = {}
+
+ common =
+ locals:
+ title : 'Project'
+ analyticssiteid: ''
+ auth: app.authLocals(req)
+ inAdmin: false
+
+ return CoffeeScript.helpers.merge common, ext
+
+app.adminCommonLocals = (req, ext) ->
+ if not ext
+ ext = {}
+
+ common = app.commonLocals(req,
+ layout: 'admin/layout.jade'
+ locals:
+ title : 'Project :: Admin'
+ inAdmin: true
+ )
+
+ app.commonLocals req, CoffeeScript.helpers.merge common, ext
+
+# Auth routes
+
+Auth = require 'authorization'
+
+## Login route
+app.get '/auth/login', (req, res) ->
+ res.render 'auth/login',
+ app.commonLocals(req,
+ locals:
+ auth:
+ hasUser: false
+ )
+
+## Process Login route
+app.post '/auth/login', (req, res) ->
+ url = require('url').parse req.url, true
+
+ redirect_to = '/'
+ if url.query && url.query.redirect_to
+ redirect_to = url.query.redirect_to
+
+ req.app.sqlClient.query().
+ select('*').
+ from('users').
+ where('username = ?', [req.body.user.username]).
+ execute (err, rows, cols) ->
+ if !err && rows.length >= 1
+ pwd_hash = Auth.createHashedPassword(rows[0].salt, req.body.user.password)
+
+ req.app.sqlClient.query().
+ select('id').
+ from('users').
+ where('username = ?', [req.body.user.username]).
+ and('hashed_password = ?', [pwd_hash]).
+ execute (err, prows, pcols) ->
+ if err || prows.length < 1
+ req.flash 'error', 'Incorrect credentials'
+ res.redirect '/auth/login?redirect_to=' + redirect_to
+ else
+ req.session.user_id = prows[0].id
+ app.sqlClient.query().update('users').
+ set({'last_login': dateFormat(new Date, 'yyyy-mm-dd HH:MM:ss')}).
+ where('id = ?', [prows[0].id]).
+ execute (err, rows, cols) ->
+ if err
+ console.log 'error updating last login'
+ console.log err
+ res.redirect redirect_to
+ else
+ req.flash 'error', 'Incorrect credentials'
+ res.redirect '/auth/login?redirect_to=' + redirect_to
+
+## Logout route
+app.get '/auth/logout', Auth.requireLogin, (req, res) ->
+ if req.session
+ req.session.destroy () -> return
+ res.redirect '/'
+
+# Index route
+app.get '/', (req, res) ->
+ res.render 'index',
+ app.commonLocals req
+
+# Load additional routes here
+
+require('./routes/admin/index')(app)
+
+# A Route for Creating a 500 Error (Useful to keep around)
+app.get '/500', (req, res) ->
+ throw new Error 'An expected error'
+
+# The 404 Route (ALWAYS Keep this as the last route)
+app.get '/*', (req, res) ->
+ throw new NotFound
+
+NotFound = (msg) ->
+ this.name = 'NotFound'
+ Error.call this, msg
+ Error.captureStackTrace this, arguments.callee
+sys.inherits NotFound, Error
+
+if !module.parent
+ funk.parallel () ->
+ dialect.sync {interval: 60 * 1000}, () ->
+ app.listen port
+ console.log 'Express server listening on port %d, environment: %s', app.address().port, app.settings.env
+ console.log 'Using connect %s, Express %s', connect.version, express.version
+ console.log 'Http Listening on http://0.0.0.0:' + port
+ return
+ return
332 bin/initproject.sh
@@ -0,0 +1,332 @@
+#!/bin/bash
+
+# === FUNCTIONS ===
+# --- print and exit functions ---
+function print_info()
+{
+ echo " -- $1"
+}
+
+function print_warn()
+{
+ echo " ** $1. You may want to look into this, continuing..."
+}
+
+function force_exit()
+{
+ echo " !! $2, exiting..."
+ show_help
+ exit $1
+}
+
+function safe_exit()
+{
+ echo " -- Safely exiting..."
+ cleanup
+ exit 0
+}
+
+function show_help()
+{
+ #echo "Usage: $0 --name=project_name --type=(mysql|drizzle|mongo) [--deploy.port.staging=] [--deploy.port.production=] [--deploy.user=] [--deploy.host=] [--git.remote=]"
+ cat << EOF
+ usage: $0 -n project_name [additional_options]
+
+ This script prepares project boilerplate.
+
+ OPTIONS
+ -n --name Name of the project (lowercase)
+ ADDITIONAL OPTIONS:
+ -h --help Show this message
+ -t --type (mysql|drizzle|mongo) Projects storage type (DEFAULT: mysql)
+ --deploy.port.staging Deploy port (staging)
+ --deploy.port.production Deploy port (production)
+ --deploy.user Deploy as user
+ --deploy.host Deploy host
+ --git.remote Git remote url
+
+EOF
+}
+
+# --- helper functions ---
+function cleanup()
+{
+ print_info "Cleaning up"
+}
+
+function create_dir()
+{
+ if [ ! -d $1 ]; then
+ print_info "Creating directory: $1"
+
+ mkdir -p $1 # if $1 doesn't exist, create it
+ if [ $? -ne 0 ]; then
+ force_exit 1 "Could not create $1"
+ fi
+ fi
+}
+
+PROJECT_NAME=""
+PROJECT_TYPE="mysql"
+DEPLOY_PORT_STAGING=1200
+DEPLOY_PORT_PRODUCTION=1300
+DEPLOY_USER="root"
+DEPLOY_HOST=""
+GIT_REMOTE=""
+
+# translate long options to short
+for arg
+do
+ delim=""
+ case "$arg" in
+ --help) args="${args}-h ";;
+ --name) args="${args}-n ";;
+ --type) args="${args}-t ";;
+ --deploy.port.staging) args="${args}-1 ";;
+ --deploy.port.production) args="${args}-2 ";;
+ --deploy.user) args="${args}-3 ";;
+ --deploy.host) args="${args}-4 ";;
+ --git.remote) args="${args}-5 ";;
+ # pass through anything else
+ *) [[ "${arg:0:1}" == "-" ]] || delim="\""
+ args="${args}${delim}${arg}${delim} ";;
+ esac
+done
+eval set -- $args
+while getopts "hn:t:1:2:3:4:5:" OPTION
+do
+ case $OPTION in
+ h)
+ show_help
+ exit 1
+ ;;
+ n)
+ PROJECT_NAME=$OPTARG
+ ;;
+ t)
+ PROJECT_TYPE=$OPTARG
+ ;;
+ 1)
+ DEPLOY_PORT_STAGING=$OPTARG
+ ;;
+ 2)
+ DEPLOY_PORT_PRODUCTION=$OPTARG
+ ;;
+ 3)
+ DEPLOY_USER=$OPTARG
+ ;;
+ 4)
+ DEPLOY_HOST=$OPTARG
+ ;;
+ 5)
+ GIT_REMOTE=$OPTARG
+ ;;
+ \?)
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
+if [[ -z $PROJECT_NAME ]] || [[ -z $PROJECT_TYPE ]]; then
+ force_exit 1 "Improper number of parameters ($#)"
+fi
+
+if [ $PROJECT_NAME == "" ]; then
+ force_exit 1 "Project name required!"
+fi
+
+if [[ $PROJECT_TYPE != "mysql" ]] && [[ $PROJECT_TYPE != "drizzle" ]] && [[ $PROJECT_TYPE != "mongo" ]]; then
+ force_exit 1 "unkown type $PROJECT_TYPE"
+fi
+
+if [[ $DEPLOY_HOST != "" ]] && [[ $GIT_REMOTE == "" ]]; then
+ force_exit 1 "git.remote cannot be empty if deploy host is set"
+fi
+
+echo ""
+print_info "Preparing project $PROJECT_NAME with type $PROJECT_TYPE"
+if [[ $DEPLOY_HOST != "" ]]; then
+ print_info "Deploying config"
+ print_info " Host: $DEPLOY_HOST"
+ print_info " User: $DEPLOY_USER"
+ print_info " Staging port: $DEPLOY_PORT_STAGING"
+ print_info " Production port: $DEPLOY_PORT_PRODUCTION"
+ print_info " Remote Git url: $GIT_REMOTE"
+fi
+
+# === LOGIC ===
+
+function init_submodules()
+{
+ print_info "Initializing git submodules"
+
+ #git submodule update --init --recursive
+ git submodule init && git submodule update
+}
+
+function prepare_statics()
+{
+ print_info "Preparing static files"
+
+ create_dir "$PWD/static/img"
+ create_dir "$PWD/static/swf"
+
+ cp "$PWD/support/html5-boilerplate/js/plugins.js" "$PWD/static/js/plugins.js"
+ cp "$PWD/support/html5-boilerplate/js/script.js" "$PWD/static/js/application.js"
+ cp "$PWD/support/html5-boilerplate/css/handheld.css" "$PWD/static/css/handheld.css"
+ cp "$PWD/support/html5-boilerplate/css/style.css" "$PWD/static/css/style.css"
+ cp "$PWD/support/html5-boilerplate/robots.txt" "$PWD/static/robots.txt"
+}
+
+function prepare_deploy_config()
+{
+ print_info "Preparing deploy config"
+
+ cp "$PWD/config/deploy.rb" "$PWD/config/deploy.rb-tmp"
+ DF="$PWD/config/deploy.rb"
+ GIT_REMOTE=$(echo $GIT_REMOTE | sed -e 's/\//\\\//g')
+
+ cat "$DF" | sed -e "s/\[PROJECT\]/$PROJECT_NAME/g" -e "s/\[PROJECT_HOST\]/'$DEPLOY_HOST'/g" -e 's/\[PROJECT_GIT\]/'"$GIT_REMOTE"'/g' -e 's/\[PROJECT_HOST\]/'"$DEPLOY_HOST"'/g' -e "s/\[DEPLOY_USER\]/$DEPLOY_USER/g" > "$DF-tmp"
+ mv "$PWD/config/deploy.rb-tmp" "$PWD/config/deploy.rb"
+
+ # config/deploy/production.rb
+ cp "$PWD/config/deploy/production.rb" "$PWD/config/deploy/production.rb-tmp"
+ DF="$PWD/config/deploy/production.rb"
+
+ cat "$DF" | sed -e "s/\[PRODUCTION_PORT\]/$DEPLOY_PORT_PRODUCTION/g" > "$DF-tmp"
+ mv "$PWD/config/deploy/production.rb-tmp" "$PWD/config/deploy/production.rb.rb"
+
+ # config/deploy/staging.rb
+ cp "$PWD/config/deploy/staging.rb" "$PWD/config/deploy/staging.rb-tmp"
+ DF="$PWD/config/deploy/staging.rb"
+
+ cat "$DF" | sed -e "s/\[STAGING_PORT\]/$DEPLOY_PORT_STAGING/g" > "$DF-tmp"
+ mv "$PWD/config/deploy/staging.rb-tmp" "$PWD/config/deploy/staging.rb.rb"
+}
+
+function prepare_project_config()
+{
+ print_info "Preparing project config"
+
+ cp "$PWD/config/app_config.yml" "$PWD/config/app_config.yml-tmp"
+ DF="$PWD/config/app_config.yml"
+
+ cat "$DF" | sed -e "s/\[DB_TYPE\]/$PROJECT_TYPE/g" -e "s/\[PROJECT\]/'$PROJECT_NAME'/g" > "$DF-tmp"
+ rm "$DF"
+ mv "$PWD/config/app_config.yml-tmp" "$PWD/config/app_config.yml"
+
+ if [[ $PROJECT_TYPE == "mysql" ]] || [[ $PROJECT_TYPE == "drizzle" ]]; then
+ cp "$PWD/package-sql.json" "$PWD/package-sql.json-tmp"
+ DF="$PWD/package-sql.json"
+
+ cat "$DF" | sed -e "s/\[DB_TYPE\]/$PROJECT_TYPE/g" -e "s/\[PROJECT\]/'$PROJECT_NAME'/g" > "$DF-tmp"
+ mv "$PWD/package-sql.json-tmp" "$PWD/package.json"
+ rm "$DF"
+ rm "$PWD/package-mongo.json"
+
+ cp "$PWD/config/requirements-sql.json" "$PWD/config/requirements-sql.json-tmp"
+ DF="$PWD/config/requirements-sql.json"
+
+ cat "$DF" | sed -e "s/\[DB_TYPE\]/$PROJECT_TYPE/g" -e "s/\[PROJECT\]/'$PROJECT_NAME'/g" > "$DF-tmp"
+ mv "$PWD/config/requirements-sql.json-tmp" "$PWD/config/requirements.json"
+ rm "$DF"
+ rm "$PWD/config/requirements-mongo.json"
+ else
+ cp "$PWD/package-mongo.json" "$PWD/package-mongo.json-tmp"
+ DF="$PWD/package-mongo.json"
+
+ cat "$DF" | sed -e "s/\[DB_TYPE\]/$PROJECT_TYPE/g" -e "s/\[PROJECT\]/'$PROJECT_NAME'/g" > "$DF-tmp"
+ mv "$PWD/package-mongo.json-tmp" "$PWD/package.json"
+ rm "$DF"
+ rm "$PWD/package-sql.json"
+
+ mv "$PWD/config/requirements-mongo.json" "$PWD/config/requirements.json"
+ rm "$PWD/config/requirements-sql.json"
+ fi
+}
+
+function prepare_type_sql_common()
+{
+ mv "$PWD/app-sql.coffee" "$PWD/app.coffee"
+ mv "$PWD/init_db-sql.coffee" "$PWD/init_db.coffee"
+ mv "$PWD/lib/authorization-sql.coffee" "$PWD/lib/authorization.coffee"
+
+ rm "$PWD/app-mongo.coffee"
+ rm "$PWD/init_db-mongo.coffee"
+ rm "$PWD/models.coffee"
+ rm "$PWD/lib/authorization-mongo.coffee"
+}
+function prepare_type_mysql()
+{
+ print_info "Preparing MySQL type"
+ prepare_type_sql_common
+}
+function prepare_type_drizzle()
+{
+ print_info "Preparing Drizzle type"
+ prepare_type_sql_common
+}
+function prepare_type_mongo()
+{
+ print_info "Preparing MongoDB type"
+ mv "$PWD/app-mongo.coffee" "$PWD/app.coffee"
+ mv "$PWD/init_db-mongo.coffee" "$PWD/init_db.coffee"
+ mv "$PWD/lib/authorization-mongo.coffee" "$PWD/lib/authorization.coffee"
+
+ rm "$PWD/app-sql.coffee"
+ rm "$PWD/init_db-sql.coffee"
+ rm "$PWD/lib/authorization-sql.coffee"
+ rm "$PWD/database.sql"
+}
+
+function cleanup_template_git()
+{
+ print_info "Cleaning template project"
+ rm -fR "$PWD/.git"
+}
+
+function initialize_project_git()
+{
+ print_info "Initializing new Git project"
+ git init
+ git add *
+ git commit -a -m "Initial commit"
+
+ if [[ $GIT_REMOTE != "" ]]; then
+ git remote add origin $GIT_REMOTE
+ fi
+}
+
+function initialize_project_storage()
+{
+ print_info "Initializing project storage"
+
+ if [[ $PROJECT_TYPE == "mysql" ]] || [[ $PROJECT_TYPE == "drizzle" ]]; then
+ print_info "Create configured database and users. Then run 'coffee init_db.coffee' in project dir ($PWD)."
+ else
+ coffee "$PWD/init_db.coffee"
+ fi
+}
+
+
+init_submodules
+prepare_statics
+
+prepare_deploy_config
+prepare_project_config
+
+if [ "$PROJECT_TYPE" == "mysql" ]; then
+ prepare_type_mysql
+elif [ "$PROJECT_TYPE" == "drizzle" ]; then
+ prepare_type_drizzle
+elif [ "$PROJECT_TYPE" == "mongo" ]; then
+ prepare_type_mongo
+fi
+
+initialize_project_storage
+
+cleanup_template_git
+initialize_project_git
+
+safe_exit
1  config.json
@@ -0,0 +1 @@
+{ "version": "v0.0.1" }
17 config/app_config.yml
@@ -0,0 +1,17 @@
+common:
+ cookie_secret: 73f4e2645t2dssd51b85d633bcb4307
+ db_type: 'DB_TYPE'
+ db_host: 'lcoalhost'
+ db_port: 3306
+ db_user: 'PROJECT'
+ db_pass: 'PROJECT'
+production:
+ db_name: 'PROJECT'
+staging:
+ db_user: 'PROJECT'
+ db_pass: 'PROJECT'
+ db_name: 'PROJECT_staging'
+test:
+ db_name: 'PROJECT_test'
+development:
+ db_name: 'PROJECT_dev'
82 config/deploy.rb
@@ -0,0 +1,82 @@
+set :stages, %w[staging production]
+set :default_stage, 'staging'
+require 'capistrano/ext/multistage'
+
+set :node_env, "staging"
+set :application, "[PROJECT]"
+set :node_file, "app.coffee"
+set :host, "[PROJECT_HOST]"
+set :repository, "[PROJECT_GIT]"
+set :user, "[DEPLOY_USER]"
+set :admin_runner, '[DEPLOY_USER]'
+
+set :scm, :git
+set :deploy_via, :remote_cache
+role :app, host
+set :deploy_to, "/var/www/apps/#{application}/#{node_env}"
+set :use_sudo, true
+default_run_options[:pty] = true
+
+namespace :deploy do
+ task :start, :roles => :app, :except => { :no_release => true } do
+ sudo "start #{application}_#{node_env}"
+ end
+
+ task :stop, :roles => :app, :except => { :no_release => true } do
+ sudo "stop #{application}_#{node_env}"
+ end
+
+ task :restart, :roles => :app, :except => { :no_release => true } do
+ sudo "restart #{application}_#{node_env} || sudo start #{application}_#{node_env}"
+ end
+
+ task :create_deploy_to_with_sudo, :roles => :app do
+ sudo "mkdir -p #{deploy_to}"
+ sudo "chown #{admin_runner}:#{admin_runner} #{deploy_to}"
+ end
+
+ desc "Update submodules"
+ task :update_submodules, :roles => :app do
+ run "cd #{release_path}; git submodule init && git submodule update"
+ end
+
+ desc "Check required packages and install if packages are not installed"
+ task :check_packages, :roles => :app do
+ run "cd #{release_path}"
+ sudo "jake depends"
+ end
+
+ desc "Set default database contents"
+ task :fill_database, :roles => :app do
+ run "NODE_ENV=#{node_env} /usr/local/bin/coffee #{release_path}/init_db.coffee"
+ end
+
+ task :write_upstart_script, :roles => :app do
+ upstart_script = <<-UPSTART
+description "#{application}"
+
+start on startup
+stop on shutdown
+
+script
+# $HOME is needed. Without it, we ran into problems
+export HOME="/home/#{admin_runner}"
+export NODE_ENV="#{node_env}"
+
+cd #{current_path}
+exec sudo -u #{admin_runner} sh -c "NODE_ENV=#{node_env} PORT=#{application_port} /usr/local/bin/coffee #{current_path}/#{node_file} >> #{shared_path}/log/#{node_env}.log 2>&1"
+end script
+respawn
+UPSTART
+ put upstart_script, "/tmp/#{application}_upstart.conf"
+ sudo "mv /tmp/#{application}_upstart.conf /etc/init/#{application}_#{node_env}.conf"
+ end
+
+ task :finalize_update, :roles => :app do
+ end
+
+end
+
+before 'deploy:setup', 'deploy:create_deploy_to_with_sudo'
+after 'deploy:setup', 'deploy:write_upstart_script'
+after "deploy:finalize_update", "deploy:update_submodules", "deploy:check_packages", "deploy:fill_database"
3  config/deploy/production.rb
@@ -0,0 +1,3 @@
+set :node_env, "production"
+set :branch, "production"
+set :application_port, "[PRODUCTION_PORT]"
3  config/deploy/staging.rb
@@ -0,0 +1,3 @@
+set :node_env, "staging"
+set :branch, "master"
+set :application_port, "[STAGING_PORT]"
9 config/requirements-mongo.json
@@ -0,0 +1,9 @@
+[ "express@2.3.2"
+, "connect@1.2.1"
+, "coffee-script@1.0.1"
+, "yaml@0.1.1"
+, "jade@0.11.0"
+, "funk@1.0.1"
+, "mongoose@1.1.4"
+, "connect-mongodb@0.2.1"
+]
8 config/requirements-sql.json
@@ -0,0 +1,8 @@
+[ "express@2.3.2"
+, "connect@1.2.1"
+, "coffee-script@1.0.1"
+, "yaml@0.1.1"
+, "jade@0.11.0"
+, "funk@1.0.1"
+, "db-[DB_TYPE]@0.6.5"
+]
1  database.sql
@@ -0,0 +1 @@
+### Database alters here ###
83 helpers.coffee
@@ -0,0 +1,83 @@
+class FlashMessage
+ constructor: (@type, @messages) ->
+ if typeof @messages == 'string'
+ @messages = [@messages]
+
+ icon: () ->
+ switch @type
+ when 'info' then 'ui-icon-info'
+ when 'error' then 'ui-icon-alert'
+
+ stateClass: () ->
+ switch @type
+ when 'info' then 'ui-state-highlight'
+ when 'error' then 'ui-state-error'
+
+ toHTML: () ->
+ '<div class="ui-widget flash">' +
+ '<div class="' + @stateClass() + ' ui-corner-all">' +
+ '<p><span class="ui-icon ' + @icon() + '"></span>' + @messages.join(', ') + '</p>' +
+ '</div></div>'
+
+exports.dynamicHelpers =
+ flashMessages: (req, res) ->
+ html = ''
+ ['error', 'info'].forEach (type) ->
+ messages = req.flash type
+ if messages.length > 0
+ html += new FlashMessage(type, messages).toHTML()
+ return html
+
+exports.helpers = (app, options) ->
+ return {
+ appName: 'Valtra'
+ version: '0.0.1'
+
+ nameAndVersion: (name, version) ->
+ name + ' v' + version
+
+ truncate: (str, num) ->
+ limit = num || 20
+
+ if str && str.length > limit
+ return str.slice(0, limit) + '...'
+ else
+ return str
+ dateFormat: require('dateformat')
+ }
+
+exports.range = (low, high, step) ->
+ matrix = []
+ walker = 1 unless step
+ chars = false
+ inival = endval = plus = null
+
+ if !isNaN(low) && !isNaN(high)
+ inival = low
+ endval = high
+ else if isNaN(low) && isNaN(high)
+ chars = true
+ inival = low.charCodeAt(0)
+ endval = high.charCodeAt(0)
+ else
+ inival = (isNaN(low) ? 0 : low)
+ endval = (isNaN(high) ? 0 : high)
+
+ plus = !(inival > endval)
+
+ if plus
+ tmp = while inival <= endval
+ if chars
+ matrix.push(String.fromCharCode(inival))
+ else
+ matrix.push(inival)
+ inival += walker
+ else
+ tmp = while inival >= endval
+ if chars
+ matrix.push(String.fromCharCode(inival))
+ else
+ matrix.push(inival)
+ inival -= walker
+
+ return matrix
108 init_db-mongo.coffee
@@ -0,0 +1,108 @@
+###
+Setup Dependencies
+###
+require.paths.unshift __dirname
+s = require __dirname + "/lib/setup"
+s.ext __dirname + "/lib"
+s.ext __dirname + "/support"
+
+mongoose = require 'mongoose'
+express = require 'express'
+sys = require 'sys'
+models = require './models'
+CoffeeScript = require 'coffee-script'
+
+app = module.exports = express.createServer()
+
+tmpcfg = require('yaml').eval(
+ require('fs')
+ .readFileSync('config/app_config.yml')
+ .toString('utf-8')
+)
+global.config = CoffeeScript.helpers.merge tmpcfg['common'], tmpcfg[app.settings.env]
+
+app.set 'db_type', global.config.db_type
+app.set 'db_user', global.config.db_user
+app.set 'db_pass', global.config.db_pass
+app.set 'db_name', global.config.db_name
+
+app.configure 'development', () ->
+ console.log "configure development"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'staging', () ->
+ console.log "configure staging"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'test', () ->
+ console.log "configure test"
+
+app.configure 'production', () ->
+ console.log "configure production"
+ app.use express.errorHandler()
+
+User = Group = Role = LoginToken = db = null
+
+models.defineModels mongoose, () ->
+ db = mongoose.connect app.set('db-uri')
+
+ app.User = User = mongoose.model 'User'
+ app.Group = Group = mongoose.model 'Group'
+ app.Role = Role = mongoose.model 'Role'
+ app.LoginToken = LoginToken = mongoose.model 'LoginToken'
+
+# Create default user
+console.log "Creating default user 'admin' with password admin"
+u = new User {
+ username: 'admin'
+ password: 'admin'
+ email: 'admin@project.com'
+ 'name.first': 'Project'
+ 'name.last': 'Admin'
+}
+u.save (err, usr) ->
+ if err
+ console.log 'User save error'
+ console.log err
+ process.exit 0
+
+ console.log "User created"
+ console.log "Creating default group and roles"
+
+ gu = new GroupUser {
+ user_id: usr.id
+ username: usr.username
+ 'name.first': usr.name.first
+ 'name.last': usr.name.last
+ 'name.full': usr.name.full
+ }
+
+ g = new Group {
+ name: 'Admins'
+ users: [gu.toObject()]
+ }
+ g.save (err, grp) ->
+ if err
+ console.log 'Group save error'
+ console.log err
+ process.exit 0
+
+ console.log "Created group 'Admins'"
+
+ rg = new RoleGroup {
+ group_id: grp.id
+ name: grp.name
+ }
+
+ r = new Role {
+ key: 'admin'
+ groups: [rg.toObject()]
+ }
+ r.save (err) ->
+ if err
+ console.log 'Role save error'
+ console.log err
+ process.exit 0
+
+ console.log "Created role 'admin'"
+ process.exit 0
212 init_db-sql.coffee
@@ -0,0 +1,212 @@
+###
+Setup Dependencies
+###
+require.paths.unshift __dirname
+s = require __dirname + "/lib/setup"
+s.ext __dirname + "/lib"
+s.ext __dirname + "/support"
+
+express = require 'express'
+sys = require 'sys'
+
+CoffeeScript = require 'coffee-script'
+
+Auth = require 'authorization'
+
+app = module.exports = express.createServer()
+
+tmpcfg = require('yaml').eval(
+ require('fs')
+ .readFileSync('config/app_config.yml')
+ .toString('utf-8')
+)
+global.config = CoffeeScript.helpers.merge tmpcfg['common'], tmpcfg[app.settings.env]
+
+app.set 'db_type', global.config.db_type
+app.set 'db_user', global.config.db_user
+app.set 'db_pass', global.config.db_pass
+app.set 'db_name', global.config.db_name
+
+app.configure 'development', () ->
+ console.log "configure development"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'staging', () ->
+ console.log "configure staging"
+ app.use express.errorHandler(dumpExceptions: true, showStack: true)
+
+app.configure 'test', () ->
+ console.log "configure test"
+
+app.configure 'production', () ->
+ console.log "configure production"
+ app.use express.errorHandler()
+
+db = require('db-mysql')[app.set('db_type')].Database
+ hostname: app.set 'db_host'
+ port: app.set 'db_port'
+ database: app.set 'db_name'
+ user: app.set 'db_user'
+ password: app.set 'db_pass'
+).on('error', (error) ->
+ console.log 'ERROR connecting to SQL server: ' + error
+).on('ready', (server) ->
+ console.log 'Connected to ' + server.hostname + ' (' + server.version + ')'
+
+ console.log 'creating table users'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `users` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `username` varchar(255) NOT NULL DEFAULT '',
+ `hashed_password` varchar(255) NOT NULL DEFAULT '',
+ `salt` varchar(255) NOT NULL DEFAULT '',
+ `firstname` varchar(255) NOT NULL DEFAULT '',
+ `lastname` varchar(255) NOT NULL DEFAULT '',
+ `email` varchar(255) NOT NULL DEFAULT '',
+ `status` int unsigned NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY (`username`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+
+ console.log "Creating default user 'admin' with password admin"
+ salt = Auth.makeSalt()
+ app.sqlClient.query().
+ insert('users',
+ ['firstname', 'lastname', 'username', 'salt', 'hashed_password', 'email'],
+ ['Project', 'Admin', 'admin', salt, Auth.createHashedPassword(salt, 'admin'), 'admin@project.com']
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR creating user: ' + err
+ throw err
+ console.log 'User created with id: %s', result.id
+ )
+
+ console.log 'creating table groups'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `groups` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY (`name`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+ console.log "Creating default group 'Admins'"
+ app.sqlClient.query().
+ insert('groups',
+ ['name'],
+ ['Admins']
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR creating group: ' + err
+ throw err
+ console.log 'Group created with id: %s', result.id
+ )
+
+ console.log 'creating table group_users'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `group_users` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `group_id` bigint NOT NULL DEFAULT 0,
+ `user_id` bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+ console.log "Assigning user 'admin' to grou 'Admins'"
+ app.sqlClient.query().
+ insert('group_users',
+ ['group_id', 'user_id'],
+ [1, 1]
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR assigning user to group: ' + err
+ throw err
+ console.log 'Group assigment created with id: %s', result.id
+ )
+
+ console.log 'creating table roles'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `roles` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `key` varchar(255) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY (`key`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+ console.log "Creating default role 'admin'"
+ app.sqlClient.query().
+ insert('roles',
+ ['key'],
+ ['admin']
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR creating role: ' + err
+ throw err
+ console.log 'Role created with id: %s', result.id
+ )
+
+ console.log 'creating table role_users'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `role_users` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `role_id` bigint NOT NULL DEFAULT 0,
+ `user_id` bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+ console.log "Assigning user 'admin' to role 'admin'"
+ app.sqlClient.query().
+ insert('role_users',
+ ['role_id', 'user_id'],
+ [1, 1]
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR assigning user to role: ' + err
+ throw err
+ console.log 'Role assigment created with id: %s', result.id
+ )
+
+ console.log 'creating table role_groups'
+ app.sqlClient.query().execute(
+ "CREATE TABLE IF NOT EXISTS `role_groups` (
+ `id` bigint unsigned NOT NULL auto_increment,
+ `role_id` bigint NOT NULL DEFAULT 0,
+ `group_id` bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
+ (err) ->
+ if err
+ throw err
+
+ console.log "Assigning group 'Admins' to role 'admin'"
+ app.sqlClient.query().
+ insert('role_groups',
+ ['role_id', 'group_id'],
+ [1, 1]
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR assigning group to role: ' + err
+ throw err
+ console.log 'Role assigment created with id: %s', result.id
+ )
+
+ setTimeout( () ->
+ process.exit 0
+ , 1000)
+)
+app.sqlClient.connect()
96 lib/authorization-mongo.coffee
@@ -0,0 +1,96 @@
+mongoose = require 'mongoose'
+
+User = mongoose.model 'User'
+Group = mongoose.model 'Group'
+Role = mongoose.model 'Role'
+LoginToken = mongoose.model 'LoginToken'
+
+_checkRoleByKey = (key) ->
+ Role.findOne {key: key}, (err, role) ->
+ if err || !role
+ r = new Role key: key
+ r.save()
+ return
+
+class Authorization
+ constructor: () ->
+ return
+
+ checkLogin: (req, res, next) ->
+ if req.session.user_id
+ User.findById req.session.user_id, (err, user) ->
+ if user
+ req.currentUser = user
+ next()
+ else if req.cookies.logintoken
+ @_authenticateFromLoginToken req, res, next
+ else
+ next()
+
+ requireLogin: (req, res, next) ->
+ url = require('url').parse req.url, true
+ redirect_to = require('querystring').stringify(redirect_to: url.href)
+
+ if req.session.user_id
+ User.findById req.session.user_id, (err, user) ->
+ if user
+ req.currentUser = user
+ next()
+ else
+ if req.xhr
+ res.redirect '/auth/login', 401
+ else
+ res.redirect '/auth/login?' + redirect_to
+ else if req.cookies.logintoken
+ @_authenticateFromLoginToken req, res, next
+ else
+ if req.xhr
+ res.redirect '/auth/login', 401
+ else
+ res.redirect '/auth/login?' + redirect_to
+
+ requireRole: (keys) ->
+ return (req, res, next) =>
+ role_keys = keys.split(' ')
+
+ for key in role_keys
+ _checkRoleByKey key
+
+ @requireLogin req, res, () ->
+ req.currentUser.hasRoles(role_keys, (tot) ->
+ #console.log '%s / %s', tot, role_keys.length
+ if tot >= role_keys.length
+ return next()
+ else
+ return res.redirect '/403'
+ )
+
+ _authenticateFromLoginToken = (req, res, next) ->
+ cookie = JSON.parse req.cookies.logintoken
+ url = require('url').parse req.url, true
+ redirect_to = require('querystring').stringify(redirect_to: url.href)
+
+ LoginToken.findOne username: cookie.username, series: cookie.series, token: cookie.token, (err, token) ->
+ if !token
+ if req.xhr
+ res.redirect '/auth/login', 401
+ else
+ res.redirect '/auth/login?' + redirect_to
+
+ User.findOne username: token.username, (err, user) ->
+ if !user
+ if req.xhr
+ res.redirect '/auth/login', 401
+ else
+ res.redirect '/auth/login?' + redirect_to
+ else
+ req.session.user_id = user.id
+ req.currentUser = user
+
+ token.token = token.randomToken()
+ token.save () ->
+ res.cookie 'logintoken', token.cookieValue, expires: new Date(Date.now() + 2 * 604800000), path: '/'
+ next()
+
+
+module.exports = new Authorization()
206 lib/authorization-sql.coffee
@@ -0,0 +1,206 @@
+crypto = require 'crypto'
+
+_checkRoleByKey = (app, key) ->
+ app.sqlClient.query().
+ select('id').
+ from('roles').
+ where('key = ?', [key]).
+ execute (err, rows, cols) ->
+ if err || rows.length < 1
+ app.sqlClient.query().
+ insert('roles',
+ ['key'],
+ [key]
+ ).execute (err, result) ->
+ if err
+ throw err
+ return
+ return
+ return
+
+class Authorization
+ constructor: () ->
+ return
+
+ checkLogin: (req, res, next) ->
+ if req.session.user_id
+ req.app.sqlClient.query().
+ select('*').
+ from('users').
+ where('id = ?', [req.session.user_id]).
+ execute (err, rows, cols) ->
+ if err || rows.length < 1
+ return next()
+ req.currentUser = rows[0]
+ next()
+ else
+ next()
+
+ requireLogin: (req, res, next) ->
+ url = require('url').parse req.url, true
+ redirect_to = require('querystring').stringify(redirect_to: url.href)
+
+ if req.session.user_id
+ req.app.sqlClient.query().
+ select('*').
+ from('users').
+ where('id = ?', [req.session.user_id]).
+ execute (err, rows, cols) ->
+ if err || rows.length < 1
+ return next()
+ req.currentUser = rows[0]
+ next()
+ else
+ if req.xhr
+ res.redirect '/auth/login', 401
+ else
+ res.redirect '/auth/login?' + redirect_to
+
+ requireRole: (keys) ->
+ return (req, res, next) =>
+ role_keys = keys.split(' ')
+
+ for key in role_keys
+ _checkRoleByKey req.app, key
+
+ @requireLogin req, res, () ->
+ #TODO: implement like in mongo
+
+ # 1. check if user has role
+ # 2. check if user groups has role
+ # 3. compare role_keys length with matched length
+ next()
+
+ makeSalt: () ->
+ salt = Math.round(new Date().valueOf() * Math.random()) + ''
+ return salt
+
+ createHashedPassword: (salt, plaintext) ->
+ return crypto.createHmac('sha1', salt).update(plaintext).digest('hex')
+
+ createUser: (app, data, next) ->
+ User =
+ firstname:''
+ lastname:''
+ username:''
+ email:''
+ salt:''
+ password:''
+ hashed_password:''
+
+ for key, value of data
+ if value
+ User[key] = value
+
+ User.salt = @makeSalt()
+ if User.password
+ User.hashed_password = @createHashedPassword(User.salt, User.password)
+
+ app.sqlClient.query().
+ insert('users',
+ ['firstname', 'lastname', 'username', 'salt', 'hashed_password', 'email'],
+ [User.firstname, User.lastname, User.username, User.salt, User.hashed_password, User.email]
+ ).execute (err, result) ->
+ if err
+ console.log 'ERROR: ' + err
+ throw err
+ console.log 'User created with id: %s', result.id
+ next(result)
+
+ updateUser: (app, data, next) ->
+ User =
+ id:0
+ firstname:''
+ lastname:''
+ username:''
+ email:''
+ salt:''
+ password:''
+ hashed_password:''
+
+ for key, value of data
+ if value
+ User[key] = value
+
+ if User.id != 0
+ if User.password != ''
+ @updateUserPassword(app, data)
+
+ app.sqlClient.query().
+ update('users').
+ set({'firstname':User.firstname, 'lastname':User.lastname, 'username':User.username, 'email':User.email}).
+ where('id = ?',[User.id]).
+ execute (err, result) ->
+ if err
+ console.log 'ERROR: ' + err
+ throw err
+ console.log 'User updated with id: %s', User.id
+
+ next(User)
+ else
+ next()
+
+ updateUserPassword: (app, data, next) ->
+ User =
+ id:0
+ password:''
+ hashed_password:''
+ salt:''
+
+ for key, value of data
+ if value
+ User[key] = value
+
+ if User.password != '' && User.id != 0
+
+ User.salt = @makeSalt()
+ User.hashed_password = @createHashedPassword(User.salt, User.password)
+ app.sqlClient.query().
+ update('users').
+ set({'salt':User.salt, 'hashed_password':User.hashed_password}).
+ where('id = ?',[User.id]).
+ execute (err, result) ->
+ if err
+ console.log 'ERROR: ' + err
+ throw err
+ console.log 'User password updated with id: %s', User.id
+
+ generatePassword: (length, seed, lower_only) ->
+ range = require('helpers').range
+ _ = require 'static/js/underscore'
+
+ chars = 0
+ decimal = 0
+ pwd = ''
+ length = 7 unless length
+ seed = false unless seed
+ lower_only = false unless lower_only
+
+ character_table = range('a','z')
+ if !lower_only
+ character_table = character_table.concat(range('A','Z'))
+ character_table = character_table.concat(range(0,9))
+ character_table = _.without(character_table, 'I', 'l', 'o', 'O', '0')
+ ctable_length = character_table.length
+
+ tmp = while chars < length
+ if seed == false
+ seed = ""+(new Date()).getTime()
+
+ hash = crypto.createHash('md5').update(seed).digest('hex')
+
+ i = 0
+ tmp = while (chars < length) && i < 32
+ ++chars
+ i += 2
+
+ hex_string = (hash.substr(i, 32)).replace(/[^a-f0-9]/gi, '')
+ decimal = parseInt hex_string, 16
+ char = character_table[(decimal % ctable_length)]
+ pwd += char
+
+ seed = false
+
+ return pwd
+
+module.exports = new Authorization()
68 lib/pager.coffee
@@ -0,0 +1,68 @@
+_url = require 'url'
+_querystring = require 'querystring'
+
+class Pager
+ @prefix = "pager_"
+
+ constructor: (@req, @id, @limit) ->
+ @max = 0
+ @vals =
+ page: 1
+ start: 0
+ end: @limit
+ max: @max
+ return
+
+ setMax: (@max) ->
+ return
+
+ values: () ->
+ url = _url.parse @req.url, true
+ @vals.max = @max
+
+ if !url.query
+ return @vals
+
+ if url.query[@_getPageVarName()]
+ @vals.page = Number url.query[@_getPageVarName()]
+
+ @vals.start = (@vals.page * @limit) - @limit
+ @vals.end = @vals.start + @limit
+ if @max >0 && @vals.end > @max
+ @vals.end = @max
+
+ return @vals
+
+ pages: () ->
+ pageCnt = Math.round(@max / @limit)
+ if pageCnt == 0
+ pageCnt = 1
+
+ pages = ({type: 'page', val: page, url: @_pageUrl(page), active: (@vals.page == page)} for page in [1..pageCnt])
+ beg = []
+ if @vals.page > 1
+ beg = [{type: 'nav', val: 'first', url: @_pageUrl(1), active: false}]
+ if @vals.page >= 2
+ beg = beg.concat([{type: 'nav', val: 'prev', url: @_pageUrl(@vals.page-1), active: false}])
+
+ end = []
+ if @vals.page < pageCnt
+ end = [{type: 'nav', val: 'last', url: @_pageUrl(pageCnt), active: false}]
+ if @vals.page <= pageCnt-1
+ end = [{type: 'nav', val: 'next', url: @_pageUrl(@vals.page+1), active: false}].concat(end)
+
+ tmp = beg.concat(pages)
+ tmp = tmp.concat(end)
+
+ return tmp
+
+ _pageUrl: (page) ->
+ url = _url.parse @req.url, true
+ url.query[@_getPageVarName()] = page
+ return url.pathname + '?' + _querystring.stringify(url.query)
+
+ _getPageVarName: () ->
+ return Pager.prefix + @id + '_page'
+
+module.exports = (req, id, limit) ->
+ return new Pager(req, id, limit)
72 lib/setup.coffee
@@ -0,0 +1,72 @@
+###
+Setup.js created by Ben Weaver (http://gist.github.com/508314)
+Coffee-Scripted by Jerry Jalava
+###
+
+fs = require 'fs'
+path = require 'path'
+
+# --- Methods
+
+run = require
+
+# A shortcut for adding `lib' and `ext' subfolders, then running a
+# main program.
+app = (base, main) ->
+ lib path.join(base, 'lib')
+ ext path.join(base, 'ext')
+
+ if main
+ return run path.join(base, main)
+
+ return exports
+
+# Local libraries
+lib = (folder) ->
+ if exists folder
+ require.paths.unshift folder
+ return exports
+
+# Third-party libraries
+ext = (folder) ->
+ if !exists folder
+ return exports
+
+ # Some programmers refer to third-party libraries in a fully-qualified manner.
+ require.paths.unshift folder
+
+ fs.readdirSync(folder).forEach (name) ->
+ base = path.join folder, name
+ linked = false
+
+ # Pure-JS packages have a `lib' folder with LIBRARY.js files in it.
+ # Packages with C++ bindings will have a `build/default' folder
+ # with LIBRARY.node files in it after running node-waf.
+ [path.join(base, '/build/default'), path.join(base, '/lib')]
+ .forEach (folder) ->
+ if exists folder
+ require.paths.unshift folder
+ linked = true
+ return
+
+ # If neither `lib' or `build' were found, fallback to linking the
+ # folder itself.
+ if !linked
+ require.paths.unshift base
+ return
+
+ return exports
+
+# --- Aux
+
+exists = (filename) ->
+ try
+ fs.statSync filename
+ return true
+ catch x
+ return false
+
+exports.app = app
+exports.lib = lib
+exports.ext = ext
+exports.run = run
221 models.coffee
@@ -0,0 +1,221 @@
+crypto = require('crypto')
+CoffeeScript = require 'coffee-script'
+dateFormat = require 'dateformat'
+async = require('async')
+
+toLower = (v) ->
+ v.toLowerCase()
+
+validatePresenceOf = (value) ->
+ value && value.length
+
+defineModels = (mongoose, next) ->
+ Schema = mongoose.Schema
+ ObjectId = Schema.ObjectId
+
+ ###
+ Model: User
+ ###
+
+ User = new Schema
+ username:
+ type: String
+ validate: [validatePresenceOf, 'username is required']
+ index:
+ unique: true
+ email:
+ type: String
+ validate: [validatePresenceOf, 'email is required']
+ index: true
+ name:
+ first:
+ type: String
+ default: ""
+ last:
+ type: String
+ default: ""
+ hashed_password: String
+ salt: String
+ roles: [String]
+
+ User.virtual('id').get () ->
+ this._id.toHexString()
+
+ User.virtual('name.full').get () ->
+ return this.name.first + " " + this.name.last
+
+ User.virtual('name.initials').get () ->
+ initials = String(this.name.first).substr(0,1) + String(this.name.last).substr(0,1)
+ initials = initials.toUpperCase()
+ return initials
+
+ User.virtual('password').set (password) ->
+ this._password = password
+ this.salt = this.makeSalt()
+ this.hashed_password = this.encryptPassword password
+ .get () -> this._password
+
+ User.method 'authenticate', (plainText) ->
+ this.encryptPassword(plainText) is this.hashed_password
+
+ User.method 'makeSalt', () ->
+ Math.round(new Date().valueOf() * Math.random()) + ''
+
+ User.method 'encryptPassword', (password) ->
+ crypto.createHmac('sha1', this.salt).update(password).digest('hex')
+
+ User.pre 'save', (next) ->
+ if !validatePresenceOf this.hashed_password
+ next new Error('Invalid password')
+ else
+ next()
+
+ User.method 'hasRoles', (roles, next) ->
+ Role = mongoose.model 'Role'
+ Group = mongoose.model 'Group'
+
+ tasks = []
+ user_id = this._id
+
+ for rk in roles
+ do (rk) ->
+ tasks.push (cb) ->
+ Role.findOne {key: rk}, (err, role) ->
+ if err || !role
+ return cb(null, 0)
+ if role.hasUser user_id
+ cb(null, 1)
+ else if role.groups.length > 0
+ async.forEach role.groups, (grp, cbb) ->
+ Group.findOne {_id: grp.group_id}, (e, group) ->
+ if group && group.hasUser user_id
+ return cbb()
+ else
+ return cbb(0)
+ , (e, r) ->
+ if e
+ cb(null, 0)
+ else
+ cb(null, 1)
+ else
+ cb(null, 0)
+
+ async.series tasks, (err, results) ->
+ tot = 0
+ for r in results
+ tot += r
+ next(tot)
+
+ ###
+ Model: GroupUser
+ ###
+ GroupUser = new Schema
+ user_id: ObjectId
+ username: String
+ name:
+ first: String
+ last: String
+ full: String
+
+ ###
+ Model: Groups
+ ###
+ Group = new Schema
+ name:
+ type: String
+ validate: [validatePresenceOf, 'name is required']
+ index:
+ unique: true
+ users: [GroupUser]
+
+ Group.method 'hasUser', (user_id) ->
+ for user in this.users
+ if user.user_id.toString() == user_id.toString()
+ return true
+ return false
+
+ ###
+ Model: RoleGroup
+ ###
+ RoleGroup = new Schema
+ group_id: ObjectId
+ name: String
+
+ ###
+ Model: RoleUser
+ ###
+ RoleUser = new Schema
+ user_id: ObjectId
+ username: String
+ name:
+ first: String
+ last: String
+ full: String
+
+ ###
+ Model: Role
+ ###
+ Role = new Schema
+ key:
+ type: String
+ validate: [validatePresenceOf, 'key is required']
+ index:
+ unique: true
+ set: toLower
+ groups: [RoleGroup]
+ users: [RoleUser]
+
+ Role.method 'hasGroup', (group_id) ->
+ for grp in this.groups
+ if grp.group_id.toString() == group_id.toString()
+ return true
+ return false
+
+ Role.method 'hasUser', (user_id) ->
+ for user in this.users
+ if user.user_id.toString() == user_id.toString()
+ return true
+ return false
+
+ ###
+ Model: LoginToken
+ Used for session persistence.
+ ###
+ LoginToken = new Schema
+ username:
+ type: String
+ index: true
+ series:
+ type: String
+ index: true
+ token:
+ type: String
+ index: true
+
+ LoginToken.method 'randomToken', () ->
+ Math.round (new Date().valueOf() * Math.random()) + ''
+
+ LoginToken.pre 'save', (next) ->
+ #Automatically create the tokens
+ this.token = this.randomToken()
+ if this.isNew
+ this.series = this.randomToken()
+ next()
+
+ LoginToken.virtual('id').get () ->
+ this._id.toHexString()
+
+ LoginToken.virtual('cookieValue').get () ->
+ JSON.stringify username: this.username, token: this.token, series: this.series
+
+ mongoose.model 'User', User
+ mongoose.model 'Group', Group
+ mongoose.model 'Role', Role
+ mongoose.model 'GroupUser', GroupUser
+ mongoose.model 'RoleGroup', RoleGroup
+ mongoose.model 'RoleUser', RoleUser
+ mongoose.model 'LoginToken', LoginToken
+
+ next()
+
+exports.defineModels = defineModels
24 package-mongo.json
@@ -0,0 +1,24 @@
+{
+ "name": "[PROJECT]",
+ "description": "[PROJECT] description here",
+ "version": "0.0.1",
+ "homepage": "",
+ "author": "",
+ "directories": {
+ "public": "./static"
+ },
+ "engines": {
+ "node": ">= 0.4.3"
+ },
+ "dependencies": {
+ "express": ">= 2.3.2",
+ "connect": ">= 1.2.1",
+ "yaml": ">= 0.1.1",
+ "coffee-script": ">= 1.0.1",
+ "jade": ">= 0.11.0",
+ "funk": ">= 1.0.1",
+ "mongoose": ">= 1.1.4",
+ "connect-mongodb": ">= 0.2.1"
+ }
+}
+
23 package-sql.json
@@ -0,0 +1,23 @@
+{
+ "name": "[PROJECT]",
+ "description": "[PROJECT] description here",
+ "version": "0.0.1",
+ "homepage": "",
+ "author": "",
+ "directories": {
+ "public": "./static"
+ },
+ "engines": {
+ "node": ">= 0.4.3"
+ },
+ "dependencies": {
+ "express": ">= 2.3.2",
+ "connect": ">= 1.2.1",
+ "yaml": ">= 0.1.1",
+ "coffee-script": ">= 1.0.1",
+ "jade": ">= 0.11.0",
+ "funk": ">= 1.0.1",
+ "db-[DB_TYPE]": ">= 0.6.5"
+ }
+}
+
8 routes/admin/index.coffee
@@ -0,0 +1,8 @@
+Auth = require 'authorization'
+
+module.exports = (app) ->
+ # index route
+ app.get '/admin', Auth.requireRole('admin'), (req, res) ->
+
+ res.render 'admin/index',
+ app.adminCommonLocals req
1  static/WebSocketMain.swf
0  static/css/admin.css
No changes.
0  static/css/layout.css
No changes.
0  static/js/admin.js
No changes.
1  static/js/dd_belatedpng.js
1  static/js/jquery-1.4.2.min.js
632 static/js/jquery.form.js
@@ -0,0 +1,632 @@
+/*
+ * jQuery Form Plugin
+ * version: 2.18 (06-JAN-2009)
+ * @requires jQuery v1.2.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id$
+ */
+;(function($) {
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are intended to be exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').bind('submit', function() {
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ return false; // <-- important!
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ if (typeof options == 'function')
+ options = { success: options };
+