Skip to content

Instantly share code, notes, and snippets.

@der-On
Last active January 3, 2016 07:18
Show Gist options
  • Save der-On/8428136 to your computer and use it in GitHub Desktop.
Save der-On/8428136 to your computer and use it in GitHub Desktop.
RESTfull controllers in geddy.js
/*
* 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;
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;
/**
* @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