diff --git a/lib-es5/utils/index.js b/lib-es5/utils/index.js index 25978c8c..49a41614 100644 --- a/lib-es5/utils/index.js +++ b/lib-es5/utils/index.js @@ -466,6 +466,7 @@ function build_eager(transformations) { return format == null ? transformationString : `${transformationString}/${format}`; }).join('|'); } + /** * Build the custom headers for the request * @private @@ -791,6 +792,24 @@ function patchFetchFormat() { } } +function build_distribution_domain(options) { + var source = consumeOption(options, 'source', ''); + var cloud_name = consumeOption(options, 'cloud_name', config().cloud_name); + + if (!cloud_name) { + throw new Error('Must supply cloud_name in tag or in configuration'); + } + + var secure = consumeOption(options, 'secure', config().secure); + var private_cdn = consumeOption(options, 'private_cdn', config().private_cdn); + var cname = consumeOption(options, 'cname', config().cname); + var secure_distribution = consumeOption(options, 'secure_distribution', config().secure_distribution); + var cdn_subdomain = consumeOption(options, 'cdn_subdomain', config().cdn_subdomain); + var secure_cdn_subdomain = consumeOption(options, 'secure_cdn_subdomain', config().secure_cdn_subdomain); + + return unsigned_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution); +} + function url(public_id) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; @@ -886,7 +905,7 @@ function url(public_id) { } // eslint-disable-next-line no-empty } catch (error) {} - var hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64'); + var hash = compute_hash(to_sign + api_secret, signature_algorithm, 'base64'); signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8); signature = `s--${signature}--`; } @@ -1002,6 +1021,7 @@ function finalize_resource_type(resource_type, type, url_suffix, use_root_path, } return [resource_type, type]; } + // cdn_subdomain and secure_cdn_subdomain // 1) Customers in shared distribution (e.g. res.cloudinary.com) // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. @@ -1092,7 +1112,7 @@ function api_sign_request(params_to_sign, api_secret) { return `${k}=${toArray(v).join(",")}`; }).sort().join("&"); - return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); + return compute_hash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); } /** @@ -1103,7 +1123,7 @@ function api_sign_request(params_to_sign, api_secret) { * @param {string} encoding type of encoding * @return {string} computed hash value */ -function computeHash(input, signature_algorithm, encoding) { +function compute_hash(input, signature_algorithm, encoding) { if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) { throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`); } @@ -1132,6 +1152,13 @@ function clear_blank(hash) { return filtered_hash; } +function sort_object_by_key(object) { + return Object.keys(object).sort().reduce(function (obj, key) { + obj[key] = object[key]; + return obj; + }, {}); +} + function merge(hash1, hash2) { return _extends({}, hash1, hash2); } @@ -1150,11 +1177,14 @@ function sign_request(params) { function webhook_signature(data, timestamp) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - ensurePresenceOf({ data, timestamp }); + ensurePresenceOf({ + data, + timestamp + }); var api_secret = ensureOption(options, 'api_secret'); var signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM); - return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex'); + return compute_hash(data + timestamp + api_secret, signature_algorithm, 'hex'); } /** @@ -1253,7 +1283,9 @@ function download_backedup_asset(asset_id, version_id) { * @param options */ function api_download_url(action, params, options) { - var download_params = _extends({}, params, { mode: "download" }); + var download_params = _extends({}, params, { + mode: "download" + }); var cloudinary_params = exports.sign_request(download_params, options); return exports.api_url(action, options) + "?" + hashToQuery(cloudinary_params); } @@ -1487,6 +1519,7 @@ function process_video_params(param) { return null; } } + /** * Returns a Hash of parameters used to create an archive * @private @@ -1530,7 +1563,10 @@ exports.create_source_tag = function create_source_tag(src, source_type) { var codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; mime_type += `; codecs=${codecs_str}`; } - return ``; + return ``; }; function build_explicit_api_params(public_id) { @@ -1726,6 +1762,9 @@ exports.jsonArrayParam = jsonArrayParam; exports.download_folder = download_folder; exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; +exports.compute_hash = compute_hash; +exports.build_distribution_domain = build_distribution_domain; +exports.sort_object_by_key = sort_object_by_key; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib-es5/v2/search.js b/lib-es5/v2/search.js index 248063ee..de7832a4 100644 --- a/lib-es5/v2/search.js +++ b/lib-es5/v2/search.js @@ -5,10 +5,18 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var api = require('./api'); +var config = require('../config'); var _require = require('../utils'), isEmpty = _require.isEmpty, - isNumber = _require.isNumber; + isNumber = _require.isNumber, + compute_hash = _require.compute_hash, + build_distribution_domain = _require.build_distribution_domain, + clear_blank = _require.clear_blank, + sort_object_by_key = _require.sort_object_by_key; + +var _require2 = require('../utils/encoding/base64Encode'), + base64Encode = _require2.base64Encode; var Search = function () { function Search() { @@ -19,6 +27,7 @@ var Search = function () { aggregate: [], with_field: [] }; + this._ttl = 300; } _createClass(Search, [{ @@ -89,6 +98,16 @@ var Search = function () { return this; } + }, { + key: 'ttl', + value: function ttl(newTtl) { + if (isNumber(newTtl)) { + this._ttl = newTtl; + return this; + } + + throw new Error('New TTL value has to be a Number.'); + } }, { key: 'to_query', value: function to_query() { @@ -111,6 +130,36 @@ var Search = function () { options = options || {}; return api.search(this.to_query(), options, callback); } + }, { + key: 'to_url', + value: function to_url(ttl, next_cursor) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + var apiSecret = 'api_secret' in options ? options.api_secret : config().api_secret; + if (!apiSecret) { + throw new Error('Must supply api_secret'); + } + + var urlTtl = ttl || this._ttl; + + var query = this.to_query(); + + var urlCursor = next_cursor; + if (query.next_cursor && !next_cursor) { + urlCursor = query.next_cursor; + } + delete query.next_cursor; + + var dataOrderedByKey = sort_object_by_key(clear_blank(query)); + var encodedQuery = base64Encode(JSON.stringify(dataOrderedByKey)); + + var urlPrefix = build_distribution_domain(options); + + var signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, 'sha256', 'hex'); + + var urlWithoutCursor = `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}`; + return urlCursor ? `${urlWithoutCursor}/${urlCursor}` : urlWithoutCursor; + } }], [{ key: 'instance', value: function instance() { @@ -148,6 +197,11 @@ var Search = function () { return this.instance().sort_by(field_name, dir); } + }, { + key: 'ttl', + value: function ttl(newTtl) { + return this.instance().ttl(newTtl); + } }]); return Search; diff --git a/lib/utils/index.js b/lib/utils/index.js index 9497baf3..63d5dbfc 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -33,7 +33,7 @@ const isUndefined = require("lodash/isUndefined"); const smart_escape = require("./encoding/smart_escape"); const consumeOption = require('./parsing/consumeOption'); const toArray = require('./parsing/toArray'); -let { base64EncodeURL } = require('./encoding/base64EncodeURL'); +let {base64EncodeURL} = require('./encoding/base64EncodeURL'); const encodeDoubleArray = require('./encoding/encodeDoubleArray'); const config = require("../config"); @@ -45,7 +45,10 @@ const ensureOption = require('./ensureOption').defaults(config()); const entries = require('./entries'); const isRemoteUrl = require('./isRemoteUrl'); const getSDKVersions = require('./encoding/sdkAnalytics/getSDKVersions'); -const {getAnalyticsOptions, getSDKAnalyticsSignature} = require('cloudinary-core').Util; +const { + getAnalyticsOptions, + getSDKAnalyticsSignature +} = require('cloudinary-core').Util; exports = module.exports; const utils = module.exports; @@ -373,7 +376,9 @@ function build_upload_params(options) { } function encode_key_value(arg) { - if (!isObject(arg)) { return arg; } + if (!isObject(arg)) { + return arg; + } return entries(arg).map(([k, v]) => `${k}=${v}`).join('|'); } @@ -403,7 +408,9 @@ function escapeMetadataValue(value) { * @return {string} */ function encode_context(metadataObj) { - if (!isObject(metadataObj)) { return metadataObj; } + if (!isObject(metadataObj)) { + return metadataObj; + } return entries(metadataObj).map(([key, value]) => { // if string, simply parse the value and move on @@ -434,6 +441,7 @@ function build_eager(transformations) { return format == null ? transformationString : `${transformationString}/${format}`; }).join('|'); } + /** * Build the custom headers for the request * @private @@ -488,7 +496,7 @@ function generate_transformation_string(options) { let named_transformation = []; if (base_transformations.some(isObject)) { base_transformations = base_transformations.map(tr => utils.generate_transformation_string( - isObject(tr) ? clone(tr) : { transformation: tr } + isObject(tr) ? clone(tr) : {transformation: tr} )); } else { named_transformation = base_transformations.join("."); @@ -655,13 +663,13 @@ function updateable_resource_params(options, params = {}) { if (options.quality_override != null) { params.quality_override = options.quality_override; } - if (options.asset_folder != null){ + if (options.asset_folder != null) { params.asset_folder = options.asset_folder; } - if (options.display_name != null){ + if (options.display_name != null) { params.display_name = options.display_name; } - if (options.unique_display_name != null){ + if (options.unique_display_name != null) { params.unique_display_name = options.unique_display_name; } return params; @@ -727,6 +735,27 @@ function patchFetchFormat(options = {}) { } } +function build_distribution_domain(source, options) { + const cloud_name = consumeOption(options, 'cloud_name', config().cloud_name); + if (!cloud_name) { + throw new Error('Must supply cloud_name in tag or in configuration'); + } + + let secure = consumeOption(options, 'secure', null); + const ssl_detected = consumeOption(options, 'ssl_detected', config().ssl_detected); + if (secure === null) { + secure = ssl_detected || config().secure; + } + + const private_cdn = consumeOption(options, 'private_cdn', config().private_cdn); + const cname = consumeOption(options, 'cname', config().cname); + const secure_distribution = consumeOption(options, 'secure_distribution', config().secure_distribution); + const cdn_subdomain = consumeOption(options, 'cdn_subdomain', config().cdn_subdomain); + const secure_cdn_subdomain = consumeOption(options, 'secure_cdn_subdomain', config().secure_cdn_subdomain); + + return unsigned_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution); +} + function url(public_id, options = {}) { let signature, source_to_sign; utils.patchFetchFormat(options); @@ -741,20 +770,6 @@ function url(public_id, options = {}) { } let long_url_signature = !!consumeOption(options, "long_url_signature", config().long_url_signature); let format = consumeOption(options, "format"); - let cloud_name = consumeOption(options, "cloud_name", config().cloud_name); - if (!cloud_name) { - throw "Unknown cloud_name"; - } - let private_cdn = consumeOption(options, "private_cdn", config().private_cdn); - let secure_distribution = consumeOption(options, "secure_distribution", config().secure_distribution); - let secure = consumeOption(options, "secure", null); - let ssl_detected = consumeOption(options, "ssl_detected", config().ssl_detected); - if (secure === null) { - secure = ssl_detected || config().secure; - } - let cdn_subdomain = consumeOption(options, "cdn_subdomain", config().cdn_subdomain); - let secure_cdn_subdomain = consumeOption(options, "secure_cdn_subdomain", config().secure_cdn_subdomain); - let cname = consumeOption(options, "cname", config().cname); let shorten = consumeOption(options, "shorten", config().shorten); let sign_url = consumeOption(options, "sign_url", config().sign_url); let api_secret = consumeOption(options, "api_secret", config().api_secret); @@ -807,20 +822,11 @@ function url(public_id, options = {}) { // eslint-disable-next-line no-empty } catch (error) { } - let hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64'); + let hash = compute_hash(to_sign + api_secret, signature_algorithm, 'base64'); signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8); signature = `s--${signature}--`; } - let prefix = unsigned_url_prefix( - public_id, - cloud_name, - private_cdn, - cdn_subdomain, - secure_cdn_subdomain, - cname, - secure, - secure_distribution - ); + let prefix = build_distribution_domain(public_id, options); let resultUrl = [prefix, resource_type, type, signature, transformation, version, public_id].filter(function (part) { return (part != null) && part !== ''; }).join('/').replace(/ /g, '%20'); @@ -833,7 +839,11 @@ function url(public_id, options = {}) { let urlAnalytics = ensureOption(options, 'urlAnalytics', false); if (urlAnalytics === true) { - let { sdkCode, sdkSemver, techVersion } = getSDKVersions(); + let { + sdkCode, + sdkSemver, + techVersion + } = getSDKVersions(); let sdkVersions = { sdkCode: ensureOption(options, 'sdkCode', sdkCode), sdkSemver: ensureOption(options, 'sdkSemver', sdkSemver), @@ -930,6 +940,7 @@ function finalize_resource_type(resource_type, type, url_suffix, use_root_path, } return [resource_type, type]; } + // cdn_subdomain and secure_cdn_subdomain // 1) Customers in shared distribution (e.g. res.cloudinary.com) // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. @@ -986,7 +997,7 @@ function unsigned_url_prefix( return prefix; } -function base_api_url(path= [], options = {}) { +function base_api_url(path = [], options = {}) { let cloudinary = ensureOption(options, "upload_prefix", UPLOAD_PREFIX); let cloud_name = ensureOption(options, "cloud_name"); let encode_path = unencoded_path => encodeURIComponent(unencoded_path).replace("'", '%27'); @@ -1013,7 +1024,7 @@ function api_sign_request(params_to_sign, api_secret) { ).map( ([k, v]) => `${k}=${toArray(v).join(",")}` ).sort().join("&"); - return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); + return compute_hash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); } /** @@ -1024,7 +1035,7 @@ function api_sign_request(params_to_sign, api_secret) { * @param {string} encoding type of encoding * @return {string} computed hash value */ -function computeHash(input, signature_algorithm, encoding) { +function compute_hash(input, signature_algorithm, encoding) { if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) { throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`); } @@ -1045,8 +1056,15 @@ function clear_blank(hash) { return filtered_hash; } +function sort_object_by_key(object) { + return Object.keys(object).sort().reduce((obj, key) => { + obj[key] = object[key]; + return obj; + }, {}); +} + function merge(hash1, hash2) { - return { ...hash1, ...hash2 }; + return {...hash1, ...hash2}; } function sign_request(params, options = {}) { @@ -1059,11 +1077,14 @@ function sign_request(params, options = {}) { } function webhook_signature(data, timestamp, options = {}) { - ensurePresenceOf({ data, timestamp }); + ensurePresenceOf({ + data, + timestamp + }); let api_secret = ensureOption(options, 'api_secret'); let signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM); - return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex'); + return compute_hash(data + timestamp + api_secret, signature_algorithm, 'hex'); } /** @@ -1154,7 +1175,10 @@ function download_backedup_asset(asset_id, version_id, options = {}) { * @param options */ function api_download_url(action, params, options) { - const download_params = {...params, mode: "download"} + const download_params = { + ...params, + mode: "download" + } let cloudinary_params = exports.sign_request(download_params, options); return exports.api_url(action, options) + "?" + hashToQuery(cloudinary_params); } @@ -1234,7 +1258,9 @@ function download_folder(folder_path, options = {}) { * @return {string} A string representing the HTML attribute */ function join_pair(key, value) { - if (!value) { return void 0; } + if (!value) { + return void 0; + } return value === true ? key : key + "='" + value + "'"; } @@ -1267,7 +1293,9 @@ function cloudinary_js_config() { } function v1_result_adapter(callback) { - if (callback == null) { return undefined; } + if (callback == null) { + return undefined; + } return function (result) { if (result.error != null) { return callback(result.error); @@ -1371,6 +1399,7 @@ function process_video_params(param) { return null; } } + /** * Returns a Hash of parameters used to create an archive * @private @@ -1410,11 +1439,14 @@ exports.create_source_tag = function create_source_tag(src, source_type, codecs let codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; mime_type += `; codecs=${codecs_str}`; } - return ``; + return ``; }; function build_explicit_api_params(public_id, options = {}) { - return [exports.build_upload_params(extend({}, { public_id }, options))]; + return [exports.build_upload_params(extend({}, {public_id}, options))]; } function generate_responsive_breakpoints_string(breakpoints) { @@ -1537,7 +1569,8 @@ function jsonArrayParam(data, modifier) { * Empty function - do nothing * */ -exports.NOP = function () {}; +exports.NOP = function () { +}; exports.generate_auth_token = generate_auth_token; exports.getUserAgent = getUserAgent; exports.build_upload_params = build_upload_params; @@ -1588,6 +1621,9 @@ exports.jsonArrayParam = jsonArrayParam; exports.download_folder = download_folder; exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; +exports.compute_hash = compute_hash; +exports.build_distribution_domain = build_distribution_domain; +exports.sort_object_by_key = sort_object_by_key; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib/v2/search.js b/lib/v2/search.js index 2ac64a39..87b1ae55 100644 --- a/lib/v2/search.js +++ b/lib/v2/search.js @@ -1,5 +1,14 @@ const api = require('./api'); -const {isEmpty, isNumber} = require('../utils'); +const config = require('../config'); +const { + isEmpty, + isNumber, + compute_hash, + build_distribution_domain, + clear_blank, + sort_object_by_key +} = require('../utils'); +const {base64Encode} = require('../utils/encoding/base64Encode'); const Search = class Search { constructor() { @@ -8,6 +17,7 @@ const Search = class Search { aggregate: [], with_field: [] }; + this._ttl = 300; } static instance() { @@ -38,6 +48,10 @@ const Search = class Search { return this.instance().sort_by(field_name, dir); } + static ttl(newTtl) { + return this.instance().ttl(newTtl); + } + expression(value) { this.query_hash.expression = value; return this; @@ -92,6 +106,15 @@ const Search = class Search { return this; } + ttl(newTtl) { + if (isNumber(newTtl)) { + this._ttl = newTtl; + return this; + } + + throw new Error('New TTL value has to be a Number.'); + } + to_query() { Object.keys(this.query_hash).forEach((k) => { let v = this.query_hash[k]; @@ -109,6 +132,33 @@ const Search = class Search { options = options || {}; return api.search(this.to_query(), options, callback); } + + to_url(ttl, next_cursor, options = {}) { + const apiSecret = 'api_secret' in options ? options.api_secret : config().api_secret; + if (!apiSecret) { + throw new Error('Must supply api_secret'); + } + + const urlTtl = ttl || this._ttl; + + const query = this.to_query(); + + let urlCursor = next_cursor; + if (query.next_cursor && !next_cursor) { + urlCursor = query.next_cursor; + } + delete query.next_cursor; + + const dataOrderedByKey = sort_object_by_key(clear_blank(query)); + const encodedQuery = base64Encode(JSON.stringify(dataOrderedByKey)); + + const urlPrefix = build_distribution_domain(options.source, options); + + const signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, 'sha256', 'hex'); + + const urlWithoutCursor = `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}`; + return urlCursor ? `${urlWithoutCursor}/${urlCursor}` : urlWithoutCursor; + } }; module.exports = Search; diff --git a/test/integration/api/search/search_spec.js b/test/integration/api/search/search_spec.js index 00394fea..ef05ca7e 100644 --- a/test/integration/api/search/search_spec.js +++ b/test/integration/api/search/search_spec.js @@ -3,6 +3,8 @@ const cloudinary = require('../../../../cloudinary'); const helper = require("../../../spechelper"); const testConstants = require('../../../testUtils/testConstants'); const describe = require('../../../testUtils/suite'); +const exp = require("constants"); +const cluster = require("cluster"); const { TIMEOUT, TAGS, @@ -24,66 +26,6 @@ const SEARCH_TAG = 'npm_advanced_search_' + UNIQUE_JOB_SUFFIX_ID; const ASSET_IDS = []; describe("search_api", function () { - describe("unit", function () { - it('should create empty json', function () { - var query_hash = cloudinary.v2.search.instance().to_query(); - expect(query_hash).to.eql({}); - }); - it('should always return same object in fluent interface', function () { - let instance = cloudinary.v2.search.instance(); - [ - 'expression', - 'sort_by', - 'max_results', - 'next_cursor', - 'aggregate', - 'with_field' - ].forEach(method => expect(instance).to.eql(instance[method]('emptyarg'))); - }); - it('should add expression to query', function () { - var query = cloudinary.v2.search.expression('format:jpg').to_query(); - expect(query).to.eql({ - expression: 'format:jpg' - }); - }); - it('should add sort_by to query', function () { - var query = cloudinary.v2.search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_query(); - expect(query).to.eql({ - sort_by: [ - { - created_at: 'asc' - }, - { - updated_at: 'desc' - } - ] - }); - }); - it('should add max_results to query', function () { - var query = cloudinary.v2.search.max_results('format:jpg').to_query(); - expect(query).to.eql({ - max_results: 'format:jpg' - }); - }); - it('should add next_cursor to query', function () { - var query = cloudinary.v2.search.next_cursor('format:jpg').to_query(); - expect(query).to.eql({ - next_cursor: 'format:jpg' - }); - }); - it('should add aggregate arguments as array to query', function () { - var query = cloudinary.v2.search.aggregate('format').aggregate('size_category').to_query(); - expect(query).to.eql({ - aggregate: ['format', 'size_category'] - }); - }); - it('should add with_field to query', function () { - var query = cloudinary.v2.search.with_field('context').with_field('tags').to_query(); - expect(query).to.eql({ - with_field: ['context', 'tags'] - }); - }); - }); describe("integration", function () { this.timeout(TIMEOUT.LONG); before(function () { @@ -111,7 +53,7 @@ describe("search_api", function () { }) ]).delay(10000) .then((uploadResults) => { - uploadResults.forEach(({ value }) => { + uploadResults.forEach(({value}) => { ASSET_IDS.push(value.asset_id); }); }); diff --git a/test/unit/search/search_spec.js b/test/unit/search/search_spec.js new file mode 100644 index 00000000..5785f8c7 --- /dev/null +++ b/test/unit/search/search_spec.js @@ -0,0 +1,134 @@ +const cloudinary = require('../../../cloudinary'); + +describe('Search', () => { + it('should create empty json', function () { + var query_hash = cloudinary.v2.search.instance().to_query(); + expect(query_hash).to.eql({}); + }); + + it('should always return same object in fluent interface', function () { + let instance = cloudinary.v2.search.instance(); + [ + 'expression', + 'sort_by', + 'max_results', + 'next_cursor', + 'aggregate', + 'with_field' + ].forEach(method => expect(instance).to.eql(instance[method]('emptyarg'))); + }); + + it('should add expression to query', function () { + var query = cloudinary.v2.search.expression('format:jpg').to_query(); + expect(query).to.eql({ + expression: 'format:jpg' + }); + }); + + it('should add sort_by to query', function () { + var query = cloudinary.v2.search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_query(); + expect(query).to.eql({ + sort_by: [ + { + created_at: 'asc' + }, + { + updated_at: 'desc' + } + ] + }); + }); + + it('should add max_results to query', function () { + var query = cloudinary.v2.search.max_results('format:jpg').to_query(); + expect(query).to.eql({ + max_results: 'format:jpg' + }); + }); + + it('should add next_cursor to query', function () { + var query = cloudinary.v2.search.next_cursor('format:jpg').to_query(); + expect(query).to.eql({ + next_cursor: 'format:jpg' + }); + }); + + it('should add aggregate arguments as array to query', function () { + var query = cloudinary.v2.search.aggregate('format').aggregate('size_category').to_query(); + expect(query).to.eql({ + aggregate: ['format', 'size_category'] + }); + }); + + it('should add with_field to query', function () { + var query = cloudinary.v2.search.with_field('context').with_field('tags').to_query(); + expect(query).to.eql({ + with_field: ['context', 'tags'] + }); + }); + + describe('to_url', () => { + const cloudName = 'test123'; + + const commonUrlOptions = { + secure: true, + cloud_name: cloudName, + api_secret: 'secret', + api_key: 'key' + }; + + beforeEach(() => { + cloudinary.v2.config(commonUrlOptions); + }); + + const expression = 'resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m'; + const search = cloudinary.v2.search.expression(expression).sort_by('public_id', 'desc').max_results(30); + + const encodedSearchPayload = 'eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIHVw' + + 'bG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjozMCwic29ydF9ieSI6W3sicHVibGljX2lkIjoiZGVzYyJ9XX0='; + const ttl300Sig = '431454b74cefa342e2f03e2d589b2e901babb8db6e6b149abf25bc0dd7ab20b7'; + const ttl1000Sig = '25b91426a37d4f633a9b34383c63889ff8952e7ffecef29a17d600eeb3db0db7'; + const defaultTtl = 300; + const newTtl = 1000; + const nextCursor = 'db27cfb02b3f69cb39049969c23ca430c6d33d5a3a7c3ad1d870c54e1a54ee0faa5acdd9f6d288666986001711759d10'; + + it('should build cached search url', () => { + const actual = search.to_url(); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including ttl', () => { + const actual = search.to_url(newTtl); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor', () => { + const actual = search.to_url(undefined, nextCursor); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}/${nextCursor}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor and ttl', () => { + const actual = search.to_url(newTtl, nextCursor); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/${nextCursor}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor and ttl set on instance', () => { + const actual = search.next_cursor(nextCursor).ttl(newTtl).to_url(); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/${nextCursor}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url when private cdn configured', () => { + cloudinary.v2.config({ + private_cdn: true + }); + const actual = search.to_url(defaultTtl); + const expected = `https://${cloudName}-res.cloudinary.com/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + }); +}); diff --git a/test/unit/url/video_url_spec.js b/test/unit/url/video_url_spec.js index 541ad54b..6c97e63a 100644 --- a/test/unit/url/video_url_spec.js +++ b/test/unit/url/video_url_spec.js @@ -206,7 +206,8 @@ describe("Cloudinary::Utils for video", function () { var options, path, source; source = "movie_id"; options = createTestConfig({ - cloud_name: "test123" + cloud_name: "test123", + secure: false }); path = utils.video_thumbnail_url(source, options); it("should generate a cloudinary URI to the video thumbnail", function () { diff --git a/test/utils/utils_spec.js b/test/utils/utils_spec.js index e78fb591..e93fb031 100644 --- a/test/utils/utils_spec.js +++ b/test/utils/utils_spec.js @@ -28,7 +28,10 @@ const { const TEST_TAG = helper.TEST_TAG; const createTestConfig = require('../testUtils/createTestConfig'); -const {clear_blank} = require("../../lib/utils"); +const { + clear_blank, + sort_object_by_key +} = require("../../lib/utils"); // Defined globals var cloud_name = ''; @@ -1752,4 +1755,19 @@ describe("utils", function () { }); }); }) + + describe('sort_object_by_keys', () => { + it("should sort object's properties alphabetically", () => { + const unordered = { + z: 'irrelevant', + m: 'irrelevant', + a: 'irrelevant' + }; + + assert.deepStrictEqual(Object.keys(unordered), ['z', 'm', 'a']); + + const ordered = sort_object_by_key(unordered); + assert.deepStrictEqual(Object.keys(ordered), ['a', 'm', 'z']) + }); + }); }); diff --git a/types/cloudinary_ts_spec.ts b/types/cloudinary_ts_spec.ts index 9b551a9d..c2255c01 100644 --- a/types/cloudinary_ts_spec.ts +++ b/types/cloudinary_ts_spec.ts @@ -881,6 +881,7 @@ cloudinary.v2.search .expression('cat -tags:kitten') .sort_by('public_id', 'desc') .aggregate('format') + .ttl(10) .execute().then(result => console.log(result)); // $ExpectType Promise @@ -889,6 +890,7 @@ cloudinary.v2.search .with_field('context') .with_field('tags') .max_results(10) + .ttl(10) .execute().then(result => console.log(result)); // $ExpectType Promise @@ -896,8 +898,17 @@ cloudinary.v2.search .expression('resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m') .sort_by('public_id', 'desc') .max_results(30) + .ttl(10) .execute().then(result => console.log(result)); +// $ExpectType string +cloudinary.v2.search + .expression('resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m') + .sort_by('public_id', 'desc') + .max_results(30) + .ttl(10) + .to_url(); + // $ExpectType string let test2 = cloudinary.v2.url("sample.jpg", { sign_url: true, diff --git a/types/index.d.ts b/types/index.d.ts index cf4b414b..3851b2e8 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -59,7 +59,15 @@ declare module 'cloudinary' { | "auto:none" | "liquid" | "ocr_text"; - type Angle = number | (string & {}) | Array | "auto_right" | "auto_left" | "ignore" | "vflip" | "hflip"; + type Angle = + number + | (string & {}) + | Array + | "auto_right" + | "auto_left" + | "ignore" + | "vflip" + | "hflip"; type ImageEffect = | (string & {}) | "hue" @@ -112,7 +120,14 @@ declare module 'cloudinary' { | "gamma" | "improve"; - type VideoEffect = (string & {}) | "accelerate" | "reverse" | "boomerang" | "loop" | "make_transparent" | "transition"; + type VideoEffect = + (string & {}) + | "accelerate" + | "reverse" + | "boomerang" + | "loop" + | "make_transparent" + | "transition"; type AudioCodec = (string & {}) | "none" | "aac" | "vorbis" | "mp3"; type AudioFrequency = string @@ -510,7 +525,7 @@ declare module 'cloudinary' { public_id?: string; quality_analysis?: boolean; resource_type?: "image" | "video" | "raw" | "auto"; - responsive_breakpoints?: Record; + responsive_breakpoints?: Record; return_delete_token?: boolean timestamp?: number; transformation?: TransformationOptions; @@ -565,7 +580,15 @@ declare module 'cloudinary' { /****************************** API *************************************/ type Status = (string & {}) | "pending" | "approved" | "rejected"; - type StreamingProfiles = (string & {}) | "4k" | "full_hd" | "hd" | "sd" | "full_hd_wifi" | "full_hd_lean" | "hd_lean"; + type StreamingProfiles = + (string & {}) + | "4k" + | "full_hd" + | "hd" + | "sd" + | "full_hd_wifi" + | "full_hd_lean" + | "hd_lean"; type ModerationKind = (string & {}) | "manual" | "webpurify" | "aws_rek" | "metascan"; type AccessMode = (string & {}) | "public" | "authenticated"; type TargetArchiveFormat = (string & {}) | "zip" | "tgz"; @@ -800,7 +823,11 @@ declare module 'cloudinary' { namespace utils { - function sign_request(params_to_sign: SignApiOptions, options?: ConfigAndUrlOptions): { signature: string; api_key: string; [key:string]:any}; + function sign_request(params_to_sign: SignApiOptions, options?: ConfigAndUrlOptions): { + signature: string; + api_key: string; + [key: string]: any + }; function api_sign_request(params_to_sign: SignApiOptions, api_secret: string): string; @@ -822,13 +849,13 @@ declare module 'cloudinary' { function download_zip_url(options?: ArchiveApiOptions | ConfigAndUrlOptions): string; - function download_backedup_asset(asset_id?: string, version_id?: string, options?: ArchiveApiOptions | ConfigAndUrlOptions) : string + function download_backedup_asset(asset_id?: string, version_id?: string, options?: ArchiveApiOptions | ConfigAndUrlOptions): string function generate_auth_token(options?: AuthTokenApiOptions): string; function webhook_signature(data?: string, timestamp?: number, options?: ConfigOptions): string; - function private_download_url(publicID: string, format:string, options: Partial<{ + function private_download_url(publicID: string, format: string, options: Partial<{ resource_type: ResourceType; type: DeliveryType; expires_at: number; @@ -839,15 +866,26 @@ declare module 'cloudinary' { /****************************** Admin API V2 Methods *************************************/ namespace api { - function create_streaming_profile(name: string, options: AdminApiOptions | { display_name?: string, representations: TransformationOptions }, callback?: ResponseCallback): Promise; + function create_streaming_profile(name: string, options: AdminApiOptions | { + display_name?: string, + representations: TransformationOptions + }, callback?: ResponseCallback): Promise; function create_transformation(name: string, transformation: TransformationOptions, callback?: ResponseCallback): Promise; - function create_transformation(name: string, transformation: TransformationOptions, options?: AdminApiOptions | { allowed_for_strict?: boolean }, callback?: ResponseCallback): Promise; + function create_transformation(name: string, transformation: TransformationOptions, options?: AdminApiOptions | { + allowed_for_strict?: boolean + }, callback?: ResponseCallback): Promise; - function create_upload_mapping(folder: string, options: AdminApiOptions | { template: string }, callback?: ResponseCallback): Promise; + function create_upload_mapping(folder: string, options: AdminApiOptions | { + template: string + }, callback?: ResponseCallback): Promise; - function create_upload_preset(options?: AdminApiOptions | { name?: string, unsigned?: boolean, disallow_public_id?: boolean }, callback?: ResponseCallback): Promise; + function create_upload_preset(options?: AdminApiOptions | { + name?: string, + unsigned?: boolean, + disallow_public_id?: boolean + }, callback?: ResponseCallback): Promise; function delete_all_resources(value?: AdminAndResourceOptions, callback?: ResponseCallback): Promise; @@ -947,7 +985,10 @@ declare module 'cloudinary' { function resources_by_tag(tag: string, callback?: ResponseCallback): Promise; - function restore(public_ids: string[], options?: AdminApiOptions | { resource_type: ResourceType, type: DeliveryType }, callback?: ResponseCallback): Promise; + function restore(public_ids: string[], options?: AdminApiOptions | { + resource_type: ResourceType, + type: DeliveryType + }, callback?: ResponseCallback): Promise; function restore(public_ids: string[], callback?: ResponseCallback): Promise; @@ -965,13 +1006,25 @@ declare module 'cloudinary' { function search_folders(search_input: string, callback?: ResponseCallback): Promise; - function tags(options?: AdminApiOptions | { max_results?: number, next_cursor?: string, prefix?: string }, callback?: ResponseCallback): Promise; + function tags(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + prefix?: string + }, callback?: ResponseCallback): Promise; - function transformation(transformation: TransformationOptions, options?: AdminApiOptions | { max_results?: number, next_cursor?: string, named?: boolean }, callback?: ResponseCallback): Promise; + function transformation(transformation: TransformationOptions, options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + named?: boolean + }, callback?: ResponseCallback): Promise; function transformation(transformation: TransformationOptions, callback?: ResponseCallback): Promise; - function transformations(options?: AdminApiOptions | { max_results?: number, next_cursor?: string, named?: boolean }, callback?: ResponseCallback): Promise; + function transformations(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + named?: boolean + }, callback?: ResponseCallback): Promise; function transformations(callback?: ResponseCallback): Promise; @@ -991,15 +1044,23 @@ declare module 'cloudinary' { function update_resources_access_mode_by_tag(access_mode: AccessMode, tag: string, callback?: ResponseCallback): Promise; - function update_streaming_profile(name: string, options: { display_name?: string, representations: Array<{ transformation?: VideoTransformationOptions }> }, callback?: ResponseCallback): Promise; + function update_streaming_profile(name: string, options: { + display_name?: string, + representations: Array<{ transformation?: VideoTransformationOptions }> + }, callback?: ResponseCallback): Promise; function update_transformation(transformation_name: TransformationOptions, updates?: TransformationOptions, callback?: ResponseCallback): Promise; function update_transformation(transformation_name: TransformationOptions, callback?: ResponseCallback): Promise; - function update_upload_mapping(name: string, options: AdminApiOptions | { template: string }, callback?: ResponseCallback): Promise; + function update_upload_mapping(name: string, options: AdminApiOptions | { + template: string + }, callback?: ResponseCallback): Promise; - function update_upload_preset(name?: string, options?: AdminApiOptions | { unsigned?: boolean, disallow_public_id?: boolean }, callback?: ResponseCallback): Promise; + function update_upload_preset(name?: string, options?: AdminApiOptions | { + unsigned?: boolean, + disallow_public_id?: boolean + }, callback?: ResponseCallback): Promise; function update_upload_preset(name?: string, callback?: ResponseCallback): Promise; @@ -1007,7 +1068,10 @@ declare module 'cloudinary' { function upload_mapping(name?: string, callback?: ResponseCallback): Promise; - function upload_mappings(options?: AdminApiOptions | { max_results?: number, next_cursor?: string }, callback?: ResponseCallback): Promise; + function upload_mappings(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string + }, callback?: ResponseCallback): Promise; function upload_mappings(callback?: ResponseCallback): Promise; @@ -1015,15 +1079,18 @@ declare module 'cloudinary' { function upload_preset(name?: string, callback?: ResponseCallback): Promise; - function upload_presets(options?: AdminApiOptions | { max_results?: number, next_cursor?: string }, callback?: ResponseCallback): Promise; + function upload_presets(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string + }, callback?: ResponseCallback): Promise; function usage(callback?: ResponseCallback, options?: AdminApiOptions): Promise; function usage(options?: AdminApiOptions): Promise; - function create_folder(path:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function create_folder(path: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; - function delete_folder(path:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function delete_folder(path: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; /****************************** Structured Metadata API V2 Methods *************************************/ @@ -1039,9 +1106,9 @@ declare module 'cloudinary' { function delete_metadata_field(field_external_id: string, callback?: ResponseCallback): Promise; - function metadata_field_by_field_id(external_id:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function metadata_field_by_field_id(external_id: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; - function metadata_field_by_field_id(external_id:string, callback?: ResponseCallback): Promise; + function metadata_field_by_field_id(external_id: string, callback?: ResponseCallback): Promise; function update_metadata_field(external_id: string, field: MetadataFieldApiOptions, options?: AdminApiOptions, callback?: ResponseCallback): Promise; @@ -1081,11 +1148,17 @@ declare module 'cloudinary' { /****************************** Upload API V2 Methods *************************************/ namespace uploader { - function add_context(context: string, public_ids: string[], options?: { type?: DeliveryType, resource_type?: ResourceType }, callback?: ResponseCallback): Promise; + function add_context(context: string, public_ids: string[], options?: { + type?: DeliveryType, + resource_type?: ResourceType + }, callback?: ResponseCallback): Promise; function add_context(context: string, public_ids: string[], callback?: ResponseCallback): Promise; - function add_tag(tag: string, public_ids: string[], options?: { type?: DeliveryType, resource_type?: ResourceType }, callback?: ResponseCallback): Promise; + function add_tag(tag: string, public_ids: string[], options?: { + type?: DeliveryType, + resource_type?: ResourceType + }, callback?: ResponseCallback): Promise; function add_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; @@ -1093,7 +1166,11 @@ declare module 'cloudinary' { function create_zip(options?: ArchiveApiOptions, callback?: ResponseCallback): Promise; - function destroy(public_id: string, options?: { resource_type?: ResourceType, type?: DeliveryType, invalidate?: boolean }, callback?: ResponseCallback,): Promise; + function destroy(public_id: string, options?: { + resource_type?: ResourceType, + type?: DeliveryType, + invalidate?: boolean + }, callback?: ResponseCallback,): Promise; function destroy(public_id: string, callback?: ResponseCallback,): Promise; @@ -1101,41 +1178,80 @@ declare module 'cloudinary' { function explicit(public_id: string, callback?: ResponseCallback): Promise; - function explode(public_id: string, options?: { page?: 'all', type?: DeliveryType, format?: ImageAndVideoFormatOptions, notification_url?: string, transformations?: TransformationOptions }, callback?: ResponseCallback): Promise + function explode(public_id: string, options?: { + page?: 'all', + type?: DeliveryType, + format?: ImageAndVideoFormatOptions, + notification_url?: string, + transformations?: TransformationOptions + }, callback?: ResponseCallback): Promise function explode(public_id: string, callback?: ResponseCallback): Promise - function generate_sprite(tag: string, options?: { transformation?: TransformationOptions, format?: ImageAndVideoFormatOptions, notification_url?: string, async?: boolean }, callback?: ResponseCallback): Promise; + function generate_sprite(tag: string, options?: { + transformation?: TransformationOptions, + format?: ImageAndVideoFormatOptions, + notification_url?: string, + async?: boolean + }, callback?: ResponseCallback): Promise; function generate_sprite(tag: string, callback?: ResponseCallback): Promise; function image_upload_tag(field?: string, options?: UploadApiOptions): Promise; - function multi(tag: string, options?: { transformation?: TransformationOptions, async?: boolean, format?: ImageAndVideoFormatOptions, notification_url?: string }, callback?: ResponseCallback): Promise; + function multi(tag: string, options?: { + transformation?: TransformationOptions, + async?: boolean, + format?: ImageAndVideoFormatOptions, + notification_url?: string + }, callback?: ResponseCallback): Promise; function multi(tag: string, callback?: ResponseCallback): Promise; - function remove_all_context(public_ids: string[], options?: { context?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_all_context(public_ids: string[], options?: { + context?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_all_context(public_ids: string[], callback?: ResponseCallback): Promise; - function remove_all_tags(public_ids: string[], options?: { tag?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_all_tags(public_ids: string[], options?: { + tag?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_all_tags(public_ids: string[], callback?: ResponseCallback): Promise; - function remove_tag(tag: string, public_ids: string[], options?: { tag?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_tag(tag: string, public_ids: string[], options?: { + tag?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; - function rename(from_public_id: string, to_public_id: string, options?: { resource_type?: ResourceType, type?: DeliveryType, to_type?: DeliveryType, overwrite?: boolean, invalidate?: boolean }, callback?: ResponseCallback): Promise; + function rename(from_public_id: string, to_public_id: string, options?: { + resource_type?: ResourceType, + type?: DeliveryType, + to_type?: DeliveryType, + overwrite?: boolean, + invalidate?: boolean + }, callback?: ResponseCallback): Promise; function rename(from_public_id: string, to_public_id: string, callback?: ResponseCallback): Promise; - function replace_tag(tag: string, public_ids: string[], options?: { resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function replace_tag(tag: string, public_ids: string[], options?: { + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function replace_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; - function text(text: string, options?: TextStyleOptions | { public_id?: string }, callback?: ResponseCallback): Promise; + function text(text: string, options?: TextStyleOptions | { + public_id?: string + }, callback?: ResponseCallback): Promise; function text(text: string, callback?: ResponseCallback): Promise; @@ -1171,13 +1287,16 @@ declare module 'cloudinary' { function upload_url(options?: ConfigOptions): Promise; - function create_slideshow(options?: ConfigOptions & { manifest_transformation?: TransformationOptions, manifest_json?: Record}, callback?: UploadResponseCallback): Promise; + function create_slideshow(options?: ConfigOptions & { + manifest_transformation?: TransformationOptions, + manifest_json?: Record + }, callback?: UploadResponseCallback): Promise; /****************************** Structured Metadata API V2 Methods *************************************/ - function update_metadata(metadata: string | Record, public_ids: string[], options?:UploadApiOptions, callback?: ResponseCallback): Promise; + function update_metadata(metadata: string | Record, public_ids: string[], options?: UploadApiOptions, callback?: ResponseCallback): Promise; - function update_metadata(metadata: string| Record, public_ids: string[], callback?: ResponseCallback): Promise; + function update_metadata(metadata: string | Record, public_ids: string[], callback?: ResponseCallback): Promise; } /****************************** Search API *************************************/ @@ -1196,10 +1315,14 @@ declare module 'cloudinary' { sort_by(key: string, value: 'asc' | 'desc'): search; + ttl(newTtl: number): search; + to_query(value?: string): search; with_field(value?: string): search; + to_url(newTtl?: number, next_cursor?: string, options?: ConfigOptions): string; + static aggregate(args?: string): search; static expression(args?: string): search; @@ -1212,6 +1335,8 @@ declare module 'cloudinary' { static sort_by(key: string, value: 'asc' | 'desc'): search; + static ttl(newTtl: number): search; + static with_field(args?: string): search; } @@ -1223,11 +1348,11 @@ declare module 'cloudinary' { function sub_account(subAccountId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; - function create_sub_account(name: string, cloudName: string, customAttributes?: Record, enabled?: boolean, baseAccount?: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + function create_sub_account(name: string, cloudName: string, customAttributes?: Record, enabled?: boolean, baseAccount?: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; function delete_sub_account(subAccountId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; - function update_sub_account(subAccountId: string, name?: string, cloudName?: string, customAttributes?: Record, enabled?: boolean, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + function update_sub_account(subAccountId: string, name?: string, cloudName?: string, customAttributes?: Record, enabled?: boolean, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; function user(userId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise;