Source: client.js

var querystring = require("querystring");

var oauth = require("oauth");

/**
 * @callback client~connectCallback
 * @param {(string|null)} error - error message or null
 * @param {(string|null)} authentication_url - the oauth authentication url or null
 */

/**
 * @callback client~verifyCallback
 * @param {(string|null)} error - error message or null
 */

/**
 * @callback client~resultsCallback
 * @param {(string|null)} error - error message or null
 * @param {(object|null)} results - the results from the api call
 */

/**
 * Represents an API Client
 *
 * @constructor
 * @param {object} [options={}] - setup options
 * @param {client~connectCallback} [callback] - call client.connect immediately after setup, passing it callback
 */
var client = function(options, callback){
    if(!(this instanceof client)){
        return new client(options, callback);
    }
    if(typeof(options) == "function"){
        callback = options;
        options = {};
    }
    options = options || {};
    this.consumerKey = options.consumerKey;
    this.consumerSecret = options.consumerSecret;
    this.oauthToken = options.oauthToken || null;
    this.oauthSecret = options.oauthSecret || null;
    this.authorizationCallback = options.authorizationCallback || null;
    this.baseUrl = options.baseUrl || "https://api.shapeways.com";
    this.apiVersion = options.apiVersion || "v1";

    // Remove trailing slash from baseUrl
    this.baseUrl = this.baseUrl.replace(/\/$/, "");

    this.connection = new oauth.OAuth(
        this.url("/oauth1/request_token/"),
        this.url("/oauth1/access_token/"),
        this.consumerKey,
        this.consumerSecret,
        "1.0A",
        this.authorizationCallback,
        "HMAC-SHA1"
    );

    if(typeof(callback) == "function"){
        this.connect(callback);
    }
};

/**
 * Build a full API url from the portion that is given
 *
 * @method url
 * @memberof client
 * @instance client
 * @param {string} path - the api path portion to build
 * @param {object} [params] - Object of query string parameters to use
 * @return {string} Returns full API url from path and params
 **/
client.prototype.url = function(path, params){
    if(path.indexOf("/") !== 0){
        path = "/" + path;
    }

    if(path.indexOf("/", path.length - 1) === -1){
        path += "/";
    }

    var url = this.baseUrl + path + this.apiVersion;
    if(params){
        url += "?" + querystring.stringify(params);
    }
    return url;
};

/**
 * Connect to API server and get OAuth request token and authentication url
 *
 * @method connect
 * @memberof client
 * @instance client
 * @param {client~connectCallback} [callback] - function to call when finished
 **/
client.prototype.connect = function(callback){
    var self = this;
    if(typeof(callback) != "function"){
        callback = function(){};
    }
    this.connection.getOAuthRequestToken(function(error, token, secret, results){
        if(error){
            return callback(error, null);
        }

        self.oauthToken = token;
        self.oauthSecret = secret;
        callback(null, results.authentication_url);
    });
};

/**
 * Parse the required parameters from OAuth authentication response url and
 * pass to client.verify
 *
 * @method verifyUrl
 * @memberof client
 * @instance client
 * @param {client~verifyCallback} [callback] - function to call when finished
 **/
client.prototype.verifyUrl = function(url, callback){
    var query = querystring.parse(url.split("?", 2)[1]);
    return this.verify(query.oauth_token, query.oauth_verifier, callback);
};

/**
 * Request an OAuth access token with the provided oauth_token and oauth_verifier parameters
 *
 * @method verify
 * @memberof client
 * @instance client
 *
 * @param {string} token - oauth_token from authentication response query string
 * @param {string} verifier - oauth_verifier from authentication response query string
 * @param {client~verifyCallback} [callback] - function to call when finished
 **/
client.prototype.verify = function(token, verifier, callback){
    if(typeof(callback) != "function"){
        callback = function(){};
    }

    var self = this;
    this.connection.getOAuthAccessToken(token, this.oauthSecret, verifier, function(error, token, secret, results){
        if(error){
            return callback(error);
        }

        self.oauthToken = token;
        self.oauthSecret = secret;
        callback(null);
    });
};

/**
 * Make a HTTP GET request to the url using the OAuth credentials
 *
 * @method get
 * @memberof client
 * @instance client
 *
 * @param {string} url - the url to make the request to
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.get = function(url, callback){
    if(!callback){
        callback = function(){};
    }

    if(!this.oauthToken){
        return callback("no oauth token, make sure to call client.connect and client.verify");
    }

    this.connection.get(url, this.oauthToken, this.oauthSecret, function(error, data, result){
        if(error){
            return callback(error);
        }

        callback(null, JSON.parse(data));
    });
};

/**
 * Make a HTTP DELETE request to the url using the OAuth credentials
 *
 * @method delete
 * @memberof client
 * @instance client
 *
 * @param {string} url - the url to make the request to
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.delete = function(url, callback){
    if(!callback){
        callback = function(){};
    }

    if(!this.oauthToken){
        return callback("no oauth token, make sure to call client.connect and client.verify");
    }

    this.connection.delete(url, this.oauthToken, this.oauthSecret, function(error, data, result){
        if(error){
            return callback(error);
        }

        callback(null, JSON.parse(data));
    });
};

/**
 * Make a HTTP POST request to the url using the OAuth credentials
 *
 * @method post
 * @memberof client
 * @instance client
 *
 * @param {string} url - the url to make the request to
 * @param {string} [body] - the POST body data to send with the request
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.post = function(url, body, callback){
    if(typeof(body) == "function"){
        callback = body;
        body = null;
    }

    if(!callback){
        callback = function(){};
    }

    if(!this.oauthToken){
        return callback("no oauth token, make sure to call client.connect and client.verify");
    }

    this.connection.post(url, this.oauthToken, this.oauthSecret, body, function(error, data, result){
        if(error){
            return callback(error);
        }

        callback(null, JSON.parse(data));
    });
};

/**
 * Make a HTTP PUT request to the url using the OAuth credentials
 *
 * @method put
 * @memberof client
 * @instance client
 *
 * @param {string} url - the url to make the request to
 * @param {string} [body] - the PUT body data to send with the request
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.put = function(url, body, callback){
    if(typeof(body) == "function"){
        callback = body;
        body = null;
    }

    if(!callback){
        callback = function(){};
    }

    if(!this.oauthToken){
        return callback("no oauth token, make sure to call client.connect and client.verify");
    }

    this.connection.put(url, this.oauthToken, this.oauthSecret, body, function(error, data, result){
        if(error){
            return callback(error);
        }

        callback(null, JSON.parse(data));
    });
};

/**
 * Make an API call [GET /api/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-api-v1-1}
 *
 * @method getApiInfo
 * @memberof client
 * @instance client
 *
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getApiInfo = function(callback){
    this.get(this.url("/api/"), callback);
};

/**
 * Make an API call [GET /orders/cart/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-orders-cart-v1}
 *
 * @method getCart
 * @memberof client
 * @instance client
 *
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getCart = function(callback){
    this.get(this.url("/orders/cart/"), callback);
};

/**
 * Make an API call [POST /orders/cart/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#POST_-orders-cart-v1}
 *
 * @method addToCart
 * @memberof client
 * @instance client
 *
 * @param {object} params - parameters to send with the query
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.addToCart = function(params, callback){
    if(!params.modelId){
        return callback("params is missing required modelId parameter");
    }

    this.post(this.url("/orders/cart/", params), null, callback);
};

/**
 * Make an API call [GET /materials/{materialId}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-materials-materialId-v1}
 *
 * @method getMaterial
 * @memberof client
 * @instance client
 *
 * @param {number} materialId - material id of the material to get
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getMaterial = function(materialId, callback){
    this.get(this.url("/materials/" + materialId), callback);
};

/**
 * Make an API call [GET /materials/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-materials-v1}
 *
 * @method getMaterials
 * @memberof client
 * @instance client
 *
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getMaterials = function(callback){
    this.get(this.url("/materials/"), callback);
};

/**
 * Make an API call [GET /models/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-models-v1}
 *
 * @method getModels
 * @memberof client
 * @instance client
 *
 * @param {number} [page] - which page to fetch
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getModels = function(page, callback){
    var params = {};
    if(typeof(page) == "function"){
        callback = page;
        page = null;
    }

    if(page){
        params = {page: page};
    }
    this.get(this.url("/models/", params), callback);
};

/**
 * Make an API call [GET /models/{modelId}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-v1}
 *
 * @method getModel
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model to fetch
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getModel = function(modelId, callback){
    this.get(this.url("/models/" + modelId), callback);
};

/**
 * Make an API call [POST /models/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#POST_-models-v1}
 *
 * The `file` parameter is base64 + URI encoded before sending to server
 *
 * @method addModel
 * @memberof client
 * @instance client
 *
 * @param {object} params - the model data to use
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.addModel = function(params, callback){
    var required = ["file", "fileName", "hasRightsToModel", "acceptTermsAndConditions"];
    var missing = [];
    required.forEach(function(property){
        if(!(property in params)){
            missing.push(property);
        }
    });
    if(missing.length){
        return callback("error, params missing required parameters: " + missing);
    }

    params.file = encodeURI(new Buffer(params.file).toString("base64"));
    this.post(this.url("/models/"), JSON.stringify(params), callback);
};

/**
 * Make an API call [GET /models/{modelId}/info/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-info-v1}
 *
 * @method getModelInfo
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model to fetch information for
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getModelInfo = function(modelId, callback){
    this.get(this.url("/models/" + modelId + "/info/"), callback);
};

/**
 * Make an API call [DELETE /models/{modelId}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#DELETE_-models-modelId-v1}
 *
 * @method deleteModel
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model to delete
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.deleteModel = function(modelId, callback){
    this.delete(this.url("/models/" + modelId), callback);
};

/**
 * Make an API call [PUT /models/{modelId}/info/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#PUT_-models-modelId-info-v1}
 *
 * @method updateModelInfo
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model to update
 * @param {object} params - model data to update with
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.updateModelInfo = function(modelId, params, callback){
    this.put(this.url("/models/" + modelId), JSON.stringify(params), callback);
};

/**
 * Make an API call [GET /models/{modelId}/files/{fileVersion}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-models-modelId-files-fileVersion-v1}
 *
 * @method getModelFile
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model the file belongs to
 * @param {int} fileVersion - the version of the file to get
 * @param {boolean} [includeFile=false] - whether or not to include the raw file data with the response
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getModelFile = function(modelId, fileVersion, includeFile, callback){
    if(typeof(includeFile) == "function"){
        callback = includeFile;
        includeFile = 0;
    }
    var params = {
        file: +includeFile,
    };
    this.get(this.url("/models/" + modelId + "/files/" + fileVersion, params), callback);
};

/**
 * Make an API call [POST /models/{modelId}/photos/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#POST_-models-modelId-photos-v1}
 *
 * The `file` parameter is base64 + URI encoded before sending to server
 *
 * @method addModelPhoto
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model the photo belongs to
 * @param {object} params - the photo information
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.addModelPhoto = function(modelId, params, callback){
    if(!("file" in params)){
        return callback("error, params missing required property: file");
    }

    params.file = encodeURI(new Buffer(params.file).toString("base64"));
    this.post(this.url("/models/" + modelId + "/photos/"), JSON.stringify(params), callback);
};

/**
 * Make an API call [POST /models/{modelId}/files/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#POST_-models-modelId-files-v1}
 *
 * The `file` parameter is base64 + URI encoded before sending to server
 *
 * @method addModelFile
 * @memberof client
 * @instance client
 *
 * @param {number} modelId - the model the file belongs to
 * @param {object} params - the file information
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.addModelFile = function(modelId, params, callback){
    var required = ["file", "fileName", "hasRightsToModel", "acceptTermsAndConditions"];
    var missing = [];
    required.forEach(function(property){
        if(!(property in params)){
            missing.push(property);
        }
    });
    if(missing.length){
        return callback("error, params missing required parameters: " + missing);
    }

    params.file = encodeURI(new Buffer(params.file).toString("base64"));
    this.post(this.url("/models/" + modelId + "/files/"), JSON.stringify(params), callback);
};

/**
 * Make an API call [GET /printers/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-printers-v1}
 *
 * @method getPrinters
 * @memberof client
 * @instance client
 *
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getPrinters = function(callback){
    this.get(this.url("/printers/"), callback);
};

/**
 * Make an API call [GET /printers/{printerId}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-printers-printerId-v1}
 *
 * @method getPrinters
 * @memberof client
 * @instance client
 *
 * @param {number} printerId - the printer id to get information for
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getPrinter = function(printerId, callback){
    this.get(this.url("/printers/" + printerId), callback);
};

/**
 * Make an API call [GET /categories/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-categories-v1}
 *
 * @method getCategories
 * @memberof client
 * @instance client
 *
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getCategories = function(callback){
    this.get(this.url("/categories/"), callback);
};

/**
 * Make an API call [GET /categories/{categoryId}/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#GET_-categories-categoryId-v1}
 *
 * @method getCategory
 * @memberof client
 * @instance client
 *
 * @param {number} catId - the category id to fetch
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getCategory = function(catId, callback){
    this.get(this.url("/categories/" + catId), callback);
};

/**
 * Make an API call [POST /price/v1]{@link https://developers.shapeways.com/docs?li=dh_docs#POST_-price-v1}
 *
 * @method gePrice
 * @memberof client
 * @instance client
 *
 * @param {object} params - the pricing information to get pricing for
 * @param {client~resultsCallback} [callback] - function to call when finished
 **/
client.prototype.getPrice = function(params, callback){
    var required = ["volume", "area", "xBoundMin", "xBoundMax", "yBoundMin", "yBoundMax", "zBoundMin", "zBoundMax"];
    var missing = [];
    required.forEach(function(property){
        if(!(property in params)){
            missing.push(property);
        }
    });
    if(missing.length){
        return callback("error, params missing required properties: " + missing);
    }

    this.post(this.url("/price/"), JSON.stringify(params), callback);
};

module.exports = client;