diff --git a/.mocharc.json b/.mocharc.json index 01f0c171..f589a890 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,4 +1,4 @@ { "recursive": true, "retries": 3 -} +} \ No newline at end of file diff --git a/lib/api.js b/lib/api.js index f5a598d8..9ec93760 100644 --- a/lib/api.js +++ b/lib/api.js @@ -125,6 +125,27 @@ exports.restore = function restore(public_ids, callback, options = {}) { }, callback, options); }; +exports.restore_by_asset_ids = function restore_by_asset_ids(asset_ids, callback, options = {}) { + options.content_type = "json"; + let uri = ["resources", "restore"]; + + // make sure asset_ids is always an array + if (!Array.isArray(asset_ids)) { + asset_ids = [asset_ids]; + } + + return call_api( + "post", + uri, + { + asset_ids: asset_ids, + versions: options.versions + }, + callback, + options + ); +}; + exports.update = function update(public_id, callback, options = {}) { let params, resource_type, type, uri; resource_type = options.resource_type || "image"; diff --git a/lib/v2/api.js b/lib/v2/api.js index 4f109841..3696334f 100644 --- a/lib/v2/api.js +++ b/lib/v2/api.js @@ -15,6 +15,7 @@ v1_adapters(exports, api, { resources_by_asset_folder: 1, resource: 1, restore: 1, + restore_by_asset_ids: 1, update: 1, delete_resources: 1, delete_resources_by_prefix: 1, diff --git a/test/integration/api/admin/api_spec.js b/test/integration/api/admin/api_spec.js index d63aa92b..54135687 100644 --- a/test/integration/api/admin/api_spec.js +++ b/test/integration/api/admin/api_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-tabs */ const sinon = require('sinon'); const formatDate = require("date-fns").format; const subDate = require("date-fns").sub; @@ -1431,6 +1432,191 @@ describe("api", function () { expect(finalDelete.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted"); }); }); + + describe("restore_by_asset_ids", function () { + this.timeout(TIMEOUT.MEDIUM); + + const publicId = "api_test_restore" + UNIQUE_JOB_SUFFIX_ID; + let uploadedAssetId; + + before(() => uploadImage({ + public_id: publicId, + backup: true, + tags: UPLOAD_TAGS + }) + .then(wait(2000)) + .then(() => cloudinary.v2.api.resource(publicId)) + .then((resource) => { + uploadedAssetId = resource.asset_id; + expect(resource).not.to.be(null); + expect(resource.bytes).to.eql(3381); + return cloudinary.v2.api.delete_resources(publicId); + }) + .then(() => cloudinary.v2.api.resource(publicId)) + .then((resource) => { + expect(resource).not.to.be(null); + expect(resource.bytes).to.eql(0); + expect(resource.placeholder).to.eql(true); + }) + ); + + it("should restore a deleted resource when passed an asset ID", () => cloudinary.v2.api + .restore_by_asset_ids([uploadedAssetId]) + .then((response) => { + let info = response[uploadedAssetId]; + expect(info).not.to.be(null); + expect(info.bytes).to.eql(3381); + return cloudinary.v2.api.resources_by_asset_ids([uploadedAssetId]); + }) + .then((response) => { + const { resources } = response; + expect(resources[0]).not.to.be(null); + expect(resources[0].bytes).to.eql(3381); + })); + + it.skip("should restore different versions of a deleted asset when passed an asset ID", async function () { + this.timeout(TIMEOUT.LARGE); + + // Upload the same file twice (upload->delete->upload->delete) + + // Upload and delete a file + const firstUpload = await uploadImage({ + public_id: PUBLIC_ID_BACKUP_1, + backup: true + }); + await wait(1000)(); + + const firstDelete = await API_V2.delete_resources([ + PUBLIC_ID_BACKUP_1 + ]); + + // Upload and delete it again, this time add angle to create a different 'version' + const secondUpload = await uploadImage({ + public_id: PUBLIC_ID_BACKUP_1, + backup: true, + angle: "0" + }); + await wait(1000)(); + + const secondDelete = await API_V2.delete_resources([ + PUBLIC_ID_BACKUP_1 + ]); + await wait(1000)(); + + // Sanity, ensure these uploads are different before we continue + expect(firstUpload.bytes).not.to.equal(secondUpload.bytes); + + // Ensure all files were uploaded correctly + expect(firstUpload).not.to.be(null); + expect(secondUpload).not.to.be(null); + + // Ensure all files were deleted correctly + expect(firstDelete).to.have.property("deleted"); + expect(secondDelete).to.have.property("deleted"); + + // Get the asset ID and versions of the deleted asset + const getVersionsResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, { + versions: true + }); + const assetId = getVersionsResp.asset_id; + + const firstAssetVersion = getVersionsResp.versions[0].version_id; + const secondAssetVersion = getVersionsResp.versions[1].version_id; + + // Restore first version by passing in the asset ID, ensure it's equal to the upload size + await wait(1000)(); + const firstVerRestore = await API_V2.restore_by_asset_ids([assetId], { + versions: [firstAssetVersion] + }); + + expect(firstVerRestore[assetId].bytes).to.eql( + firstUpload.bytes + ); + + // Restore second version by passing in the asset ID, ensure it's equal to the upload size + await wait(1000)(); + const secondVerRestore = await API_V2.restore_by_asset_ids( + [assetId], + { versions: [secondAssetVersion] } + ); + expect(secondVerRestore[assetId].bytes).to.eql( + secondUpload.bytes + ); + + // Cleanup, + const finalDeleteResp = await API_V2.delete_resources([ + PUBLIC_ID_BACKUP_1 + ]); + expect(finalDeleteResp).to.have.property("deleted"); + }); + + it("should restore two different deleted assets when passed asset IDs", async () => { + // Upload two different files + const firstUpload = await uploadImage({ + public_id: PUBLIC_ID_BACKUP_1, + backup: true + }); + const secondUpload = await uploadImage({ + public_id: PUBLIC_ID_BACKUP_2, + backup: true, + angle: "0" + }); + + // delete both resources + const deleteAll = await API_V2.delete_resources([ + PUBLIC_ID_BACKUP_1, + PUBLIC_ID_BACKUP_2 + ]); + + // Expect correct deletion of the assets + expect(deleteAll.deleted[PUBLIC_ID_BACKUP_1]).to.be("deleted"); + expect(deleteAll.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted"); + + const getFirstAssetVersion = await API_V2.resource( + PUBLIC_ID_BACKUP_1, + { versions: true } + ); + + const getSecondAssetVersion = await API_V2.resource( + PUBLIC_ID_BACKUP_2, + { versions: true } + ); + + const firstAssetId = getFirstAssetVersion.asset_id; + const secondAssetId = getSecondAssetVersion.asset_id; + + const firstAssetVersion = + getFirstAssetVersion.versions[0].version_id; + const secondAssetVersion = + getSecondAssetVersion.versions[0].version_id; + + const IDS_TO_RESTORE = [firstAssetId, secondAssetId]; + const VERSIONS_TO_RESTORE = [firstAssetVersion, secondAssetVersion]; + + const restore = await API_V2.restore_by_asset_ids(IDS_TO_RESTORE, { + versions: VERSIONS_TO_RESTORE + }); + + // Expect correct restorations + expect(restore[firstAssetId].bytes).to.equal( + firstUpload.bytes + ); + expect(restore[secondAssetId].bytes).to.equal( + secondUpload.bytes + ); + + // Cleanup + const finalDelete = await API_V2.delete_resources([ + PUBLIC_ID_BACKUP_1, + PUBLIC_ID_BACKUP_2 + ]); + // Expect correct deletion of the assets + expect(finalDelete.deleted[PUBLIC_ID_BACKUP_1]).to.be("deleted"); + expect(finalDelete.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted"); + }); + }); + + describe('mapping', function () { before(function () { this.mapping = `api_test_upload_mapping${Math.floor(Math.random() * 100000)}`; diff --git a/types/cloudinary_ts_spec.ts b/types/cloudinary_ts_spec.ts index 29eec712..6fead38a 100644 --- a/types/cloudinary_ts_spec.ts +++ b/types/cloudinary_ts_spec.ts @@ -520,11 +520,25 @@ cloudinary.v2.api.resources_by_tag("mytag", console.log(result, error); }); +// $ExpectType Promise cloudinary.v2.api.restore(["image1", "image2"], function (error, result) { console.log(result, error); }); +// $ExpectType Promise +cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"], + { content_type: 'json' }, + function (error, result) { + console.log(result, error); + }); + +// $ExpectType Promise +cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"], + function (error, result) { + console.log(result, error); + }); + // $ExpectType Promise cloudinary.v2.api.root_folders(function (err, res) { console.log(err); diff --git a/types/index.d.ts b/types/index.d.ts index 20378969..83189868 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1098,9 +1098,13 @@ declare module 'cloudinary' { function restore(public_ids: string[], options?: AdminApiOptions | { resource_type: ResourceType, type: DeliveryType - }, callback?: ResponseCallback): Promise; + }, callback?: ResponseCallback): Promise; + + function restore(public_ids: string[], callback?: ResponseCallback): Promise; + + function restore_by_asset_ids(asset_ids: string[], options?: AdminAndResourceOptions, callback?: ResponseCallback): Promise; - function restore(public_ids: string[], callback?: ResponseCallback): Promise; + function restore_by_asset_ids(asset_ids: string[], callback?: ResponseCallback): Promise; function root_folders(callback?: ResponseCallback, options?: AdminApiOptions): Promise;