Last active
January 3, 2016 07:18
-
-
Save der-On/8428136 to your computer and use it in GitHub Desktop.
RESTfull controllers in geddy.js
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
/* | |
* Geddy JavaScript Web development framework | |
* Copyright 2112 Matthew Eernisse (mde@fleegix.org) | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
*/ | |
var Application = function () { | |
var _respond = this.respond; | |
this._respond = this.respond; | |
this.respond = function(data, options) { | |
var format = this.params.format || 'json'; | |
if (options && options.format) format = options.format; | |
if (typeof data === 'undefined') { | |
data = {} | |
} | |
if (format === 'html') { | |
data.geddy = geddy; | |
} | |
return _respond.call(this, data, options); | |
} | |
// all resource controllers are pretty much the same | |
this.isResourceController = function(type) | |
{ | |
this.respondsWith = ['json']; | |
var self = this; | |
var _type = type.toLowerCase(); | |
var _types; | |
if (_type === 'person') { | |
_types = 'people'; | |
} | |
else { | |
_types = (_type.substring(_type.length - 1) === 's') ? _type + 'es' : _type + 's'; | |
} | |
this.index = function (req, resp, params) { | |
var query = null; | |
var opts = {nocase: true}; | |
if (params.query) { | |
query = (typeof params.query !== 'object') ? JSON.parse(params.query) : params.query; | |
} | |
if (params.sort) { | |
opts.sort = (typeof params.sort !== 'object') ? JSON.parse(params.sort) : params.sort; | |
} | |
if (params.limit) { | |
opts.limit = (typeof params.limit !== 'number') ? parseInt(params.limit) : params.limit; | |
} | |
if (params.skip) { | |
opts.skip = (typeof params.skip !== 'number') ? parseInt(params.skip) : params.skip; | |
} | |
geddy.model[type].all(query, opts, function(error, models) { | |
var data = { | |
params: params | |
}; | |
if (error) { | |
params.error = error; | |
} | |
else if(models) { | |
// force arrays for consistent output | |
if (typeof models.length !== 'number') { | |
models = [models]; | |
} | |
data[_types] = models; | |
} | |
else { | |
data[_types] = null; | |
} | |
self.respond(data); | |
}); | |
}; | |
this.create = function (req, resp, params) { | |
var model = geddy.model[type].create(params); | |
if (model.isValid()) { | |
geddy.model[type].save(model, function(error, model){ | |
var data = { | |
params: params | |
}; | |
data[_type] = model; | |
if (error) { | |
params.error = error; | |
} | |
self.respond(data); | |
}); | |
} | |
else { | |
self.respond({errors: model.errors, params: params}); | |
} | |
}; | |
this.show = function (req, resp, params) { | |
geddy.model[type].first(params.id || params.query, function(error, model) { | |
var data = { | |
params: params | |
}; | |
data[_type] = model; | |
if (error) { | |
params.error = error; | |
} | |
self.respond(data); | |
}); | |
}; | |
this.update = function (req, resp, params) { | |
geddy.model[type].first(params.id || params.query, function(error, model) { | |
if (error) { | |
self.respond({ error: error, params: params }); | |
} | |
else { | |
model.updateProperties(params); | |
if (model.isValid()) { | |
geddy.model[type].save(model, function(error, model) { | |
var data = { | |
params: params | |
}; | |
data[_type] = model; | |
if (error) { | |
params.error = error; | |
} | |
self.respond(data); | |
}); | |
} | |
else { | |
self.respond({ errors: model.errors, params: params }); | |
} | |
} | |
}); | |
}; | |
this.destroy = function (req, resp, params) { | |
geddy.model[type].remove(params.id || params.query, function(error) { | |
var data = { | |
params: params | |
}; | |
if (error) { | |
params.error = error; | |
} | |
self.respond(data); | |
}); | |
}; | |
} | |
}; | |
exports.Application = Application; | |
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
var Rest = require('../modules/rest'); | |
var restApi = new Rest(); | |
var model = geddy.model | |
, _baseConfig | |
, _data = {}; | |
_baseConfig = {}; | |
function urlizedModelName(name) | |
{ | |
var urlized = name.toLowerCase(); | |
if (urlized === 'person') { | |
return 'people'; | |
} | |
urlized = (urlized.substring(urlized.length - 1) === 's') ? urlized + 'es' : urlized + 's'; | |
return urlized; | |
} | |
function isObjectEmpty(obj) | |
{ | |
for(var attr in obj) { | |
return false; | |
} | |
return true; | |
} | |
var Adapter = function (options) { | |
var opts = options || {} | |
, config; | |
this.name = 'rest'; | |
this.config = _baseConfig; | |
this.client = null; | |
this.config.host = opts.host || null; | |
this.config.username = opts.username || null; | |
this.config.password = opts.password || null; | |
this.init = function () {}; | |
this.load = function (query, callback) { | |
if (query.byId) { | |
restApi.read(urlizedModelName(query.model.modelName) + '/' + query.byId, {}, onLoaded); | |
} | |
else { | |
restApi.read(urlizedModelName(query.model.modelName), { | |
query: isObjectEmpty(query.rawConditions) ? null : query.rawConditions, | |
sort: query.opts.sort || null, | |
limit: query.opts.limit || null, | |
skip: query.opts.skip || null | |
}, onLoaded); | |
} | |
function onLoaded(error, _data) | |
{ | |
if (error) { | |
callback(error, null); | |
return; | |
} | |
var singularName = query.model.modelName.toLowerCase(); | |
var pluralName = (singularName.substring(singularName.length - 1) === 's') ? singularName + 'es' : singularName + 's'; | |
if (singularName === 'person') { | |
pluralName = 'people'; | |
} | |
var items = []; | |
if (_data[pluralName]) { | |
_data[pluralName].forEach(function(itemData) { | |
var item = query.model.create(itemData); | |
item.id = itemData.id; | |
item._saved = true; | |
items.push(item); | |
}); | |
} | |
else if (_data[singularName]) { | |
var itemData = _data[singularName]; | |
var item = query.model.create(itemData); | |
item.id = itemData.id; | |
item._saved = true; | |
items.push(item); | |
} | |
else if (_data['error']) { | |
callback(new Error(_data['error']), null); | |
} | |
if (query.opts.limit === 1) { | |
if (items.length > 0) { | |
items = items[0]; | |
} | |
else { | |
items = null; | |
} | |
} | |
callback(null, items); | |
} | |
}; | |
this.update = function (data, query, callback) { | |
restApi.update(urlizedModelName(data.type) + '/' + data.id, { | |
data: data.toObj() | |
}, onUpdated); | |
function onUpdated(error, data) | |
{ | |
if (error) { | |
callback(error, data); | |
return; | |
} | |
callback(null, data); | |
} | |
}; | |
this.remove = function (query, callback) { | |
restApi.delete(urlizedModelName(query.model.modelName) + '/' + query.byId, onRemoved); | |
function onRemoved(error) | |
{ | |
if(error) { | |
callback(error); | |
return; | |
} | |
callback(null); | |
} | |
}; | |
this.insert = function (data, opts, callback) { | |
restApi.create(urlizedModelName(data.type), { | |
data: data.toObj() | |
}, onCreated); | |
function onCreated(error, _data) { | |
if (error) { | |
callback(error, null); | |
return; | |
} | |
if (_data['error']) { | |
callback(new Error(_data['error']), null); | |
return; | |
} | |
var resource = _data[data.type.toLowerCase()]; | |
data.updateProperties(resource); | |
data.id = resource.id | |
data._saved = true; | |
callback(null, data); | |
} | |
} | |
}; | |
module.exports.Adapter = Adapter; |
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
/** | |
* @class Rest | |
* @constructor | |
* | |
* @param String apiUrl - URL to Rest API | |
*/ | |
function Rest(apiUrl) | |
{ | |
/** | |
* @property String apiUrl - URL to Rest API | |
*/ | |
this.apiUrl = apiUrl || window.location.href.split('#')[0]; | |
} | |
/** | |
* @method ajax | |
* @param String method - HTTP method ('GET','POST','PUT','DELETE') | |
* @param String path - route/path to request | |
* @param Object options - (optional) | |
* @param Fuction callback - (optional) | |
* | |
* @options | |
* Object data - Data to send with the request | |
* Object query - mongodb-style query object | |
* Object sort - contains key value pairs, where values can be 'asc' or 'desc' | |
* Object limit | |
*/ | |
Rest.prototype.ajax = function(method, path, options, callback) | |
{ | |
if (typeof options === 'function') { | |
callback = options; | |
var options = {}; | |
} | |
if (typeof callback !== 'function') { | |
callback = function() {}; | |
} | |
if (typeof options !== 'object') { | |
var options = {}; | |
} | |
var sort = options.sort || null; | |
var query = options.query || null; | |
var limit = options.limit || null; | |
var skip = options.skip || null; | |
var data = options.data || null; | |
var forceUncached = (typeof options.forceUncached !== 'undefined') ? options.forceUncached : true; | |
method = method.toUpperCase(); | |
var url = this.apiUrl + path; | |
var urlQuery = []; | |
// prevent aggressive browser caching by appending timestamp | |
if (forceUncached) { | |
urlQuery.push('t=' + new Date().getTime()); | |
} | |
if (query) { | |
urlQuery.push('query=' + JSON.stringify(query)); | |
} | |
if (sort) { | |
urlQuery.push('sort=' + JSON.stringify(sort)); | |
} | |
if (limit) { | |
urlQuery.push('limit=' + limit); | |
} | |
if (skip) { | |
urlQuery.push('skip=' + skip); | |
} | |
// compensate for missing browser support of PUT and DELETE | |
if (method == 'PUT') { | |
method = 'POST'; | |
urlQuery.push('_method=PUT'); | |
} | |
else if(method == 'DELETE') { | |
method = 'POST'; | |
urlQuery.push('_method=DELETE'); | |
} | |
if (urlQuery.length > 0) { | |
if (url.indexOf('?') < 0) { | |
url += '?' + urlQuery.join('&'); | |
} | |
else { | |
url += '&' + urlQuery.join('&'); | |
} | |
} | |
$.ajax({ | |
type: method, | |
crossDomain: true, | |
dataType: 'json', | |
url: url, | |
data: data, | |
success: onSuccess | |
}); | |
function onSuccess(data, textStatus) | |
{ | |
if (textStatus === 'success') { | |
var error = null; | |
if (data['error']) { | |
error = new Error(data.error); | |
} | |
callback(error, data); | |
} | |
else { | |
callback(new Error(textStatus), data); | |
} | |
} | |
} | |
/** | |
* @method read | |
* Reads data | |
* | |
* @param String path - route/path to request | |
* @param Object options - (optional) | |
* @param Fuction callback - (optional) | |
* | |
* @options | |
* Object data - Data to send with the request | |
* Object query - mongodb-style query object | |
* Object sort - contains key value pairs, where values can be 'asc' or 'desc' | |
*/ | |
Rest.prototype.read = function(path, options, callback) | |
{ | |
this.ajax('GET', path, options, callback); | |
} | |
/** | |
* @method creates | |
* Creates a resource | |
* | |
* @param String path - route/path to request | |
* @param Object options - (optional) | |
* @param Fuction callback - (optional) | |
* | |
* @options | |
* Object data - Data to send with the request | |
*/ | |
Rest.prototype.create = function(path, options, callback) | |
{ | |
this.ajax('POST', path, options, callback); | |
} | |
/** | |
* @method update | |
* Updates a single resource | |
* | |
* @param String path - route/path to request | |
* @param Object options - (optional) | |
* @param Fuction callback - (optional) | |
* | |
* @options | |
* Object data - Data to send with the request | |
*/ | |
Rest.prototype.update = function(path, options, callback) | |
{ | |
this.ajax('PUT', path, options, callback); | |
} | |
/** | |
* @method delete | |
* Deletes a single resource | |
* | |
* @param String path - route/path to request | |
* @param Object options - (optional) | |
* @param Fuction callback - (optional) | |
* | |
* @options | |
* Object data - Data to send with the request | |
*/ | |
Rest.prototype.delete = function(path, options, callback) | |
{ | |
this.ajax('DELETE', path, options, callback); | |
} | |
module.exports = Rest; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment