feat: implement file copy functionality from reverse shares to user files

- Added a new endpoint to copy files from reverse shares to a user's personal files, ensuring only the creator can perform this action.
- Implemented error handling for various scenarios, including file not found, unauthorized access, and storage limitations.
- Updated the UI to include a "Copy to my files" action, enhancing user experience and accessibility.
- Localized new messages for success and error states in both English and Portuguese.
- Refactored related components to support the new copy functionality, ensuring a seamless integration into the existing workflow.
This commit is contained in:
Daniel Luiz Alves
2025-06-21 11:37:47 -03:00
parent c265b8e08d
commit 978a1e5755
14 changed files with 615 additions and 380 deletions

View File

@@ -350,8 +350,9 @@ export class ReverseShareService {
throw new Error("Unauthorized to download this file");
}
const fileName = file.name;
const expires = 3600; // 1 hour
const url = await this.fileService.getPresignedGetUrl(file.objectName, expires);
const url = await this.fileService.getPresignedGetUrl(file.objectName, expires, fileName);
return { url, expiresIn: expires };
}
@@ -485,6 +486,96 @@ export class ReverseShareService {
return this.formatFileResponse(updatedFile);
}
async copyReverseShareFileToUserFiles(fileId: string, creatorId: string) {
const file = await this.reverseShareRepository.findFileById(fileId);
if (!file) {
throw new Error("File not found");
}
if (file.reverseShare.creatorId !== creatorId) {
throw new Error("Unauthorized to copy this file");
}
const { prisma } = await import("../../shared/prisma.js");
const { ConfigService } = await import("../config/service.js");
const configService = new ConfigService();
const maxFileSize = BigInt(await configService.getValue("maxFileSize"));
if (file.size > maxFileSize) {
const maxSizeMB = Number(maxFileSize) / (1024 * 1024);
throw new Error(`File size exceeds the maximum allowed size of ${maxSizeMB}MB`);
}
const maxTotalStorage = BigInt(await configService.getValue("maxTotalStoragePerUser"));
const userFiles = await prisma.file.findMany({
where: { userId: creatorId },
select: { size: true },
});
const currentStorage = userFiles.reduce((acc: bigint, userFile: any) => acc + userFile.size, BigInt(0));
if (currentStorage + file.size > maxTotalStorage) {
const availableSpace = Number(maxTotalStorage - currentStorage) / (1024 * 1024);
throw new Error(`Insufficient storage space. You have ${availableSpace.toFixed(2)}MB available`);
}
const newObjectName = `${creatorId}/${Date.now()}-${file.name}`;
if (this.fileService.isFilesystemMode()) {
const { FilesystemStorageProvider } = await import("../../providers/filesystem-storage.provider.js");
const provider = FilesystemStorageProvider.getInstance();
const sourceBuffer = await provider.downloadFile(file.objectName);
await provider.uploadFile(newObjectName, sourceBuffer);
} else {
const downloadUrl = await this.fileService.getPresignedGetUrl(file.objectName, 300);
const uploadUrl = await this.fileService.getPresignedPutUrl(newObjectName, 300);
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new Error(`Failed to download file: ${response.statusText}`);
}
const fileBuffer = Buffer.from(await response.arrayBuffer());
const uploadResponse = await fetch(uploadUrl, {
method: "PUT",
body: fileBuffer,
headers: {
"Content-Type": "application/octet-stream",
},
});
if (!uploadResponse.ok) {
throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);
}
}
const newFileRecord = await prisma.file.create({
data: {
name: file.name,
description: file.description || `Copied from: ${file.reverseShare.name || "Unnamed"}`,
extension: file.extension,
size: file.size,
objectName: newObjectName,
userId: creatorId,
},
});
return {
id: newFileRecord.id,
name: newFileRecord.name,
description: newFileRecord.description,
extension: newFileRecord.extension,
size: newFileRecord.size.toString(),
objectName: newFileRecord.objectName,
userId: newFileRecord.userId,
createdAt: newFileRecord.createdAt.toISOString(),
updatedAt: newFileRecord.updatedAt.toISOString(),
};
}
private formatReverseShareResponse(reverseShare: ReverseShareData) {
const result = {
id: reverseShare.id,