diff --git a/package.json b/package.json index 88da9950..b8fbd36a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.9.5", + "version": "1.9.6", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", diff --git a/src/drive/share/index.ts b/src/drive/share/index.ts index 8bdac00a..b04ae0f9 100644 --- a/src/drive/share/index.ts +++ b/src/drive/share/index.ts @@ -31,6 +31,7 @@ import { UpdateUserRolePayload, UpdateUserRoleResponse, } from './types'; +import { ItemType } from 'src/workspaces'; export * as ShareTypes from './types'; @@ -363,13 +364,7 @@ export class Share { * @returns {Promise<{ data: SharingInvite[] }>} A promise containing the private folder data. */ - public getSharedFolderInvitations({ - itemId, - itemType, - }: { - itemId: string; - itemType: 'folder' | 'file'; - }): Promise { + public getSharedFolderInvitations({ itemId, itemType }: { itemId: string; itemType: ItemType }): Promise { return this.client.get(`sharings/${itemType}/${itemId}/invites`, this.headers()); } diff --git a/src/drive/share/types.ts b/src/drive/share/types.ts index 7a8a18db..12b76e8e 100644 --- a/src/drive/share/types.ts +++ b/src/drive/share/types.ts @@ -1,4 +1,5 @@ import { FolderChild } from '../storage/types'; +import { ItemType } from './../../workspaces/types'; export interface GenerateShareLinkPayload { itemId: string; @@ -78,6 +79,21 @@ export interface ShareDomainsResponse { list: string[]; } +export interface WorkspaceItemUser { + id: string; + itemId: string; + itemType: ItemType; + workspaceId: string; + createdAt: string | null; + updatedAt: string | null; + creator: { + uuid: string; + name: string; + lastname: string; + email: string; + }; +} + export type ListShareLinksItem = Pick< ShareLink, 'id' | 'token' | 'views' | 'timesValid' | 'active' | 'isFolder' | 'createdAt' | 'updatedAt' | 'fileSize' @@ -115,6 +131,7 @@ export type SharedFolders = { user: { avatar: string | null; email: string; lastname: string; name: string; uuid: string }; userId: number; uuid: string; + token: string | null; }; export type SharedFiles = { @@ -139,8 +156,10 @@ export type SharedFiles = { thumbnails: []; type: string; updatedAt: string; + user: { avatar: string | null; email: string; lastname: string; name: string; uuid: string }; userId: number; uuid: string; + token: string | null; }; export type ListSharedItemsResponse = { @@ -160,7 +179,7 @@ export type ListAllSharedFoldersResponse = { export type ShareFolderWithUserPayload = { itemId: string; - itemType: 'folder' | 'file'; + itemType: ItemType; notifyUser: boolean; notificationMessage?: string; sharedWith: string; @@ -172,7 +191,7 @@ export type ShareFolderWithUserPayload = { export type CreateSharingPayload = { itemId: string; - itemType: 'folder' | 'file'; + itemType: ItemType; encryptionKey: string; encryptionAlgorithm: string; encryptedCode: string; @@ -312,7 +331,7 @@ export type SharingInvitation = { export type SharingInvite = { id: string; itemId: string; - itemType: 'file' | 'folder'; + itemType: ItemType; sharedWith: string; encryptionKey: string; encryptionAlgorithm: string; @@ -325,7 +344,7 @@ export type SharingInvite = { export type SharingMeta = { id: string; itemId: string; - itemType: 'file' | 'folder'; + itemType: ItemType; ownerId: string; sharedWith: string; encryptionKey: string; diff --git a/src/drive/storage/index.ts b/src/drive/storage/index.ts index 6d8776af..6882a2ce 100644 --- a/src/drive/storage/index.ts +++ b/src/drive/storage/index.ts @@ -3,6 +3,7 @@ import { ApiSecurity, ApiUrl, AppDetails } from '../../shared'; import { CustomHeaders, addResourcesTokenToHeaders, headersWithToken } from '../../shared/headers'; import { HttpClient, RequestCanceler } from '../../shared/http/client'; import { UUID } from '../../shared/types/userSettings'; +import { ItemType } from './../../workspaces/types'; import { AddItemsToTrashPayload, CheckDuplicatedFilesPayload, @@ -23,6 +24,7 @@ import { FileEntryByUuid, FileMeta, FolderAncestor, + FolderAncestorWorkspace, FolderMeta, FolderTreeResponse, MoveFilePayload, @@ -202,8 +204,8 @@ export class Storage { const customHeaders = workspacesToken ? { - 'x-internxt-workspace': workspacesToken, - } + 'x-internxt-workspace': workspacesToken, + } : undefined; const { promise, requestCanceler } = this.client.getCancellable( `/folders/content/${folderUuid}?${query}`, @@ -223,8 +225,8 @@ export class Storage { public getFile(fileId: string, workspacesToken?: string): [Promise, RequestCanceler] { const customHeaders = workspacesToken ? { - 'x-internxt-workspace': workspacesToken, - } + 'x-internxt-workspace': workspacesToken, + } : undefined; const { promise, requestCanceler } = this.client.getCancellable( `/files/${fileId}/meta`, @@ -489,8 +491,8 @@ export class Storage { */ public updateFileMetaByUUID( fileUuid: string, - payload: { plainName?: string; type?: string | null; }, - resourcesToken?: Token + payload: { plainName?: string; type?: string | null }, + resourcesToken?: Token, ): Promise { return this.client.put( `/files/${fileUuid}/meta`, @@ -635,11 +637,30 @@ export class Storage { * Gets the ancestors of a given folder UUID * * @param {string} uuid - UUID of the folder. - * @param {boolean} [isShared=false] - Whether the folder is a shared item or not. * @returns {Promise} A promise that resolves with an array of ancestors of the given folder. */ - public getFolderAncestors(uuid: string, isShared = false): Promise { - return this.client.get(`folders/${uuid}/ancestors?isSharedItem=${isShared}`, this.headers()); + public getFolderAncestors(uuid: string): Promise { + return this.client.get(`folders/${uuid}/ancestors`, this.headers()); + } + + /** + * Gets the ancestors of an item with the given UUID and type in a Workspace + * + * @param {string} workspaceId - UUID of the workspace. + * @param {string} itemType - itemType to know if the item is file or folder + * @param {string} uuid - UUID of the item. + * @returns {Promise} A promise that resolves with an array of ancestors of the given folder. + */ + public getFolderAncestorsInWorkspace( + workspaceId: string, + itemType: ItemType, + uuid: string, + resourcesToken?: Token, + ): Promise { + return this.client.get( + `workspaces/${workspaceId}/${itemType}/${uuid}/ancestors`, + addResourcesTokenToHeaders(this.headers(), resourcesToken), + ); } /** @@ -651,8 +672,8 @@ export class Storage { public getFolderMeta(uuid: string, workspacesToken?: string, resourcesToken?: string): Promise { const customHeaders = workspacesToken ? { - 'x-internxt-workspace': workspacesToken, - } + 'x-internxt-workspace': workspacesToken, + } : undefined; return this.client.get( diff --git a/src/drive/storage/types.ts b/src/drive/storage/types.ts index 9c3dd837..5f1f0ff7 100644 --- a/src/drive/storage/types.ts +++ b/src/drive/storage/types.ts @@ -1,4 +1,5 @@ import { SharingMeta } from '../share/types'; +import { UserResumeData } from '../users/types'; export interface DriveFolderData { id: number; @@ -19,6 +20,7 @@ export interface DriveFolderData { userId: number; user_id: number; uuid: string; + user?: UserResumeData; } export interface DriveFileData { @@ -45,6 +47,7 @@ export interface DriveFileData { shares?: Array; sharings?: { type: string; id: string }[]; uuid: string; + user?: UserResumeData; } export interface Thumbnail { @@ -431,6 +434,10 @@ export interface FolderAncestor { userId: number; uuid: string; } +export interface FolderAncestorWorkspace { + uuid: string; + plainName: string; +} export interface FolderMeta { id: number; uuid: string; diff --git a/src/drive/users/types.ts b/src/drive/users/types.ts index ab645881..8b89e1b2 100644 --- a/src/drive/users/types.ts +++ b/src/drive/users/types.ts @@ -1,5 +1,13 @@ import { UUID, UserSettings } from '../../shared/types/userSettings'; +export interface UserResumeData { + avatar: string | null; + email: string; + lastname: string | null; + name: string; + uuid: string; +} + export type Token = string; export interface InitializeUserResponse { diff --git a/test/drive/storage/index.test.ts b/test/drive/storage/index.test.ts index 1295c046..db5da617 100644 --- a/test/drive/storage/index.test.ts +++ b/test/drive/storage/index.test.ts @@ -8,12 +8,14 @@ import { DriveFolderData, EncryptionVersion, FetchPaginatedFolderContentResponse, + FileMeta, + FolderAncestorWorkspace, MoveFolderPayload, MoveFolderResponse, UpdateFilePayload, } from '../../../src/drive/storage/types'; import { ApiSecurity, AppDetails } from '../../../src/shared'; -import { headersWithToken } from '../../../src/shared/headers'; +import { CustomHeaders, headersWithToken } from '../../../src/shared/headers'; import { HttpClient } from '../../../src/shared/http/client'; import { randomFileData } from './mothers/fileData.mother'; import { @@ -41,7 +43,7 @@ describe('# storage service tests', () => { it('Should return the expected elements, when getFolderContent is called', async () => { // Arrange const response = randomFolderContentResponse(2, 2); - const { client } = clientAndHeaders(); + const { client } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(response), requestCanceler: { @@ -69,7 +71,7 @@ describe('# storage service tests', () => { { ...subFolder2, type: 'folder' }, ], }; - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(response), requestCanceler: { @@ -102,7 +104,7 @@ describe('# storage service tests', () => { { ...subFolder2, type: 'file' }, ], }; - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(response), requestCanceler: { @@ -128,7 +130,7 @@ describe('# storage service tests', () => { // Arrange const responseSubfiles = randomSubfilesResponse(3); const randomUUID = v4(); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(responseSubfiles), requestCanceler: { @@ -153,7 +155,7 @@ describe('# storage service tests', () => { // Arrange const randomUUID = v4(); const responseSubfolders = randomSubfoldersResponse(4); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(responseSubfolders), @@ -177,7 +179,7 @@ describe('# storage service tests', () => { it('Should cancel the request', async () => { // Arrange - const { client } = clientAndHeaders(); + const { client } = clientAndHeaders({}); // Act const [promise, requestCanceler] = client.getFolderContent(1); @@ -190,7 +192,7 @@ describe('# storage service tests', () => { it('Should return the expected elements with trash', async () => { // Arrange const response = randomFolderContentResponse(2, 2); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const callStub = sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(response), requestCanceler: { @@ -241,7 +243,7 @@ describe('# storage service tests', () => { cancel: () => null, }, }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const [promise, requestCanceler] = client.createFolder(createFolderPayload); @@ -265,7 +267,7 @@ describe('# storage service tests', () => { folderName: 'ma-fol', parentFolderId: 34, }; - const { client } = clientAndHeaders(); + const { client } = clientAndHeaders({}); // Act const [promise, requestCanceler] = client.createFolder(createFolderPayload); @@ -301,7 +303,7 @@ describe('# storage service tests', () => { moved: false, }; const callStub = sinon.stub(httpClient, 'post').resolves(moveFolderResponse); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.moveFolder(payload); @@ -323,7 +325,7 @@ describe('# storage service tests', () => { it('Should call with right params & return correct response', async () => { // Arrange const payload = randomUpdateFolderMetadataPayload(); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const callStub = sinon.stub(httpClient, 'post').resolves({}); // Act @@ -350,7 +352,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'delete').resolves({ valid: true, }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.deleteFolder(2); @@ -369,7 +371,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'get').resolves({ size: 10, }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.getFolderSize(2); @@ -379,6 +381,84 @@ describe('# storage service tests', () => { expect(body).toEqual(10); }); }); + + describe('getFolderAncestorsInWorkspace', () => { + it('When resourceToken is provided then should call with right arguments & return content', async () => { + // Arrange + const workspaceId = v4(); + const itemType = 'folder'; + const itemUuid = v4() + const resourceToken = 'resource-token-workspace'; + const mockResponse: FolderAncestorWorkspace[] = [ + { + uuid: v4(), + plainName: 'mi-folder1', + }, + { + uuid: v4(), + plainName: 'mi-folder2', + }, + { + uuid: v4(), + plainName: 'mi-folder3', + }, + ]; + const callStub = sinon.stub(httpClient, 'get').resolves(mockResponse); + const { client, headers } = clientAndHeaders({ + customHeaders: { + 'internxt-resources-token': resourceToken, + }, + }); + + // Act + const body = await client.getFolderAncestorsInWorkspace( + workspaceId, + itemType, + itemUuid, + resourceToken, + ); + + // Assert + expect(callStub.firstCall.args).toEqual([ + `workspaces/${workspaceId}/${itemType}/${itemUuid}/ancestors`, + headers, + ]); + expect(body).toEqual(mockResponse); + }); + + it('When resourceToken is NOT provided then it should call the client.get method without the resourcesToken in headers', async () => { + // Arrange + const workspaceId = v4(); + const itemType = 'folder'; + const itemUuid = v4(); + const mockResponse: FolderAncestorWorkspace[] = [ + { + uuid: v4(), + plainName: 'mi-folder1', + }, + { + uuid: v4(), + plainName: 'mi-folder2', + }, + { + uuid: v4(), + plainName: 'mi-folder3', + }, + ]; + const callStub = sinon.stub(httpClient, 'get').resolves(mockResponse); + const { client, headers } = clientAndHeaders({}); + + // Act + const body = await client.getFolderAncestorsInWorkspace(workspaceId, itemType, itemUuid); + + // Assert + expect(callStub.firstCall.args).toEqual([ + `workspaces/${workspaceId}/${itemType}/${itemUuid}/ancestors`, + headers, + ]); + expect(body).toEqual(mockResponse); + }); + }); }); describe('-> files', () => { @@ -386,7 +466,7 @@ describe('# storage service tests', () => { it('Should have all the correct params on call', async () => { // Arrange const callStub = sinon.stub(httpClient, 'post').resolves({}); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const fileEntry: StorageTypes.FileEntry = { id: '1', type: 'type', @@ -435,7 +515,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'post').resolves({ valid: true, }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.updateFile(payload); @@ -464,7 +544,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'delete').resolves({ valid: true, }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const payload: DeleteFilePayload = { fileId: 5, folderId: 2, @@ -488,7 +568,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'post').resolves({ content: 'test', }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.moveFile(payload); @@ -516,7 +596,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'get').resolves({ files: [], }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.getRecentFiles(5); @@ -535,7 +615,7 @@ describe('# storage service tests', () => { const callStub = sinon.stub(httpClient, 'get').resolves({ files: [], }); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.getRecentFilesV2(5); @@ -556,7 +636,7 @@ describe('# storage service tests', () => { const fileUUID = v4(); const response = randomFileData(); const callStub = sinon.stub(httpClient, 'put').resolves(response); - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); // Act const body = await client.replaceFile(fileUUID, { @@ -576,13 +656,59 @@ describe('# storage service tests', () => { expect(body).toEqual(response); }); }); + + describe('getFile',() => { + it('When a fileId is provided without a workspaceToken then it should call getCancellable with the correct URL and headers', async () => { + // Arrange + const fileUUID = v4(); + const response = randomFileData() as FileMeta; + const callStub = sinon.stub(httpClient, 'getCancellable').returns({ + promise: Promise.resolve(response), + requestCanceler: { + cancel: () => null, + }, + }); + const workspaceToken = undefined; + const { client, headers } = clientAndHeaders({}); + + // Act + const [promise, _] = client.getFile(fileUUID, workspaceToken); + const body = await promise; + + // Assert + expect(callStub.firstCall.args).toEqual([`/files/${fileUUID}/meta`, headers]); + expect(body).toEqual(response); + }); + + it('When a fileId is provided with a workspaceToken then it should call getCancellable with the correct URL and custom headers', async() => { + // Arrange + const fileUUID = v4(); + const response = randomFileData() as FileMeta; + const callStub = sinon.stub(httpClient, 'getCancellable').returns({ + promise: Promise.resolve(response), + requestCanceler: { + cancel: () => null, + }, + }); + const workspaceToken = 'workspace-token'; + const { client, headers } = clientAndHeaders({ workspaceToken }); + + // Act + const [promise, _] = client.getFile(fileUUID, workspaceToken); + const body = await promise; + + // Assert + expect(callStub.firstCall.args).toEqual([`/files/${fileUUID}/meta`, headers]); + expect(body).toEqual(response); + }); + }); }); describe('-> quotas', () => { describe('space usage', () => { it('should call with right params & return response', async () => { // Arrange - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const callStub = sinon.stub(httpClient, 'get').resolves({ total: 10, }); @@ -601,7 +727,7 @@ describe('# storage service tests', () => { describe('space limit', () => { it('should call with right params & return response', async () => { // Arrange - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const callStub = sinon.stub(httpClient, 'get').resolves({ total: 10, }); @@ -622,7 +748,7 @@ describe('# storage service tests', () => { it('Should return the expected elements', async () => { // Arrange const response = randomFolderContentResponse(2, 2); - const { client } = clientAndHeaders(); + const { client } = clientAndHeaders({}); sinon.stub(httpClient, 'getCancellable').returns({ promise: Promise.resolve(response), requestCanceler: { @@ -641,7 +767,7 @@ describe('# storage service tests', () => { it('Should cancel the request', async () => { // Arrange - const { client } = clientAndHeaders(); + const { client } = clientAndHeaders({}); // Act const [promise, requestCanceler] = client.getTrash(); @@ -654,7 +780,7 @@ describe('# storage service tests', () => { describe('Add Items into trash', () => { it('should call with right params & return 200', async () => { - const { client, headers } = clientAndHeaders(); + const { client, headers } = clientAndHeaders({}); const callStub = sinon.stub(httpClient, 'post').resolves(true); const itemsToTrash = [ { id: 'id1', type: 'file' }, @@ -673,13 +799,23 @@ describe('# storage service tests', () => { }); }); -function clientAndHeaders( +function clientAndHeaders({ apiUrl = '', clientName = 'c-name', clientVersion = '0.1', token = 'my-token', mnemonic = 'nemo', -): { + workspaceToken = undefined, + customHeaders = undefined, +}: { + apiUrl?: string; + clientName?: string; + clientVersion?: string; + token?: string; + mnemonic?: string; + workspaceToken?: string; + customHeaders?: CustomHeaders; +}): { client: Storage; headers: object; } { @@ -691,6 +827,6 @@ function clientAndHeaders( token: token, }; const client = Storage.client(apiUrl, appDetails, apiSecurity); - const headers = headersWithToken(clientName, clientVersion, token); + const headers = headersWithToken(clientName, clientVersion, token, workspaceToken, customHeaders); return { client, headers }; }