Created
June 11, 2010 13:54
-
-
Save dvv/434496 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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 '/' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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