diff --git a/package.json b/package.json index 2e84838665..92f3350d4b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@internxt/css-config": "^1.0.3", "@internxt/inxt-js": "=1.2.21", "@internxt/lib": "^1.2.0", - "@internxt/sdk": "=1.9.23", + "@internxt/sdk": "=1.9.28", "@internxt/ui": "^0.0.23", "@phosphor-icons/react": "^2.1.7", "@popperjs/core": "^2.11.6", diff --git a/src/app/backups/hooks/useBackupDeviceActions.ts b/src/app/backups/hooks/useBackupDeviceActions.ts index 723de60951..d80abbb22a 100644 --- a/src/app/backups/hooks/useBackupDeviceActions.ts +++ b/src/app/backups/hooks/useBackupDeviceActions.ts @@ -1,6 +1,6 @@ import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; import { Device } from 'app/backups/types'; -import { deleteBackupDeviceAsFolder } from 'app/drive/services/folder.service'; +import backupsService from '../services/backups.service'; import { DriveItemData, DriveFolderData as DriveWebFolderData } from 'app/drive/types'; import { AppDispatch } from 'app/store'; import { useAppSelector } from 'app/store/hooks'; @@ -79,7 +79,7 @@ export const useBackupDeviceActions = ( dispatch(backupsThunks.deleteDeviceThunk(selectedDevice)); } else { await dispatch(deleteItemsThunk([selectedDevice as DriveItemData])).unwrap(); - await deleteBackupDeviceAsFolder(selectedDevice as DriveWebFolderData); + await backupsService.deleteBackupDeviceAsFolder((selectedDevice as DriveWebFolderData).uuid); dispatch(backupsThunks.fetchDevicesThunk()); } } diff --git a/src/app/backups/services/backups.service.test.ts b/src/app/backups/services/backups.service.test.ts new file mode 100644 index 0000000000..2eee154550 --- /dev/null +++ b/src/app/backups/services/backups.service.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it, Mock, vi } from 'vitest'; +import backupsService from './backups.service'; +import { SdkFactory } from '../../core/factory/sdk'; + +vi.mock('../../core/factory/sdk', () => ({ + SdkFactory: { + getNewApiInstance: vi.fn(), + }, +})); + +describe('backupsService', () => { + describe('deleteBackupDeviceAsFolder', () => { + const mockFolderId = 'test-folder-id'; + + it('should call deleteBackupDeviceAsFolder with the correct folderId', async () => { + const mockResponse = vi.fn().mockResolvedValue({}); + const mockStorageClient = { deleteBackupDeviceAsFolder: mockResponse }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createBackupsClient: () => mockStorageClient, + }); + + await backupsService.deleteBackupDeviceAsFolder(mockFolderId); + + expect(mockStorageClient.deleteBackupDeviceAsFolder).toHaveBeenCalledWith(mockFolderId); + }); + }); +}); diff --git a/src/app/backups/services/backups.service.ts b/src/app/backups/services/backups.service.ts index 4919f4e2f5..804353dee4 100644 --- a/src/app/backups/services/backups.service.ts +++ b/src/app/backups/services/backups.service.ts @@ -15,7 +15,7 @@ const backupsService = { async getAllDevicesAsFolders(): Promise { const backupsClient = SdkFactory.getNewApiInstance().createBackupsClient(); const encryptedFolders = await backupsClient.getAllDevicesAsFolder(); - return encryptedFolders.map(mapBackupFolder); + return encryptedFolders.filter((folder) => !folder.deleted).map(mapBackupFolder); }, async getAllBackups(mac: string): Promise { @@ -41,6 +41,11 @@ const backupsService = { const backupsClient = SdkFactory.getNewApiInstance().createBackupsClient(); return backupsClient.deleteBackupDevice(device.id); }, + + deleteBackupDeviceAsFolder(folderId: string) { + const backupsClient = SdkFactory.getNewApiInstance().createBackupsClient(); + return backupsClient.deleteBackupDeviceAsFolder(folderId); + } }; export default backupsService; diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 75598f57eb..ecfee8551b 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -8,10 +8,10 @@ import WarningMessageWrapper from '../../../drive/components/WarningMessage/Warn import BackupsAsFoldersList from '../../components/BackupsAsFoldersList/BackupsAsFoldersList'; import DeviceList from '../../components/DeviceList/DeviceList'; import FileViewerWrapper from '../../../drive/components/FileViewer/FileViewerWrapper'; -import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; +import newStorageService from '../../../drive/services/new-storage.service'; import { deleteFile } from '../../../drive/services/file.service'; import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/deleteItemsThunk'; -import { DriveFolderData as DriveWebFolderData, DriveItemData } from '../../../drive/types'; +import { DriveItemData } from '../../../drive/types'; import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; import { useBackupListActions } from 'app/backups/hooks/useBackupListActions'; @@ -84,7 +84,7 @@ export default function BackupsView(): JSX.Element { const filteredCurrentItems = currentItems.filter((item) => !selectedItemsIDs.has(item.id)); try { const deletePromises = selectedItems.map((item) => - item.isFolder ? deleteBackupDeviceAsFolder(item as DriveWebFolderData) : deleteFile(item), + item.isFolder ? newStorageService.deleteFolderByUuid(item.uuid) : deleteFile(item), ); await Promise.all(deletePromises); dispatch(deleteItemsThunk(selectedItems)); diff --git a/src/app/core/factory/sdk/index.ts b/src/app/core/factory/sdk/index.ts index 97e44cb64b..39481ea85b 100644 --- a/src/app/core/factory/sdk/index.ts +++ b/src/app/core/factory/sdk/index.ts @@ -61,13 +61,6 @@ export class SdkFactory { return Auth.client(apiUrl, appDetails, apiSecurity); } - public createStorageClient(): Storage { - const apiUrl = this.getApiUrl(); - const appDetails = SdkFactory.getAppDetails(); - const apiSecurity = this.getApiSecurity(); - return Storage.client(apiUrl, appDetails, apiSecurity); - } - public createNewStorageClient(): Storage { const apiUrl = this.getApiUrl(); const appDetails = SdkFactory.getAppDetails(); diff --git a/src/app/drive/components/DeleteBackupDialog/DeleteBackupDialog.tsx b/src/app/drive/components/DeleteBackupDialog/DeleteBackupDialog.tsx index 0feb4a47cf..97a3479737 100644 --- a/src/app/drive/components/DeleteBackupDialog/DeleteBackupDialog.tsx +++ b/src/app/drive/components/DeleteBackupDialog/DeleteBackupDialog.tsx @@ -4,12 +4,11 @@ import { useAppDispatch, useAppSelector } from 'app/store/hooks'; import { RootState } from 'app/store'; import { DriveFolderData as DriveWebFolderData, DriveItemData } from '../../types'; import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/deleteItemsThunk'; -import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; import { backupsThunks } from 'app/store/slices/backups'; -import { SdkFactory } from '../../../core/factory/sdk'; import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; import { Dialog } from '@internxt/ui'; +import backupsService from 'app/backups/services/backups.service'; interface DeleteBackupDialogProps { backupsAsFoldersPath: DriveFolderData[]; @@ -35,7 +34,7 @@ const DeleteBackupDialog = (props: DeleteBackupDialogProps): JSX.Element => { if (currentDevice && 'mac' in currentDevice) dispatch(backupsThunks.deleteDeviceThunk(currentDevice)); else { await dispatch(deleteItemsThunk([currentDevice as DriveItemData])).unwrap(); - await deleteBackupDeviceAsFolder(currentDevice as DriveWebFolderData); + await backupsService.deleteBackupDeviceAsFolder((currentDevice as DriveWebFolderData).uuid); await dispatch(backupsThunks.fetchDevicesThunk()); } onClose(); @@ -45,8 +44,7 @@ const DeleteBackupDialog = (props: DeleteBackupDialogProps): JSX.Element => { } } else { try { - const storageClient = SdkFactory.getInstance().createStorageClient(); - await storageClient.deleteFolder(currentBackupsAsFoldersPath.id); + await backupsService.deleteBackupDeviceAsFolder(currentBackupsAsFoldersPath.uuid); await dispatch(backupsThunks.fetchDevicesThunk()); onClose(); props.goToFolder(previousBackupsAsFoldersPath.id); diff --git a/src/app/drive/services/file.service/index.test.ts b/src/app/drive/services/file.service/index.test.ts new file mode 100644 index 0000000000..86e9c47917 --- /dev/null +++ b/src/app/drive/services/file.service/index.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, Mock, vi } from 'vitest'; +import { deleteFile } from '.'; +import { SdkFactory } from '../../../core/factory/sdk'; +import { DriveFileData } from '../../../drive/types'; +import { EncryptionVersion, FileStatus } from '@internxt/sdk/dist/drive/storage/types'; + +vi.mock('../../../core/factory/sdk', () => ({ + SdkFactory: { + getNewApiInstance: vi.fn(), + }, +})); + +describe('fileService', () => { + describe('deleteFileByUuid', () => { + const mockFile: DriveFileData = { + id: 0, + uuid: 'uuid', + bucket: 'bucket', + name: 'File1', + plainName: 'File1', + plain_name: 'File1', + type: 'jpg', + size: 100, + fileId: 'fileId1', + folder_id: 1, + folderId: 1, + folderUuid: 'uuid1', + createdAt: new Date().toISOString(), + created_at: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deleted: false, + deletedAt: null, + currentThumbnail: null, + encrypt_version: EncryptionVersion.Aes03, + status: FileStatus.EXISTS, + thumbnails: [], + }; + + it('should call deleteFileByUuid with the correct folderId', async () => { + const mockResponse = vi.fn().mockResolvedValue({}); + const mockStorageClient = { deleteFileByUuid: mockResponse }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + await deleteFile(mockFile); + + expect(mockStorageClient.deleteFileByUuid).toHaveBeenCalledWith(mockFile.uuid); + }); + }); +}); diff --git a/src/app/drive/services/file.service/index.ts b/src/app/drive/services/file.service/index.ts index e828fa25af..2b0dac611e 100644 --- a/src/app/drive/services/file.service/index.ts +++ b/src/app/drive/services/file.service/index.ts @@ -1,7 +1,6 @@ import { StorageTypes } from '@internxt/sdk/dist/drive'; import { FileMeta } from '@internxt/sdk/dist/drive/storage/types'; import { t } from 'i18next'; -import * as uuid from 'uuid'; import { SdkFactory } from '../../../core/factory/sdk'; import errorService from '../../../core/services/error.service'; import { DriveFileData, DriveFileMetadataPayload } from '../../types'; @@ -18,32 +17,6 @@ export function updateMetaData( return storageClient.updateFileNameWithUUID(payload, resourcesToken); } -export async function moveFile( - fileId: string, - destination: number, - bucketId: string, -): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - const payload: StorageTypes.MoveFilePayload = { - fileId: fileId, - destination: destination, - bucketId: bucketId, - destinationPath: uuid.v4(), - }; - return storageClient - .moveFile(payload) - .then((response) => { - return response; - }) - .catch((error) => { - const castedError = errorService.castError(error); - if (castedError.status) { - castedError.message = t(`tasks.move-file.errors.${castedError.status}`); - } - throw castedError; - }); -} - export async function moveFileByUuid(fileUuid: string, destinationFolderUuid: string): Promise { const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); const payload: StorageTypes.MoveFileUuidPayload = { @@ -65,8 +38,8 @@ export async function moveFileByUuid(fileUuid: string, destinationFolderUuid: st } export async function deleteFile(fileData: DriveFileData): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - await storageClient.deleteFile({ fileId: fileData.id, folderId: fileData.folderId }); + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); + await storageClient.deleteFileByUuid(fileData.uuid); } async function fetchRecents(limit: number): Promise { @@ -94,7 +67,6 @@ export function getFile(uuid: string, workspacesToken?: string): Promise, RequestCanceler] { - const payload: StorageTypes.CreateFolderPayload = { - parentFolderId: currentFolderId, - folderName: folderName, - }; - const storageClient = SdkFactory.getInstance().createStorageClient(); - const [createdFolderPromise, requestCanceler] = storageClient.createFolder(payload); - - const finalPromise = createdFolderPromise - .then((response) => { - return response; - }) - .catch((error) => { - throw errorService.castError(error); - }); - - return [finalPromise, requestCanceler]; -} - export function createFolderByUuid( parentFolderUuid: string, plainName: string, @@ -159,11 +137,6 @@ export async function deleteFolder(folderData: DriveFolderData): Promise { await trashClient.deleteFolder(folderData.id); } -export async function deleteBackupDeviceAsFolder(folderData: DriveFolderData): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - await storageClient.deleteFolder(folderData.id); -} - interface GetDirectoryFoldersResponse { folders: DriveFolderData[]; last: boolean; @@ -588,27 +561,6 @@ async function fetchFolderTree(folderUUID: string): Promise<{ return { tree, folderDecryptedNames, fileDecryptedNames, size }; } -export async function moveFolder(folderId: number, destination: number): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - const payload: StorageTypes.MoveFolderPayload = { - folderId: folderId, - destinationFolderId: destination, - }; - - return storageClient - .moveFolder(payload) - .then((response) => { - return response; - }) - .catch((err) => { - const castedError = errorService.castError(err); - if (castedError.status) { - castedError.message = t(`tasks.move-folder.errors.${castedError.status}`); - } - throw castedError; - }); -} - export async function moveFolderByUuid( folderUuid: string, destinationFolderUuid: string, @@ -629,10 +581,8 @@ export async function moveFolderByUuid( } const folderService = { - createFolder, createFolderByUuid, updateMetaData, - moveFolder, moveFolderByUuid, fetchFolderTree, downloadFolderAsZip, diff --git a/src/app/drive/services/folder.service/index.ts b/src/app/drive/services/folder.service/index.ts deleted file mode 100644 index 3f12c83984..0000000000 --- a/src/app/drive/services/folder.service/index.ts +++ /dev/null @@ -1,316 +0,0 @@ -import errorService from '../../../core/services/error.service'; -import httpService from '../../../core/services/http.service'; -import { DriveFileData, DriveFolderData, DriveItemData } from '../../types'; - -import { StorageTypes } from '@internxt/sdk/dist/drive'; -import { RequestCanceler } from '@internxt/sdk/dist/shared/http/types'; -import { Iterator } from 'app/core/collections'; -import { FlatFolderZip } from 'app/core/services/zip.service'; -import { downloadFile } from 'app/network/download'; -import { t } from 'i18next'; -import { SdkFactory } from '../../../core/factory/sdk'; -import localStorageService from '../../../core/services/local-storage.service'; - -export interface IFolders { - bucket: string; - color: string; - createdAt: Date; - encrypt_version: string; - icon: string; - iconId: number | null; - icon_id: number | null; - id: number; - name: string; - parentId: number; - parent_id: number; - updatedAt: Date; - userId: number; - user_id: number; -} - -export interface FolderChild { - bucket: string; - color: string; - createdAt: string; - encrypt_version: string; - icon: string; - iconId: number | null; - icon_id: number | null; - id: number; - name: string; - parentId: number; - parent_id: number; - updatedAt: string; - userId: number; - user_id: number; -} - -export interface FetchFolderContentResponse { - bucket: string; - children: FolderChild[]; - color: string; - createdAt: string; - encrypt_version: string; - files: DriveItemData[]; - icon: string; - id: number; - name: string; - parentId: number; - parent_id: number; - updatedAt: string; - userId: number; - user_id: number; -} - -export function createFolder( - currentFolderId: number, - folderName: string, -): [Promise, RequestCanceler] { - const payload: StorageTypes.CreateFolderPayload = { - parentFolderId: currentFolderId, - folderName: folderName, - }; - - const storageClient = SdkFactory.getInstance().createStorageClient(); - const [createdFolderPromise, requestCanceler] = storageClient.createFolder(payload); - - const finalPromise = createdFolderPromise - .then((response) => { - return response; - }) - .catch((error) => { - throw errorService.castError(error); - }); - - return [finalPromise, requestCanceler]; -} - -export async function deleteFolder(folderData: DriveFolderData): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - await storageClient.deleteFolder(folderData.id); -} - -interface GetDirectoryFoldersResponse { - folders: DriveFolderData[]; - last: boolean; -} -class DirectoryFolderIterator implements Iterator { - private offset: number; - private limit: number; - private readonly queryValues: { directoryId: number }; - - constructor(queryValues: { directoryId: number }, limit?: number, offset?: number) { - this.limit = limit || 5; - this.offset = offset || 0; - this.queryValues = queryValues; - } - - async next() { - const { directoryId } = this.queryValues; - const { folders, last } = await httpService.get( - `/storage/v2/folders/${directoryId}/folders?limit=${this.limit}&offset=${this.offset}`, - ); - - this.offset += this.limit; - - return { value: folders, done: last }; - } -} - -interface GetDirectoryFilesResponse { - files: DriveFileData[]; - last: boolean; -} -class DirectoryFilesIterator implements Iterator { - private offset: number; - private limit: number; - private readonly queryValues: { directoryId: number }; - - constructor(queryValues: { directoryId: number }, limit?: number, offset?: number) { - this.limit = limit || 5; - this.offset = offset || 0; - this.queryValues = queryValues; - } - - async next() { - const { directoryId } = this.queryValues; - const { files, last } = await httpService.get( - `/storage/v2/folders/${directoryId}/files?limit=${this.limit}&offset=${this.offset}`, - ); - - this.offset += this.limit; - - return { value: files, done: last }; - } -} - -interface FolderRef { - name: string; - folderId: number; -} - -async function addAllFilesToZip( - currentAbsolutePath: string, - downloadFile: (file: DriveFileData) => Promise, - iterator: Iterator, - zip: FlatFolderZip, -): Promise { - let pack = await iterator.next(); - let files = pack.value; - let moreFiles = !pack.done; - - const path = currentAbsolutePath; - const allFiles: DriveFileData[] = []; - - do { - const nextChunkRequest = iterator.next(); - - allFiles.push(...files); - - for (const file of files) { - const fileStream = await downloadFile(file); - await zip.addFile(path + '/' + file.name + (file.type ? '.' + file.type : ''), fileStream); - } - - pack = await nextChunkRequest; - files = pack.value; - moreFiles = !pack.done; - } while (moreFiles); - - return allFiles; -} - -async function addAllFoldersToZip( - currentAbsolutePath: string, - iterator: Iterator, - zip: FlatFolderZip, -): Promise { - let pack = await iterator.next(); - let folders = pack.value; - let moreFolders = !pack.done; - - const path = currentAbsolutePath; - const allFolders: DriveFolderData[] = []; - - do { - const nextChunkRequest = iterator.next(); - - allFolders.push(...folders); - - for (const folder of folders) { - await zip.addFolder(path + '/' + folder.name); - } - - pack = await nextChunkRequest; - folders = pack.value; - moreFolders = !pack.done; - } while (moreFolders); - - return allFolders; -} - -async function downloadFolderAsZip( - folderId: DriveFolderData['id'], - folderName: DriveFolderData['name'], - updateProgress: (progress: number) => void, -): Promise { - const rootFolder: FolderRef = { folderId: folderId, name: folderName }; - const pendingFolders: FolderRef[] = [rootFolder]; - let totalSize = 0; - let totalSizeIsReady = false; - - const zip = new FlatFolderZip(rootFolder.name, { - // TODO: check why opts.progress is causing zip corruption - // progress(loadedBytes) { - // if (!totalSizeIsReady) { - // return; - // } - // updateProgress(Math.min(loadedBytes / totalSize, 1)); - // }, - }); - - const user = localStorageService.getUser(); - - if (!user) { - throw new Error('user null'); - } - - try { - do { - const folderToDownload = pendingFolders.shift() as FolderRef; - - const foldersIterator: Iterator = new DirectoryFolderIterator( - { directoryId: folderToDownload.folderId }, - 20, - 0, - ); - const filesIterator: Iterator = new DirectoryFilesIterator( - { directoryId: folderToDownload.folderId }, - 20, - 0, - ); - - const files = await addAllFilesToZip( - folderToDownload.name, - (file) => { - return downloadFile({ - bucketId: file.bucket, - fileId: file.fileId, - creds: { - user: user.bridgeUser, - pass: user.userId, - }, - mnemonic: user.mnemonic, - }); - }, - filesIterator, - zip, - ); - - totalSize += files.reduce((a, f) => f.size + a, 0); - - const folders = await addAllFoldersToZip(folderToDownload.name, foldersIterator, zip); - - pendingFolders.push( - ...folders.map((f) => { - return { - name: folderToDownload.name + '/' + f.name, - folderId: f.id, - }; - }), - ); - } while (pendingFolders.length > 0); - - totalSizeIsReady = true; - - await zip.close(); - } catch (err) { - zip.abort(); - throw errorService.castError(err); - } -} - -export async function moveFolder(folderId: number, destination: number): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - const payload: StorageTypes.MoveFolderPayload = { - folderId: folderId, - destinationFolderId: destination, - }; - - return storageClient.moveFolder(payload).catch((err) => { - const castedError = errorService.castError(err); - if (castedError.status) { - castedError.message = t(`tasks.move-folder.errors.${castedError.status}`); - } - throw castedError; - }); -} - -const folderService = { - createFolder, - deleteFolder, - moveFolder, - downloadFolderAsZip, -}; - -export default folderService; diff --git a/src/app/drive/services/limit.service.test.ts b/src/app/drive/services/limit.service.test.ts new file mode 100644 index 0000000000..d5c7649ce7 --- /dev/null +++ b/src/app/drive/services/limit.service.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, vi, Mock } from 'vitest'; +import { SdkFactory } from '../../core/factory/sdk'; +import limitService from './limit.service'; + +vi.mock('./size.service', () => ({ + default: { + bytesToString: vi.fn(), + }, + bytesToString: vi.fn(), +})); + +vi.mock('../../core/factory/sdk', () => ({ + SdkFactory: { + getNewApiInstance: vi.fn(), + }, +})); + +describe('limitService', () => { + describe('fetchLimit', () => { + it('should fetch the space limit from the storage client', async () => { + const expectedLimit = 50000; + const mockResponse = vi.fn().mockResolvedValue({ maxSpaceBytes: expectedLimit }); + const mockStorageClient = { spaceLimitV2: mockResponse }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + const result = await limitService.fetchLimit(); + + expect(mockStorageClient.spaceLimitV2).toHaveBeenCalled(); + expect(result).toEqual(expectedLimit); + }); + }); +}); diff --git a/src/app/drive/services/limit.service.ts b/src/app/drive/services/limit.service.ts index ebe3800c99..b3337c6605 100644 --- a/src/app/drive/services/limit.service.ts +++ b/src/app/drive/services/limit.service.ts @@ -3,8 +3,8 @@ import { SdkFactory } from '../../core/factory/sdk'; import { HUNDRED_TB } from 'app/core/components/Sidenav/Sidenav'; async function fetchLimit(): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - return storageClient.spaceLimit().then((response) => { + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); + return storageClient.spaceLimitV2().then((response) => { return response.maxSpaceBytes; }); } diff --git a/src/app/drive/services/new-storage.service.test.ts b/src/app/drive/services/new-storage.service.test.ts new file mode 100644 index 0000000000..c1a9401ed1 --- /dev/null +++ b/src/app/drive/services/new-storage.service.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it, Mock, vi } from 'vitest'; +import newStorageService from './new-storage.service'; +import { SdkFactory } from '../../core/factory/sdk'; + +vi.mock('../../core/factory/sdk', () => ({ + SdkFactory: { + getNewApiInstance: vi.fn(), + }, +})); + +describe('backupsService', () => { + describe('deleteFolderByUuid', () => { + const mockFolderId = 'test-folder-id'; + + it('should call deleteFolderByUuid with the correct folderId', async () => { + const mockResponse = vi.fn().mockResolvedValue({}); + const mockStorageClient = { deleteFolderByUuid: mockResponse }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + await newStorageService.deleteFolderByUuid(mockFolderId); + + expect(mockStorageClient.deleteFolderByUuid).toHaveBeenCalledWith(mockFolderId); + }); + }); +}); diff --git a/src/app/drive/services/new-storage.service.ts b/src/app/drive/services/new-storage.service.ts index 35e9ea7060..1237f4fba6 100644 --- a/src/app/drive/services/new-storage.service.ts +++ b/src/app/drive/services/new-storage.service.ts @@ -84,6 +84,11 @@ export function getFolderContentByUuid({ }); } +export function deleteFolderByUuid(folderId: string) { + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); + return storageClient.deleteFolderByUuid(folderId); +} + const newStorageService = { searchItemsByName, getFolderAncestors, @@ -93,6 +98,7 @@ const newStorageService = { checkDuplicatedFiles, checkDuplicatedFolders, getFolderContentByUuid, + deleteFolderByUuid, }; export default newStorageService; diff --git a/src/app/drive/services/usage.service.test.ts b/src/app/drive/services/usage.service.test.ts new file mode 100644 index 0000000000..d237106a2b --- /dev/null +++ b/src/app/drive/services/usage.service.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it, Mock, vi } from 'vitest'; +import usageService from './usage.service'; +import { SdkFactory } from '../../core/factory/sdk'; +import errorService from '../../core/services/error.service'; + +vi.mock('../../core/factory/sdk', () => ({ + SdkFactory: { + getNewApiInstance: vi.fn(), + }, +})); + +vi.mock('../../core/services/error.service', () => ({ + default: { + reportError: vi.fn(), + } +})); + +describe('usageService', () => { + describe('fetchUsage', () => { + it('should fetch usage data successfully', async () => { + const mockSpaceUsageV2 = vi.fn().mockResolvedValue({ drive: 100, backups: 50 }); + const mockStorageClient = { spaceUsageV2: mockSpaceUsageV2 }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + const result = await usageService.fetchUsage(); + + expect(mockSpaceUsageV2).toHaveBeenCalled(); + expect(result).toEqual({ drive: 100, backups: 50 }); + }); + }); + + describe('getUsageDetails', () => { + it('should return usage details successfully', async () => { + const mockSpaceUsageV2 = vi.fn().mockResolvedValue({ drive: 200, backups: 100 }); + const mockStorageClient = { spaceUsageV2: mockSpaceUsageV2 }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + const result = await usageService.getUsageDetails(); + + expect(mockSpaceUsageV2).toHaveBeenCalled(); + expect(result).toEqual({ drive: 200, photos: 0, backups: 100 }); + }); + + it('should handle errors and return default values', async () => { + const mockSpaceUsageV2 = vi.fn().mockRejectedValue(new Error('API error')); + const mockStorageClient = { spaceUsageV2: mockSpaceUsageV2 }; + (SdkFactory.getNewApiInstance as Mock).mockReturnValue({ + createNewStorageClient: () => mockStorageClient, + }); + + const result = await usageService.getUsageDetails(); + expect(errorService.reportError).toHaveBeenCalledWith(expect.any(Error)); + expect(mockSpaceUsageV2).toHaveBeenCalled(); + expect(errorService.reportError).toHaveBeenCalledWith(new Error('API error')); + expect(result).toEqual({ drive: 0, photos: 0, backups: 0 }); + }); + }); + + describe('getUsagePercent', () => { + it('should calculate usage percentage correctly', () => { + const usage = 50; + const limit = 200; + + const result = usageService.getUsagePercent(usage, limit); + + expect(result).toBe(25); + }); + }); +}); diff --git a/src/app/drive/services/usage.service.ts b/src/app/drive/services/usage.service.ts index e4fd5b5983..27a5efd80a 100644 --- a/src/app/drive/services/usage.service.ts +++ b/src/app/drive/services/usage.service.ts @@ -1,4 +1,4 @@ -import { UsageResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { UsageResponseV2 } from '@internxt/sdk/dist/drive/storage/types'; import { SdkFactory } from '../../core/factory/sdk'; import errorService from '../../core/services/error.service'; @@ -8,21 +8,21 @@ export interface UsageDetailsProps { backups: number; } -export async function fetchUsage(): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); - const driveUsage = await storageClient.spaceUsage(); +export async function fetchUsage(): Promise { + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); + const driveUsage = await storageClient.spaceUsageV2(); return driveUsage; } async function getUsageDetails(): Promise { - const storageClient = SdkFactory.getInstance().createStorageClient(); + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); let drive = 0; let backups = 0; try { - const { drive: storageDrive, backups: storageBackups } = await storageClient.spaceUsage(); + const { drive: storageDrive, backups: storageBackups } = await storageClient.spaceUsageV2(); drive = storageDrive; backups = storageBackups; } catch (error) { diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 71218eecf2..e1e8c61251 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -7,7 +7,6 @@ import { queue, QueueObject } from 'async'; import { t } from 'i18next'; import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; import { RootState } from '../store'; -import { SdkFactory } from '../core/factory/sdk'; import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; import { planThunks } from '../store/slices/plan'; import { uploadItemsParallelThunk } from '../store/slices/storage/storage.thunks/uploadItemsThunk'; @@ -18,6 +17,7 @@ import { getUniqueFolderName } from '../store/slices/storage/folderUtils/getUniq import { ConnectionLostError } from './requests'; import { QueueUtilsService } from '../utils/queueUtils'; import { wait } from '../utils/timeUtils'; +import newStorageService from '../drive/services/new-storage.service'; interface UploadFolderPayload { root: IRoot; @@ -300,8 +300,7 @@ export class UploadFoldersManager { const rootFolderItem = this.tasksInfo[taskId].rootFolderItem; if (rootFolderItem) { promises.push(this.dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); - const storageClient = SdkFactory.getInstance().createStorageClient(); - promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); + promises.push(newStorageService.deleteFolderByUuid(rootFolderItem.uuid)); } await Promise.allSettled(promises); }; diff --git a/src/app/newSettings/Sections/Account/Plans/utils/planUtils.ts b/src/app/newSettings/Sections/Account/Plans/utils/planUtils.ts index e7e3e72e09..0efdc3ce0d 100644 --- a/src/app/newSettings/Sections/Account/Plans/utils/planUtils.ts +++ b/src/app/newSettings/Sections/Account/Plans/utils/planUtils.ts @@ -4,7 +4,7 @@ import { StoragePlan, UserSubscription, } from '@internxt/sdk/dist/drive/payments/types/types'; -import { UsageResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { UsageResponseV2 } from '@internxt/sdk/dist/drive/storage/types'; import { bytesToString } from 'app/drive/services/size.service'; import { t } from 'i18next'; import { FreeStoragePlan } from '../../../../../drive/types'; @@ -86,7 +86,7 @@ const getPlanName = (storagePlan: StoragePlan | null, limit?: number) => { if (limit) return bytesToString(limit, false); return FreeStoragePlan.simpleName; }; -const getCurrentUsage = (usage: UsageResponse | null) => { +const getCurrentUsage = (usage: UsageResponseV2 | null) => { return usage?.total ?? -1; }; diff --git a/src/app/store/slices/plan/index.ts b/src/app/store/slices/plan/index.ts index f5ca40067a..8784324155 100644 --- a/src/app/store/slices/plan/index.ts +++ b/src/app/store/slices/plan/index.ts @@ -1,7 +1,7 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { StoragePlan, UserSubscription, UserType } from '@internxt/sdk/dist/drive/payments/types/types'; -import { UsageResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { UsageResponseV2 } from '@internxt/sdk/dist/drive/storage/types'; import { GetMemberUsageResponse } from '@internxt/sdk/dist/workspaces'; import workspacesService from 'app/core/services/workspace.service'; import limitService from 'app/drive/services/limit.service'; @@ -20,12 +20,12 @@ export interface PlanState { teamPlan: StoragePlan | null; planLimit: number; planUsage: number; - usageDetails: UsageResponse | null; + usageDetails: UsageResponseV2 | null; individualSubscription: UserSubscription | null; businessSubscription: UserSubscription | null; businessPlanLimit: number; businessPlanUsage: number; - businessPlanUsageDetails: UsageResponse | null; + businessPlanUsageDetails: UsageResponseV2 | null; } const initialState: PlanState = { @@ -77,7 +77,7 @@ export const fetchLimitThunk = createAsyncThunk( +export const fetchUsageThunk = createAsyncThunk( 'plan/fetchUsage', async (payload: void, { getState }) => { const isAuthenticated = getState().user.isAuthenticated; @@ -201,9 +201,10 @@ export const planSlice = createSlice({ state.businessPlanLimit = spaceLimit; state.businessPlanUsage = driveUsage + backupsUsage; state.businessPlanUsageDetails = { - driveUsage, - backupsUsage, - } as unknown as UsageResponse; + drive: driveUsage, + backups: backupsUsage, + total: driveUsage + backupsUsage, + }; }) .addCase(fetchBusinessLimitUsageThunk.rejected, () => undefined); }, diff --git a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts index f298310fd4..f7a7cd6b1b 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts @@ -3,7 +3,6 @@ import { ActionReducerMapBuilder, AnyAction, ThunkDispatch, createAsyncThunk } f import { t } from 'i18next'; import storageThunks from '.'; import { RootState } from '../../..'; -import { SdkFactory } from '../../../../core/factory/sdk'; import errorService from '../../../../core/services/error.service'; import { DriveFolderData, DriveItemData } from '../../../../drive/types'; import notificationsService, { ToastType } from '../../../../notifications/services/notifications.service'; @@ -19,6 +18,7 @@ import { deleteItemsThunk } from './deleteItemsThunk'; import { uploadItemsParallelThunk } from './uploadItemsThunk'; import { IRoot } from '../types'; import { wait } from '../../../../utils/timeUtils'; +import newStorageService from '../../../../drive/services/new-storage.service'; interface UploadFolderThunkPayload { root: IRoot; @@ -63,8 +63,7 @@ const stopUploadTask = async ( // Deletes the root folder if (rootFolderItem) { promises.push(dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); - const storageClient = SdkFactory.getInstance().createStorageClient(); - promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); + promises.push(newStorageService.deleteFolderByUuid(rootFolderItem.uuid)); } await Promise.all(promises); }; diff --git a/yarn.lock b/yarn.lock index b90fc234a1..bf59820326 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1919,10 +1919,10 @@ resolved "https://npm.pkg.github.com/download/@internxt/prettier-config/1.0.2/5bd220b8de76734448db5475b3e0c01f9d22c19b#5bd220b8de76734448db5475b3e0c01f9d22c19b" integrity sha512-t4HiqvCbC7XgQepwWlIaFJe3iwW7HCf6xOSU9nKTV0tiGqOPz7xMtIgLEloQrDA34Cx4PkOYBXrvFPV6RxSFAA== -"@internxt/sdk@=1.9.23": - version "1.9.23" - resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.9.23/a0064edd366121e977779254bfd43d95f5a2b470#a0064edd366121e977779254bfd43d95f5a2b470" - integrity sha512-aGJquus+ck51Db9SryQ62vvlJVLubMsvqjEl5kwpX2f+YJmEyGvVg3MR4ceRuDC4DHrbsmPa6kkTed0+bAR/wQ== +"@internxt/sdk@=1.9.28": + version "1.9.28" + resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.9.28/194656db26cc9c34bf71da6826ec818af5fffc18#194656db26cc9c34bf71da6826ec818af5fffc18" + integrity sha512-zVB/oG3HNhPaNTKAK7ATydubHZfaSXptzYNbWzj5NUhG+mGO2FQEkICblupUux+dxPvG5IFSAQGSsiyM9kGEQw== dependencies: axios "^0.24.0" query-string "^7.1.0"