Skip to content

Commit 8d9b2a0

Browse files
Feat: Add support for restoring asset(s) by asset id (#707)
* bumped up the version of mocha * added the functionn to restore resources by asset ID * added my function to the list of adapters for v2 * change the name to snake case * write tests for restoring resources by asset id * fix: use correct endpoint for restoring by asset id * fix: restore_by_asset_id integration tests * change function name to restore by asset ids * change function name to restore by asset ids * remove .only from my test spec * add both possibilities for my function to the typescript file * fix: add a check to make sure that asset ids is always an array * fix: swap the order of the parameters options and callback in order to fix test failures * fix: address reviewer feedback - update TypeScript definitions and revert unnecessary mocha version change * fix: v1_adapters usage * Update types/index.d.ts Co-authored-by: Patryk Konior <[email protected]> * fix: v1_adapters usage * chore: dts chores * fix: removing any as expected type * chore: setting mocha retries to 0 * fix: temporarily skipping a specific test * fix: temporarily skipping a specific test --------- Co-authored-by: cloudinary-pkoniu <[email protected]> Co-authored-by: Patryk Konior <[email protected]>
1 parent 362f676 commit 8d9b2a0

File tree

6 files changed

+229
-3
lines changed

6 files changed

+229
-3
lines changed

.mocharc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"recursive": true,
33
"retries": 3
4-
}
4+
}

lib/api.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,27 @@ exports.restore = function restore(public_ids, callback, options = {}) {
125125
}, callback, options);
126126
};
127127

