inspired-server
Version:
A RESTful entity server
274 lines (260 loc) • 8.41 kB
text/coffeescript
fs = require 'fs'
hal = require 'hal'
uuid = require 'node-uuid'
handlebars = require 'handlebars'
class Rest
templates: {}
constructor: ->
# DB
@db = new App.DB
# Load templates
@_loadTemplate(name) for name in ['html']
_loadTemplate: (name) ->
fs.readFile "#{__dirname}/../../templates/#{name}.hbs", 'utf8', (err, data) =>
@templates[name] = handlebars.compile data
handler: (request, response) =>
# Router
router = new App.Router request
# Access logs
response.on 'finish', () ->
console.log request.connection.remoteAddress, response.statusCode, request.method, request.url
# 400 Error Handling
unless router.isValid() or router.isIndex()
response.writeHead 400
response.end()
# CORS
if request.headers.origin?
response.setHeader 'Access-Control-Allow-Origin', request.headers.origin
response.setHeader 'Access-Control-Allow-Credentials', 'true'
response.setHeader 'Access-Control-Allow-Methods', 'HEAD,GET,PUT,POST,PATCH,LINK,UNLINK,DELETE'
# Entity Class
entityClass = @db.registry router.name
relationClass = @db.registry router.rel
# Relation
if router.rel
###
@todo Fix SQL injection
###
relatedInstance = new relationClass
relatedKey = (key for key, field of relatedInstance.fields() when field.references? and field.references() is router.name)[0]
collection = @db.collection router.rel, "#{relatedKey} = '#{router.id}'"
switch request.method
when 'HEAD'
collection.count().then =>
response.setHeader 'Content-Range', "items */#{collection.toLength()}"
switch router.format
when 'json'
response.writeHead 200, {'Content-Type': 'application/json'}
response.end()
when 'xml'
response.writeHead 200, {'Content-Type': 'application/xml'}
response.end()
else
response.writeHead 200, {'Content-Type': 'text/html'}
response.end()
when 'GET'
collection.load().then =>
resource = new App.Hal.Resource collection, router
response.setHeader 'Content-Range', "items */#{collection.toArray().length}"
switch router.format
when 'json'
response.writeHead 200, {'Content-Type': 'application/json'}
response.end JSON.stringify resource.toJSON()
when 'xml'
response.writeHead 200, {'Content-Type': 'application/xml'}
response.end resource.toXML()
else
response.writeHead 200, {'Content-Type': 'text/html'}
response.end @templates.html resource.toJSON()
when 'PUT'
response.writeHead 501, "Not Implemented"
response.end()
when 'POST'
request.readJSON()
.then (data) =>
instance = new relationClass data
instance.uuid = uuid.v4()
instance[relatedKey] = router.id
instance.save()
.then (result) =>
resource = new App.Hal.Resource instance, router
response.writeHead 303, 'See Other', {
Location: resource._links.self.href
}
response.end()
.catch (e) =>
throw e
.catch (e) =>
console.log '[ERROR]', e
response.writeHead 503, {'Content-Type': 'application/json'}
response.write JSON.stringify e
response.end()
when 'PATCH'
response.writeHead 501, "Not Implemented"
response.end()
when 'LINK'
response.writeHead 501, "Not Implemented"
response.end()
when 'UNLINK'
response.writeHead 501, "Not Implemented"
response.end()
when 'DELETE'
collection.delete()
.then =>
response.writeHead 200
response.end()
.catch (e) =>
console.log '[ERROR]', e
response.writeHead 503, {'Content-Type': 'application/json'}
response.write JSON.stringify e
response.end()
when 'OPTIONS'
response.writeHead 200, {
Allow: 'HEAD, GET, PUT, POST, DELETE'
'Accept-Ranges': 'items'
}
response.end()
else
response.writeHead 405, {
Allow: 'HEAD, GET, PUT, POST, DELETE'
}
response.end()
# Entity
else if router.id
switch request.method
when 'HEAD'
response.writeHead 501, "Not Implemented"
response.end()
when 'GET'
instance = new entityClass
instance.load(router.id).then =>
resource = new App.Hal.Resource instance, router
switch router.format
when 'json'
response.writeHead 200, {'Content-Type': 'application/json'}
response.end JSON.stringify resource.toJSON()
when 'xml'
response.writeHead 200, {'Content-Type': 'application/xml'}
response.end resource.toXML()
else
response.writeHead 200, {'Content-Type': 'text/html'}
response.end @templates.html resource.toJSON()
when 'PUT'
request.readJSON().then (data) =>
instance = new entityClass data
instance.save().then ->
response.writeHead 200
response.end()
when 'POST'
response.writeHead 405, 'Method Not Allowed'
response.end()
when 'PATCH'
response.writeHead 501, "Not Implemented"
response.end()
when 'LINK'
response.writeHead 501, "Not Implemented"
response.end()
when 'UNLINK'
response.writeHead 501, "Not Implemented"
response.end()
when 'DELETE'
instance = new entityClass
instance.load(router.id).then =>
instance.delete()
.then ->
response.writeHead 200
response.end()
.catch (e) =>
console.log '[ERROR]', e
response.writeHead 503, {'Content-Type': 'application/json'}
response.write JSON.stringify e
response.end()
when 'OPTIONS'
response.writeHead 200, {
Allow: 'HEAD, GET, PUT, PATCH, LINK, UNLINK, DELETE'
}
response.end()
else
response.writeHead 405, 'Method Not Allowed', {
Allow: 'HEAD, GET, PUT, PATCH, LINK, UNLINK, DELETE'
}
response.end()
# Collection
else if router.name
collection = @db.collection router.name
switch request.method
when 'HEAD'
response.writeHead 501, "Not Implemented"
response.end()
when 'GET'
collection.load().then =>
resource = new App.Hal.Resource collection, router
switch router.format
when 'json'
response.writeHead 200, {'Content-Type': 'application/json'}
response.end JSON.stringify resource.toJSON()
when 'xml'
response.writeHead 200, {'Content-Type': 'application/xml'}
response.end resource.toXML()
else
response.writeHead 200, {'Content-Type': 'text/html'}
response.end @templates.html resource.toJSON()
when 'PUT'
response.writeHead 501, "Not Implemented"
response.end()
when 'POST'
request.readJSON()
.then (data) =>
instance = new entityClass data
instance.uuid = uuid.v4()
instance.save()
.then (result) =>
resource = new App.Hal.Resource instance, router
response.writeHead 303, 'See Other', {
Location: resource._links.self.href
}
response.end()
.catch (e) =>
throw e
.catch (e) =>
console.log '[ERROR]', e
response.writeHead 503, {'Content-Type': 'application/json'}
response.write JSON.stringify e
response.end()
when 'PATCH'
response.writeHead 405, 'Method Not Allowed'
response.end()
when 'LINK'
response.writeHead 405, 'Method Not Allowed'
response.end()
when 'UNLINK'
response.writeHead 405, 'Method Not Allowed'
response.end()
when 'DELETE'
response.writeHead 501, "Not Implemented"
response.end()
when 'OPTIONS'
response.writeHead 200, {
Allow: 'HEAD, GET, PUT, POST, DELETE'
'Accept-Ranges': 'items'
}
response.end()
else
response.writeHead 405, {
Allow: 'HEAD, GET, PUT, POST, DELETE'
}
response.end()
# Index
else
resource = new App.Hal.Resource Object.keys(@db.registry()), router
switch router.format
when 'json'
response.writeHead 200, {'Content-Type': 'application/json'}
response.end JSON.stringify resource.toJSON()
when 'xml'
response.writeHead 200, {'Content-Type': 'application/xml'}
response.end resource.toXML()
else
response.writeHead 200, {'Content-Type': 'text/html'}
response.end @templates.html resource.toJSON()
module.exports = Rest