Source: lib/resource/detail.js

/*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;