Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9047c9d
bumped up the version of mocha
esguerrat Apr 10, 2025
ab5558c
added the functionn to restore resources by asset ID
esguerrat Apr 10, 2025
9ad22f6
added my function to the list of adapters for v2
esguerrat Apr 10, 2025
66f73bf
change the name to snake case
esguerrat Apr 10, 2025
e7be6c7
write tests for restoring resources by asset id
esguerrat Apr 10, 2025
88b03a7
fix: use correct endpoint for restoring by asset id
esguerrat Apr 18, 2025
29bc9da
fix: restore_by_asset_id integration tests
cloudinary-pkoniu Apr 18, 2025
391cd26
change function name to restore by asset ids
esguerrat Apr 21, 2025
6298ca8
change function name to restore by asset ids
esguerrat Apr 21, 2025
d65bbff
remove .only from my test spec
esguerrat Apr 21, 2025
f6b26a3
add both possibilities for my function to the typescript file
esguerrat Apr 21, 2025
9dead40
fix: add a check to make sure that asset ids is always an array
esguerrat Apr 23, 2025
a0ef684
fix: swap the order of the parameters options and callback in order t…
esguerrat Apr 23, 2025
096ee67
fix: address reviewer feedback - update TypeScript definitions and re…
esguerrat Aug 3, 2025
10db5a8
Merge remote-tracking branch 'origin/master' into feature-restore-res…
esguerrat Aug 3, 2025
b06b2b1
fix: v1_adapters usage
cloudinary-pkoniu Aug 14, 2025
09c9995
Update types/index.d.ts
esguerrat Aug 14, 2025
13f5822
fix: v1_adapters usage
cloudinary-pkoniu Aug 20, 2025
ab17742
chore: dts chores
cloudinary-pkoniu Aug 20, 2025
f2fee4c
Merge branch 'master' into feature-restore-resources-by-asset-id
cloudinary-pkoniu Sep 23, 2025
4a4c5ba
fix: removing any as expected type
cloudinary-pkoniu Sep 23, 2025
db3302e
chore: setting mocha retries to 0
cloudinary-pkoniu Sep 23, 2025
54391fd
fix: temporarily skipping a specific test
cloudinary-pkoniu Sep 23, 2025
cd51019
fix: temporarily skipping a specific test
cloudinary-pkoniu Sep 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"recursive": true,
"retries": 3
}
}
21 changes: 21 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
1 change: 1 addition & 0 deletions lib/v2/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
186 changes: 186 additions & 0 deletions test/integration/api/admin/api_spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-tabs */
const sinon = require('sinon');
const formatDate = require("date-fns").format;
const subDate = require("date-fns").sub;
Expand Down Expand Up @@ -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)}`;
Expand Down
14 changes: 14 additions & 0 deletions types/cloudinary_ts_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,25 @@ cloudinary.v2.api.resources_by_tag("mytag",
console.log(result, error);
});

// $ExpectType Promise<ResourceApiResponse>
cloudinary.v2.api.restore(["image1", "image2"],
function (error, result) {
console.log(result, error);
});

// $ExpectType Promise<ResourceApiResponse>
cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"],
{ content_type: 'json' },
function (error, result) {
console.log(result, error);
});

// $ExpectType Promise<ResourceApiResponse>
cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"],
function (error, result) {
console.log(result, error);
});

// $ExpectType Promise<any>
cloudinary.v2.api.root_folders(function (err, res) {
console.log(err);
Expand Down
8 changes: 6 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1098,9 +1098,13 @@ declare module 'cloudinary' {
function restore(public_ids: string[], options?: AdminApiOptions | {
resource_type: ResourceType,
type: DeliveryType
}, callback?: ResponseCallback): Promise<any>;
}, callback?: ResponseCallback): Promise<ResourceApiResponse>;

function restore(public_ids: string[], callback?: ResponseCallback): Promise<ResourceApiResponse>;

function restore_by_asset_ids(asset_ids: string[], options?: AdminAndResourceOptions, callback?: ResponseCallback): Promise<ResourceApiResponse>;

function restore(public_ids: string[], callback?: ResponseCallback): Promise<any>;
function restore_by_asset_ids(asset_ids: string[], callback?: ResponseCallback): Promise<ResourceApiResponse>;

function root_folders(callback?: ResponseCallback, options?: AdminApiOptions): Promise<any>;

Expand Down