128+
exports.restore_by_asset_ids = function restore_by_asset_ids(asset_ids, callback, options = {}) {
129+
options.content_type = "json";
130+
let uri = ["resources", "restore"];
131+
132+
// make sure asset_ids is always an array
133+
if (!Array.isArray(asset_ids)) {
134+
asset_ids = [asset_ids];
135+
}
136+
137+
return call_api(
138+
"post",
139+
uri,
140+
{
141+
asset_ids: asset_ids,
142+
versions: options.versions
143+
},
144+
callback,
145+
options
146+
);
147+
};
148+
128149
exports.update = function update(public_id, callback, options = {}) {
129150
let params, resource_type, type, uri;
130151
resource_type = options.resource_type || "image";

lib/v2/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ v1_adapters(exports, api, {
1515
resources_by_asset_folder: 1,
1616
resource: 1,
1717
restore: 1,
18+
restore_by_asset_ids: 1,
1819
update: 1,
1920
delete_resources: 1,
2021
delete_resources_by_prefix: 1,

test/integration/api/admin/api_spec.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-tabs */
12
const sinon = require('sinon');
23
const formatDate = require("date-fns").format;
34
const subDate = require("date-fns").sub;
@@ -1431,6 +1432,191 @@ describe("api", function () {
14311432
expect(finalDelete.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted");
14321433
});
14331434
});
1435+
1436+
describe("restore_by_asset_ids", function () {
1437+
this.timeout(TIMEOUT.MEDIUM);
1438+
1439+
const publicId = "api_test_restore" + UNIQUE_JOB_SUFFIX_ID;
1440+
let uploadedAssetId;
1441+
1442+
before(() => uploadImage({
1443+
public_id: publicId,
1444+
backup: true,
1445+
tags: UPLOAD_TAGS
1446+
})
1447+
.then(wait(2000))
1448+
.then(() => cloudinary.v2.api.resource(publicId))
1449+
.then((resource) => {
1450+
uploadedAssetId = resource.asset_id;
1451+
expect(resource).not.to.be(null);
1452+
expect(resource.bytes).to.eql(3381);
1453+
return cloudinary.v2.api.delete_resources(publicId);
1454+
})
1455+
.then(() => cloudinary.v2.api.resource(publicId))
1456+
.then((resource) => {
1457+
expect(resource).not.to.be(null);
1458+
expect(resource.bytes).to.eql(0);
1459+
expect(resource.placeholder).to.eql(true);
1460+
})
1461+
);
1462+
1463+
it("should restore a deleted resource when passed an asset ID", () => cloudinary.v2.api
1464+
.restore_by_asset_ids([uploadedAssetId])
1465+
.then((response) => {
1466+
let info = response[uploadedAssetId];
1467+
expect(info).not.to.be(null);
1468+
expect(info.bytes).to.eql(3381);
1469+
return cloudinary.v2.api.resources_by_asset_ids([uploadedAssetId]);
1470+
})
1471+
.then((response) => {
1472+
const { resources } = response;
1473+
expect(resources[0]).not.to.be(null);
1474+
expect(resources[0].bytes).to.eql(3381);
1475+
}));
1476+
1477+
it.skip("should restore different versions of a deleted asset when passed an asset ID", async function () {
1478+
this.timeout(TIMEOUT.LARGE);
1479+
1480+
// Upload the same file twice (upload->delete->upload->delete)
1481+
1482+
// Upload and delete a file
1483+
const firstUpload = await uploadImage({
1484+
public_id: PUBLIC_ID_BACKUP_1,
1485+
backup: true
1486+
});
1487+
await wait(1000)();
1488+
1489+
const firstDelete = await API_V2.delete_resources([
1490+
PUBLIC_ID_BACKUP_1
1491+
]);
1492+
1493+
// Upload and delete it again, this time add angle to create a different 'version'
1494+
const secondUpload = await uploadImage({
1495+
public_id: PUBLIC_ID_BACKUP_1,
1496+
backup: true,
1497+
angle: "0"
1498+
});
1499+
await wait(1000)();
1500+
1501+
const secondDelete = await API_V2.delete_resources([
1502+
PUBLIC_ID_BACKUP_1
1503+
]);
1504+
await wait(1000)();
1505+
1506+
// Sanity, ensure these uploads are different before we continue
1507+
expect(firstUpload.bytes).not.to.equal(secondUpload.bytes);
1508+
1509+
// Ensure all files were uploaded correctly
1510+
expect(firstUpload).not.to.be(null);
1511+
expect(secondUpload).not.to.be(null);
1512+
1513+
// Ensure all files were deleted correctly
1514+
expect(firstDelete).to.have.property("deleted");
1515+
expect(secondDelete).to.have.property("deleted");
1516+
1517+
// Get the asset ID and versions of the deleted asset
1518+
const getVersionsResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, {
1519+
versions: true
1520+
});
1521+
const assetId = getVersionsResp.asset_id;
1522+
1523+
const firstAssetVersion = getVersionsResp.versions[0].version_id;
1524+
const secondAssetVersion = getVersionsResp.versions[1].version_id;
1525+
1526+
// Restore first version by passing in the asset ID, ensure it's equal to the upload size
1527+
await wait(1000)();
1528+
const firstVerRestore = await API_V2.restore_by_asset_ids([assetId], {
1529+
versions: [firstAssetVersion]
1530+
});
1531+
1532+
expect(firstVerRestore[assetId].bytes).to.eql(
1533+
firstUpload.bytes
1534+
);
1535+
1536+
// Restore second version by passing in the asset ID, ensure it's equal to the upload size
1537+
await wait(1000)();
1538+
const secondVerRestore = await API_V2.restore_by_asset_ids(
1539+
[assetId],
1540+
{ versions: [secondAssetVersion] }
1541+
);
1542+
expect(secondVerRestore[assetId].bytes).to.eql(
1543+
secondUpload.bytes
1544+
);
1545+
1546+
// Cleanup,
1547+
const finalDeleteResp = await API_V2.delete_resources([
1548+
PUBLIC_ID_BACKUP_1
1549+
]);
1550+
expect(finalDeleteResp).to.have.property("deleted");
1551+
});
1552+
1553+
it("should restore two different deleted assets when passed asset IDs", async () => {
1554+
// Upload two different files
1555+
const firstUpload = await uploadImage({
1556+
public_id: PUBLIC_ID_BACKUP_1,
1557+
backup: true
1558+
});
1559+
const secondUpload = await uploadImage({
1560+
public_id: PUBLIC_ID_BACKUP_2,
1561+
backup: true,
1562+
angle: "0"
1563+
});
1564+
1565+
// delete both resources
1566+
const deleteAll = await API_V2.delete_resources([
1567+
PUBLIC_ID_BACKUP_1,
1568+
PUBLIC_ID_BACKUP_2
1569+
]);
1570+
1571+
// Expect correct deletion of the assets
1572+
expect(deleteAll.deleted[PUBLIC_ID_BACKUP_1]).to.be("deleted");
1573+
expect(deleteAll.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted");
1574+
1575+
const getFirstAssetVersion = await API_V2.resource(
1576+
PUBLIC_ID_BACKUP_1,
1577+
{ versions: true }
1578+
);
1579+
1580+
const getSecondAssetVersion = await API_V2.resource(
1581+
PUBLIC_ID_BACKUP_2,
1582+
{ versions: true }
1583+
);
1584+
1585+
const firstAssetId = getFirstAssetVersion.asset_id;
1586+
const secondAssetId = getSecondAssetVersion.asset_id;
1587+
1588+
const firstAssetVersion =
1589+
getFirstAssetVersion.versions[0].version_id;
1590+
const secondAssetVersion =
1591+
getSecondAssetVersion.versions[0].version_id;
1592+
1593+
const IDS_TO_RESTORE = [firstAssetId, secondAssetId];
1594+
const VERSIONS_TO_RESTORE = [firstAssetVersion, secondAssetVersion];
1595+
1596+
const restore = await API_V2.restore_by_asset_ids(IDS_TO_RESTORE, {
1597+
versions: VERSIONS_TO_RESTORE
1598+
});
1599+
1600+
// Expect correct restorations
1601+
expect(restore[firstAssetId].bytes).to.equal(
1602+
firstUpload.bytes
1603+
);
1604+
expect(restore[secondAssetId].bytes).to.equal(
1605+
secondUpload.bytes
1606+
);
1607+
1608+
// Cleanup
1609+
const finalDelete = await API_V2.delete_resources([
1610+
PUBLIC_ID_BACKUP_1,
1611+
PUBLIC_ID_BACKUP_2
1612+
]);
1613+
// Expect correct deletion of the assets
1614+
expect(finalDelete.deleted[PUBLIC_ID_BACKUP_1]).to.be("deleted");
1615+
expect(finalDelete.deleted[PUBLIC_ID_BACKUP_2]).to.be("deleted");
1616+
});
1617+
});
1618+
1619+
14341620
describe('mapping', function () {
14351621
before(function () {
14361622
this.mapping = `api_test_upload_mapping${Math.floor(Math.random() * 100000)}`;

types/cloudinary_ts_spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,11 +520,25 @@ cloudinary.v2.api.resources_by_tag("mytag",
520520
console.log(result, error);
521521
});
522522

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

529+
// $ExpectType Promise<ResourceApiResponse>
530+
cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"],
531+
{ content_type: 'json' },
532+
function (error, result) {
533+
console.log(result, error);
534+
});
535+
536+
// $ExpectType Promise<ResourceApiResponse>
537+
cloudinary.v2.api.restore_by_asset_ids(["abcd1234", "defg5678"],
538+
function (error, result) {
539+
console.log(result, error);
540+
});
541+
528542
// $ExpectType Promise<any>
529543
cloudinary.v2.api.root_folders(function (err, res) {
530544
console.log(err);

types/index.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,13 @@ declare module 'cloudinary' {
10981098
function restore(public_ids: string[], options?: AdminApiOptions | {
10991099
resource_type: ResourceType,
11001100
type: DeliveryType
1101-
}, callback?: ResponseCallback): Promise<any>;
1101+
}, callback?: ResponseCallback): Promise<ResourceApiResponse>;
1102+
1103+
function restore(public_ids: string[], callback?: ResponseCallback): Promise<ResourceApiResponse>;
1104+
1105+
function restore_by_asset_ids(asset_ids: string[], options?: AdminAndResourceOptions, callback?: ResponseCallback): Promise<ResourceApiResponse>;
11021106

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

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

0 commit comments

Comments
 (0)