Skip to content

Instantly share code, notes, and snippets.

@dvv
Created June 11, 2010 13:54
Show Gist options
  • Save dvv/434496 to your computer and use it in GitHub Desktop.
Save dvv/434496 to your computer and use it in GitHub Desktop.
#
# This is a staring point for an application written on Pintura
#
# useful helpers
h: require('./util')
Facet: require 'perstore/facet'
#Store: require('perstore/store/persistent-memory').PersistentMemory
Store: require('perstore/store/mongodb').MongoDB
#Store: require('perstore/store/redis').Redis
#
# define capabilities
#
security: require('pintura/pintura').config.security
publicFacets: []
userFacets: []
adminFacets: []
getCurrentAccessLevel: (levels, user) ->
user: or require('pintura/jsgi/auth').currentUser
if user
admins: require('commonjs-utils/settings').admins
if admins?.indexOf(user?.username) >= 0
return levels[2]
return levels[1]
levels[0]
security.getUserClass: () ->
exports.User
security.getAuthClass: () ->
exports.Auth
security.getAllowedFacets: (user, request) ->
getCurrentAccessLevel [publicFacets, userFacets, adminFacets], user
#
# define entity helper
#
defineEntity: (name, entityDefinition, options) ->
options: or {}
#store: Notifying Store({collection: 'test:'+name}), options.logMethods # TODO: test prefix
store: Store {collection: name} # TODO: test prefix
#store: Store {collection: 'test:'+name}
model: require('perstore/model').Model name, store, entityDefinition or {
}
exports[name]: model
if options.publicFacet
publicFacet: Facet.Permissive model, {
maxQueryLimit: 100
}
publicFacets.push publicFacet
if options.userFacet
userFacet: Facet.Permissive model, {
maxQueryLimit: 100
}
userFacets.push userFacet
if true
adminFacet: Facet.Permissive model, {
maxQueryLimit: 100
}
adminFacets.push adminFacet
{
store: store
model: model
publicFacet: publicFacet
userFacet: userFacet
adminFacet: adminFacet
}
#
# define model
#
hub: require 'tunguska/hub'
model: require('./model')
for k, v of model
e: defineEntity k, v, {}
if v.logger
store: e.store
# monkey-patch store accessors to provide logging
(v.logger.methods or ['put', 'delete']).forEach (method) ->
if store[method]
store[method]: store[method].after () ->
object:
if method is 'put' or method is 'delete'
object: arguments
hub.publish {
channel: 'event'
clientId: 'store'
result: {store: store, action: method, args: arguments}
#type: method
}
#
# define logging
#
# subscribe to event channel
hub.subscribe 'event', (message) ->
msg: message.result
#h.dir 'LOG: ', msg
event: new exports.Event({date: h.now(), text: msg.action, object: msg.object})
#
# custom media
#
# register HAML templating engine
# requires "haml": "jar:http://github.com/visionmedia/haml.js/zipball/master!/lib/" in package.json
Media: require('pintura/media').Media
html: Media.instances['text/html']
haml: require 'haml/haml'
templated: {
mediaType: 'text/html'
getQuality: (object) -> 0.5
config: require('commonjs-utils/settings').haml or {cache: false, root: 'public/views'}
serialize: (object, request, response) ->
xhr: request.headers?['x-requested-with']? is 'XMLHttpRequest'
# AJAX call? -> return pure content
if xhr
return html.serialize object, request, response
user: require('pintura/jsgi/auth').currentUser
menu: getCurrentAccessLevel ['menu-guest', 'menu-user', 'menu-admin'], user
r: {
content: 'home'
user: user
menu: menu
}
# vanilla call -> return templated content
self: this
filename: request.template or 'index'
try
if not this.config.cache or not haml.cache[filename]
template: require('fs-promise').readFileSync this.config.root + '/' + filename + '.haml'
catch x
#h.dir 'HTML!'
#['HAML: bad template ' + filename]
return html.serialize object, request, response
object: or {}
locals: object or {
items: if object instanceof Array then object.toRealArray() else object
}
locals.user: user
locals.menu: menu
#h.dir "HAML: " + filename
{
forEach: (write) ->
write haml.render(template, {
context: haml # N.B. this will be 'this' in templates
locals: locals
cache: self.config.cache
filename: filename
})
}
}
Media templated
haml.partial: (template, object) ->
require('pintura/media').forEachableToString templated.serialize(object, {template: template}, {})
#media.serialize object, {template: template}, {}
#
# custom actions
#
routes: require 'pintura/jsgi/routes'
routes.get '/index', (request) ->
{status: 200, headers: {}, body: []}
routes.post '/login', (request) ->
data: request.body
authenticate: (username, password, expires) ->
require('pintura/security').Register.prototype.authenticate(null, username, password, expires)
createUser: (username, password) ->
require('pintura/security').Register.prototype.createUser(username, password)
try
if data.register
h.when createUser(data.username, data.password), (user) ->
h.redirect '/', user.headers
else
h.when authenticate(data.username, data.password, data.expires), (user) ->
h.redirect '/', user.headers
catch x
h.redirect '/'
routes.post '/logout', (request) ->
authenticate: (username, password, expires) ->
require('pintura/security').Register.prototype.authenticate(null, username, password, expires)
try
h.when authenticate(null), (response) ->
h.redirect '/', response.headers
catch x
h.redirect '/'
h: require './util'
window.defer: require('commonjs-utils/promise').defer
window.wait: require('commonjs-utils/promise').wait
model: require './model'
window.Model: (entity) ->
entity: or 'Class'
patch: (obj) ->
if obj
obj.save: ()->
id: this.id
delete this.id
window.CALL(entity + '/' + id, 'PUT', this, {}).then patch
obj.remove: () ->
window.CALL entity + '/' + this.id, 'DELETE', {}, {}
obj.validate: () ->
# TODO: apply JSON-Schema?!
true
obj: h.apply obj, model[entity].prototype
obj
self: {
# TODO: getter
schema: () ->
model[entity].properties
# TODO: getter
methods: () ->
model[entity].prototype
create: (props) ->
window.CALL(entity + '/', 'POST', props or {}, {}).then patch
load: (id) ->
window.CALL(entity + '/' + id, 'GET', null, {}).then patch
query: (criteria, options, params) ->
options: or {
offset: 0
limit: Infinity
}
# compose querystring
try
queryString: require('perstore/resource-query').Query(criteria, params).toString()
queryString: '?' + queryString if queryString
catch x
queryString: '0=1'
# honor range
options.offset: 0 unless options.offset?
options.limit: Infinity unless options.limit?
range: 'items=' + options.offset + '-'
if options.limit isnt Infinity
range: range + (options.offset+options.limit-1)
headers: {
range: range
}
window.CALL(entity + '/' + queryString, 'GET', null, headers).then (data) ->
data.map(patch)
}
self
#!/usr/bin/env coffee
sys: require 'sys'
# global settings, read from local.json
settings: require 'commonjs-utils/settings'
h: require './util'
# frontend database
redis: require 'redis/redis-client'
db: redis.createClient()
# run the app once frontend database is connected
db.addListener 'connected', () ->
run()
# helper functions
rnd: () ->
Math.floor(Math.random()*1000)
# redirector high-load script
redirector: (req, res, id) ->
# try to get target URL
db.get 'redirect:' + id + ':url', (err, url) ->
# if no URL is found...
if not url
# catch nonexistent IDs?
if settings.server.honeypot
# yes. redirect to the honeypot
url: '/honeypot/' + id
id: 'honeypot'
else
# no. register new ID and create redirect URL
url: 'http://google.com?q=' +rnd()+rnd()+rnd()
db.setnx 'redirect:' + id + ':url', url
# increment hit counter for ID
db.incr 'redirect:' + id + ':count', (err, value) ->
# redirect to target URL unless error
if not err
h.redirect.call res, url
db.incr 'redirect:count'
else
db.incr 'redirect:error:count'
# installation medium-load script
install: (req, res, id) ->
# report success
h.respond.call res, 200
# register installation
data: 'dummy'
db.setnx 'install:' + id + ':data', data, (err, value) ->
if not err
db.incr 'install:count'
# simple content script (for benchmarks)
hello: (req, res) ->
h.respond.call res, 200, {'Content-Type': 'text/plain'}, 'Hello, world!\n'
# define routes
routes: [{
pattern: '/redirect/:id'
handler: redirector
}]
# plug test scripts in
if settings.server.debug
routes.unshift {
pattern: '=/test'
handler: (req, res) ->
redirector.call res, req, res, rnd()
},{
pattern: '=/hello'
handler: hello
}
# serve static content via nginx until node support fast static file serving
# N.B. now node is 6 times slower
if settings.server.nginx
public: (req, res) ->
h.redirect.call res, settings.server.nginx + req.url
index: (req, res) ->
sys.debug sys.inspect req
h.redirect.call res, settings.server.nginx + '/public/'
routes.push {
pattern: '/public/'
handler: public
},{
pattern: '=/'
handler: index
}
# application entry point
run: () ->
# compile routes patterns
for i in [0...routes.length]
path: routes[i].pattern
if path instanceof String
s: path.replace /\/:(\w+)/g, '(?:/([^\/]+)|/)'
if s isnt path
routes[i].pattern: new RegExp('^' + s + '$')
# setup pintura application
pinturaApp: require('pintura/pintura').app
app: require './app'
# make lib files available at client side
pinturaApp: require('transporter/jsgi/transporter').Transporter {loader: require('nodules').forEngine('browser').useLocal().getModuleSource}, pinturaApp
# honor extensions
Extension: require('pintura/jsgi/extension').Extension
pinturaApp: Extension {
'json': 'application/json',
'js': 'application/javascript',
'html': 'text/html',
'xml': 'text/xml'
}, pinturaApp
# handle static files unless nginx is configured
#pinturaApp: require('pintura/jsgi/static').Static({urls:[''], roots:['public']}, pinturaApp) unless settings.server.nginx
### / means /index.html
##request.url: '/index.html' if request.url is '/'
# routes
#pinturaApp: require('pintura/jsgi/routes').Routes [], pinturaApp
#
nodeApp: require('jsgi-node').Listener pinturaApp
# define the request handler
handler: (request, response) ->
#if request.url.substr(0, 6) is '/index' then sys.debug sys.inspect request
# process defined routes
url: request.url
for i in [0...routes.length]
pattern: routes[i].pattern
args: false
if pattern instanceof RegExp
args: pattern.exec url
else
if pattern.charAt(0) is '='
#sys.debug sys.inspect url
args: [] if url is pattern.substring 1
else
args: [] if url.indexOf(pattern) is 0
if args
args.shift()
args.unshift request, response
return routes[i].handler.apply response, args
# delegate the rest to pintura
nodeApp request, response
# setup the server
server: require('http').createServer handler
port: settings.server.port or 80
if settings.server.nodes > 1
nodes: require('multi-node/multi-node').listen {port: port, nodes: settings.server.nodes}, server
sys.puts settings.server.nodes + ' servers running at http://*:' + port if nodes.isMaster
else
server.listen port
sys.puts 'Server running at http://*:' + port
# start REPL
if not nodes or nodes.isMaster
repl: require('repl').start()
repl.scope.require = require
repl.scope.sys = require 'sys'
#repl.scope.fixture: require 'fixture'
#repl.scope.app: require 'app'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment