/*jshint laxcomma: true, smarttabs: true, node:true, unused:true, esnext:true */
'use strict';
/**
* Mixin class providing functionality for detail endpoints
* @module tastypie/lib/resource/detail
* @author Eric Satterwhite
* @since 1.0.1
* @requires boom
* @requires mout/collection/forEach
* @requires tastypie/lib/class
* @requires tastypie/lib/http
**/
var Boom = require('boom')
, each = require('mout/collection/forEach')
, Class = require('../class')
, http = require('../http')
, Detail
;
function annotate( err, bundle ){
if(!err){
return err;
}
err.req = bundle.req;
err.res = bundle.res;
return err;
}
function delete_db( bundle, err, data ){
if( err ){
err = annotate( err );
return this.emit( 'error', err );
}
if(!this.options.returnData){
return this.respond( bundle, http.noContent );
}
this.full_dehydrate(bundle.object, bundle, ( err, data ) => {
if( err ){
err = annotate( err, bundle );
return this.emit( 'error', err );
}
bundle.data = data;
this.options.cache.set(bundle.toKey( 'detail' ) , null );
return this.respond( bundle );
});
}
/**
* @mixin
* @alias module:tastypie/lib/resource/detail
*/
Detail = new Class({
delete_detail: function delete_detail( bundle ){
return this.get_object( bundle, ( err, obj ) => {
bundle.object = obj;
if( !obj ){
bundle.data = {
message: `No object found at ${bundle.req.path}`
,statusCode: 404
,error:'Not Found'
};
return this.respond(bundle, http.notFound);
}
this.remove_object( bundle, delete_db.bind( this, bundle ) );
});
}
/**
* Dispatches detail requests which operated on a sigular, specific object
* @method module:tastypie/lib/resource/detail#dispatch_detail
* @param {Request} req An express request object
* @param {Response} rep An express response object
* @param {Function} next An express next callback
**/
, dispatch_detail: function dispatch_detail( req, res ){
return this.dispatch( 'detail', this.bundle( req, res ) );
}
/**
* Top level method used to retreive indiidual objects by id.
* This method handles caching results as well as reading from the cache
* @where applicable
* @method module:tastypie/lib/resource/detail#get_detail
* @param {Bundle} bundle A bundle representing the current request.
**/
, get_detail: function get_detail( bundle ){
var that = this;
this.from_cache( 'detail', bundle, function( err, data ){
if( err ) return that.emit( 'error', annotate( err, bundle ) );
if( !data ){
let err = Boom.notFound(`No object found at ${bundle.req.path}`);
return that.emit( 'error', annotate( err, bundle ) );
}
that.full_dehydrate( data, bundle, function( err, data ){
bundle.data = data;
return that.respond( bundle );
});
});
}
/**
* Method used to retrieve a specific object
* **NOTE** This method *must* be implement for specific use cases. The default does not implement this method
* @method module:tastypie/lib/resource/detail#get_object
* @param {module:tastypie/lib/resource~Bundle} bundle
* @param {module:tastypie/lib/resource~Nodeback} callback
* @example
var MyResource = Resource.extend({
get_object: function( bundle, callback ){
Model.get( bundle.req.params.pk, callback )
}
})
* @example
var MyResource = new Class({
inherits: Resource
, get_object: function( bundle, callback ){
this.get_objects(bundle,function(e, objects){
var obj = JSON.parse( objects ).filter(function( obj ){
return obj._id = bundle.req.params.pk
})[0]
callback( null, obj )
})
}
})
**/
, get_object: function get_object( bundle, callback ){
var e = Boom.notImplemented("method get_object not implemented");
e = annotate( e, bundle );
e.next = bundle.next;
callback && callback( e );
}
/**
* High level function called in response to an OPTIONS request. Returns with an Allow header and empty body
* @method module:tastypie/lib/resource/detail#options_detail
* @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `OPTIONS` request
**/
, options_detail: function options_detail( bundle ){
bundle.data = null;
this.respond( bundle, null, null, ( err, reply ) => {
let methods = [];
each( this.options.allowed.detail,function( value, key ){
if( value ){
methods.push( key.toUpperCase() );
}
});
reply.header('Allow',methods.join(','));
});
}
/**
* high level function called to handle PATCH requests
* @method module:tastypie/lib/resource/detail#patch_detail
* @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `PATCH` request
**/
, patch_detail: function patch_detail( bundle ){
var response = http.ok
, that = this
, format
;
format = this.format( bundle, this.options.serializer.types );
this.deserialize( bundle.req.payload, format, function( err, data ){
bundle = that.bundle(bundle.req, bundle.res, data );
// update_object must save the object
that.update_object( bundle, function( err, bndl ){
if( err ){
err.req = bundle.req;
err.res = bundle.res;
return that.emit( 'error', err );
}
if( !that.options.returnData ){
bundle.data = null;
response = http.noContent;
} else {
that.full_dehydrate( bndl.object, bndl, function( err, data ){
if( err ){
err.req = bundle.req;
err.res = bundle.res;
return that.emit( 'error', err );
}
bndl.data = data;
return that.respond( bndl, response );
});
}
});
});
}
/**
* Top level method used to handle post request to listing endpoints. Used to update instances with supplied data
* @method module:tastypie/lib/resource/detail#put_detail
* @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `PUT` request
**/
, put_detail: function put_detail( bundle ){
var response = http.ok
, that = this
, format
;
format = this.format( bundle, this.options.serializer.types );
this.deserialize( bundle.req.payload, format, function( err, data ){
bundle = that.bundle(bundle.req, bundle.res, data );
// replace_object must save the object
that.replace_object( bundle, function( err, bndl ){
if( err ){
err = annotate(err, bundle);
return that.emit( 'error', err );
}
if( !that.options.returnData ){
bundle.data = null;
response = http.noContent;
}
that.full_dehydrate( bndl.object, bndl, function( err, data ){
if( err ){
err = annotate(err, bundle);
return that.emit( 'error', err );
}
bndl.data = data;
return that.respond( bndl, response );
});
});
});
}
/**
* Low level function called to delete existing objects
* @method module:tastypie/lib/resource/detail#remove_object
* @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `DELETE` request
* @param {module:tastypie/lib/resource~NodeBack} callback a callback to be caled when finished
**/
, remove_object: function( bundle, callback ){
var e = Boom.notImplemented('method remove_object not implemented');
e = annotate(e, bundle);
return callback && callback( e, null );
}
/**
* Low level function called to replace existing objects
* @method module:tastypie/lib/resource/detail#replace_object
* @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `PUT` request
* @param {module:tastypie/lib/resource~NodeBack} callback a callback to be caled when finished
**/
, replace_object: function replace_object( bundle, callback ){
var e = Boom.notImplemented("method replace_object not implemented");
e = annotate(e, bundle);
return callback && callback( e, null );
}
/**
* Method that is responsibe for updating a specific object during a *PUT* request
* @method module:tastypie/lib/resource/detail#update_object
* @param {module:tastypie/lib/resource~Bundle} bundle The data bundle representing the current request
* @param {module:tastypie/lib/resource~Nodeback} callback callback to be called when the operation finishes.
**/
, update_object: function update_object( bundle, callback ){
var e = Boom.notImplemented("method update_object not implemented");
e = annotate(e, bundle);
return callback && callback( e, null );
}
});
module.exports = Detail;