diff --git a/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.Designer.cs b/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.Designer.cs new file mode 100644 index 00000000..f2e8218e --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.Designer.cs @@ -0,0 +1,658 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Drive; +using DysonNetwork.Shared.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Drive.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20260111152243_CleanCloudFile")] + partial class CleanCloudFile + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Drive.Billing.QuotaRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Quota") + .HasColumnType("bigint") + .HasColumnName("quota"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_quota_records"); + + b.ToTable("quota_records", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.Model.PersistentTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("description"); + + b.Property("ErrorMessage") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("error_message"); + + b.Property("EstimatedDurationSeconds") + .HasColumnType("bigint") + .HasColumnName("estimated_duration_seconds"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("LastActivity") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_activity"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property>("Parameters") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("parameters"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Progress") + .HasColumnType("double precision") + .HasColumnName("progress"); + + b.Property>("Results") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("results"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("TaskId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("task_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_tasks"); + + b.ToTable("tasks", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.FilePool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BillingConfig") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("billing_config"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("description"); + + b.Property("IsHidden") + .HasColumnType("boolean") + .HasColumnName("is_hidden"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("PolicyConfig") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("policy_config"); + + b.Property("StorageConfig") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("storage_config"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_pools"); + + b.ToTable("pools", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BundleId") + .HasColumnType("uuid") + .HasColumnName("bundle_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IsMarkedRecycle") + .HasColumnType("boolean") + .HasColumnName("is_marked_recycle"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("ObjectId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("object_id"); + + b.Property("PoolId") + .HasColumnType("uuid") + .HasColumnName("pool_id"); + + b.PrimitiveCollection("SensitiveMarks") + .HasColumnType("jsonb") + .HasColumnName("sensitive_marks"); + + b.Property("StorageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("storage_id"); + + b.Property("StorageUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("storage_url"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("uploaded_at"); + + b.Property>("UserMeta") + .HasColumnType("jsonb") + .HasColumnName("user_meta"); + + b.HasKey("Id") + .HasName("pk_files"); + + b.HasIndex("BundleId") + .HasDatabaseName("ix_files_bundle_id"); + + b.HasIndex("ObjectId") + .HasDatabaseName("ix_files_object_id"); + + b.HasIndex("PoolId") + .HasDatabaseName("ix_files_pool_id"); + + b.ToTable("files", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FileId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("file_id"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("path"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_file_indexes"); + + b.HasIndex("FileId") + .HasDatabaseName("ix_file_indexes_file_id"); + + b.HasIndex("Path", "AccountId") + .HasDatabaseName("ix_file_indexes_path_account_id"); + + b.ToTable("file_indexes", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("Passcode") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("passcode"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_bundles"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_bundles_slug"); + + b.ToTable("bundles", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileObject", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("HasCompression") + .HasColumnType("boolean") + .HasColumnName("has_compression"); + + b.Property("HasThumbnail") + .HasColumnType("boolean") + .HasColumnName("has_thumbnail"); + + b.Property("Hash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("hash"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("mime_type"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_file_objects"); + + b.ToTable("file_objects", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFilePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FileId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("file_id"); + + b.Property("Permission") + .HasColumnType("integer") + .HasColumnName("permission"); + + b.Property("SubjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("subject_id"); + + b.Property("SubjectType") + .HasColumnType("integer") + .HasColumnName("subject_type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_file_permissions"); + + b.ToTable("file_permissions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileReplica", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IsPrimary") + .HasColumnType("boolean") + .HasColumnName("is_primary"); + + b.Property("ObjectId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("object_id"); + + b.Property("PoolId") + .HasColumnType("uuid") + .HasColumnName("pool_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("StorageId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("storage_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_file_replicas"); + + b.HasIndex("ObjectId") + .HasDatabaseName("ix_file_replicas_object_id"); + + b.HasIndex("PoolId") + .HasDatabaseName("ix_file_replicas_pool_id"); + + b.ToTable("file_replicas", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnFileBundle", "Bundle") + .WithMany("Files") + .HasForeignKey("BundleId") + .HasConstraintName("fk_files_bundles_bundle_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnFileObject", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .HasConstraintName("fk_files_file_objects_object_id"); + + b.HasOne("DysonNetwork.Shared.Models.FilePool", "Pool") + .WithMany() + .HasForeignKey("PoolId") + .HasConstraintName("fk_files_pools_pool_id"); + + b.Navigation("Bundle"); + + b.Navigation("Object"); + + b.Navigation("Pool"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File") + .WithMany("FileIndexes") + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_file_indexes_files_file_id"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileReplica", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnFileObject", "Object") + .WithMany("FileReplicas") + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_file_replicas_file_objects_object_id"); + + b.HasOne("DysonNetwork.Shared.Models.FilePool", "Pool") + .WithMany() + .HasForeignKey("PoolId") + .HasConstraintName("fk_file_replicas_pools_pool_id"); + + b.Navigation("Object"); + + b.Navigation("Pool"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b => + { + b.Navigation("FileIndexes"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileObject", b => + { + b.Navigation("FileReplicas"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.cs b/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.cs new file mode 100644 index 00000000..e95abb11 --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20260111152243_CleanCloudFile.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Drive.Migrations +{ + /// + public partial class CleanCloudFile : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "file_meta", + table: "files"); + + migrationBuilder.DropColumn( + name: "has_compression", + table: "files"); + + migrationBuilder.DropColumn( + name: "has_thumbnail", + table: "files"); + + migrationBuilder.DropColumn( + name: "hash", + table: "files"); + + migrationBuilder.DropColumn( + name: "is_encrypted", + table: "files"); + + migrationBuilder.DropColumn( + name: "mime_type", + table: "files"); + + migrationBuilder.DropColumn( + name: "size", + table: "files"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn>( + name: "file_meta", + table: "files", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn( + name: "has_compression", + table: "files", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "has_thumbnail", + table: "files", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "hash", + table: "files", + type: "character varying(256)", + maxLength: 256, + nullable: true); + + migrationBuilder.AddColumn( + name: "is_encrypted", + table: "files", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "mime_type", + table: "files", + type: "character varying(256)", + maxLength: 256, + nullable: true); + + migrationBuilder.AddColumn( + name: "size", + table: "files", + type: "bigint", + nullable: false, + defaultValue: 0L); + } + } +} diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs index 917c557e..2cc14389 100644 --- a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs @@ -261,36 +261,10 @@ namespace DysonNetwork.Drive.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("expired_at"); - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("HasThumbnail") - .HasColumnType("boolean") - .HasColumnName("has_thumbnail"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsEncrypted") - .HasColumnType("boolean") - .HasColumnName("is_encrypted"); - b.Property("IsMarkedRecycle") .HasColumnType("boolean") .HasColumnName("is_marked_recycle"); - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - b.Property("Name") .IsRequired() .HasMaxLength(1024) @@ -310,10 +284,6 @@ namespace DysonNetwork.Drive.Migrations .HasColumnType("jsonb") .HasColumnName("sensitive_marks"); - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - b.Property("StorageId") .HasMaxLength(32) .HasColumnType("character varying(32)") diff --git a/DysonNetwork.Drive/Startup/BroadcastEventHandler.cs b/DysonNetwork.Drive/Startup/BroadcastEventHandler.cs index b497757d..43006694 100644 --- a/DysonNetwork.Drive/Startup/BroadcastEventHandler.cs +++ b/DysonNetwork.Drive/Startup/BroadcastEventHandler.cs @@ -190,12 +190,8 @@ public class BroadcastEventHandler( { uploadTask = null; } - - if (fileToUpdate.IsEncrypted) - { - uploads.Add((processingFilePath, string.Empty, contentType, false)); - } - else if (!pool.PolicyConfig.NoOptimization) + + if (!pool.PolicyConfig.NoOptimization) { var fileExtension = Path.GetExtension(processingFilePath); switch (contentType.Split('/')[0]) diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs index be665559..0741d827 100644 --- a/DysonNetwork.Drive/Storage/FileController.cs +++ b/DysonNetwork.Drive/Storage/FileController.cs @@ -67,10 +67,6 @@ public class FileController( var tempFilePath = Path.Combine(Path.GetTempPath(), file.Id); if (System.IO.File.Exists(tempFilePath)) { - if (file.IsEncrypted) - return Task.FromResult(StatusCode(StatusCodes.Status403Forbidden, - "Encrypted files cannot be accessed before they are processed and stored.")); - return Task.FromResult(PhysicalFile(tempFilePath, file.MimeType ?? "application/octet-stream", file.Name, enableRangeProcessing: true)); } diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index f51d86f2..5caf28a0 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -110,7 +110,9 @@ public class FileService( var (managedTempPath, fileSize, finalContentType) = await PrepareFileAsync(fileId, filePath, fileName, contentType); - var file = CreateFileObject(fileId, fileName, finalContentType, fileSize, finalExpiredAt, bundle, accountId); + var fileObject = CreateFileObject(fileId, accountId, finalContentType, fileSize); + + var file = CreateCloudFile(fileId, fileName, fileObject, finalExpiredAt, bundle, accountId); if (!pool.PolicyConfig.NoMetadata) { @@ -118,11 +120,11 @@ public class FileService( } var (processingPath, isTempFile) = - await ProcessEncryptionAsync(fileId, managedTempPath, encryptPassword, pool, file); + await ProcessEncryptionAsync(fileId, managedTempPath, encryptPassword, pool, fileObject); - file.Hash = await HashFileAsync(processingPath); + fileObject.Hash = await HashFileAsync(processingPath); - await SaveFileToDatabaseAsync(file); + await SaveFileToDatabaseAsync(file, fileObject); await PublishFileUploadedEventAsync(file, pool, processingPath, isTempFile); @@ -182,11 +184,26 @@ public class FileService( return (managedTempPath, fileSize, finalContentType); } - private SnCloudFile CreateFileObject( + private SnFileObject CreateFileObject( + string fileId, + Guid accountId, + string contentType, + long fileSize + ) + { + return new SnFileObject + { + Id = fileId, + AccountId = accountId, + MimeType = contentType, + Size = fileSize, + }; + } + + private SnCloudFile CreateCloudFile( string fileId, string fileName, - string contentType, - long fileSize, + SnFileObject fileObject, Instant? expiredAt, SnFileBundle? bundle, Guid accountId @@ -196,8 +213,8 @@ public class FileService( { Id = fileId, Name = fileName, - MimeType = contentType, - Size = fileSize, + Object = fileObject, + ObjectId = fileId, ExpiredAt = expiredAt, BundleId = bundle?.Id, AccountId = accountId, @@ -209,7 +226,7 @@ public class FileService( string managedTempPath, string? encryptPassword, FilePool pool, - SnCloudFile file + SnFileObject fileObject ) { if (string.IsNullOrWhiteSpace(encryptPassword)) @@ -223,27 +240,14 @@ public class FileService( File.Delete(managedTempPath); - file.IsEncrypted = true; - file.MimeType = "application/octet-stream"; - file.Size = new FileInfo(encryptedPath).Length; + fileObject.MimeType = "application/octet-stream"; + fileObject.Size = new FileInfo(encryptedPath).Length; return Task.FromResult((encryptedPath, true)); } - private async Task SaveFileToDatabaseAsync(SnCloudFile file) + private async Task SaveFileToDatabaseAsync(SnCloudFile file, SnFileObject fileObject) { - var fileObject = new SnFileObject - { - Id = file.Id, - AccountId = file.AccountId, - Size = file.Size, - Meta = file.FileMeta, - MimeType = file.MimeType, - Hash = file.Hash, - HasCompression = file.HasCompression, - HasThumbnail = file.HasThumbnail - }; - var replica = new SnFileReplica { Id = Guid.NewGuid(), @@ -254,9 +258,19 @@ public class FileService( IsPrimary = true }; + var permission = new SnFilePermission + { + Id = Guid.NewGuid(), + FileId = file.Id, + SubjectType = SnFilePermissionType.Someone, + SubjectId = file.AccountId.ToString(), + Permission = SnFilePermissionLevel.Write + }; + db.Files.Add(file); db.FileObjects.Add(fileObject); db.FileReplicas.Add(replica); + db.FilePermissions.Add(permission); await db.SaveChangesAsync(); file.ObjectId = file.Id; @@ -327,11 +341,11 @@ public class FileService( if (orientation is 6 or 8) (width, height) = (height, width); meta["exif"] = exif; meta["ratio"] = height != 0 ? (double)width / height : 0; - file.FileMeta = meta; + file.Object!.Meta = meta; } catch (Exception ex) { - file.FileMeta = new Dictionary(); + file.Object!.Meta = new Dictionary(); logger.LogError(ex, "Failed to analyze image file {FileId}", file.Id); } @@ -342,7 +356,7 @@ public class FileService( try { var mediaInfo = await FFProbe.AnalyseAsync(filePath); - file.FileMeta = new Dictionary + file.Object!.Meta = new Dictionary { ["width"] = mediaInfo.PrimaryVideoStream?.Width, ["height"] = mediaInfo.PrimaryVideoStream?.Height, @@ -378,8 +392,8 @@ public class FileService( .ToList(), }; if (mediaInfo.PrimaryVideoStream is not null) - file.FileMeta["ratio"] = (double)mediaInfo.PrimaryVideoStream.Width / - mediaInfo.PrimaryVideoStream.Height; + file.Object!.Meta["ratio"] = (double)mediaInfo.PrimaryVideoStream.Width / + mediaInfo.PrimaryVideoStream.Height; } catch (Exception ex) { @@ -856,7 +870,6 @@ file class UpdatableCloudFile(SnCloudFile file) return setter => setter .SetProperty(f => f.Name, Name) .SetProperty(f => f.Description, Description) - .SetProperty(f => f.FileMeta, FileMeta) .SetProperty(f => f.UserMeta, userMeta) .SetProperty(f => f.IsMarkedRecycle, IsMarkedRecycle); } diff --git a/DysonNetwork.Shared/Models/CloudFile.cs b/DysonNetwork.Shared/Models/CloudFile.cs index 305d6e31..625cba78 100644 --- a/DysonNetwork.Shared/Models/CloudFile.cs +++ b/DysonNetwork.Shared/Models/CloudFile.cs @@ -13,23 +13,25 @@ public class SnCloudFile : ModelBase, ICloudFile, IIdentifiedResource [MaxLength(32)] public string Id { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty); - [MaxLength(1024)] public string Name { get; set; } = string.Empty; + [MaxLength(1024)] public string Name { get; set; } [MaxLength(4096)] public string? Description { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; } [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } [Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = []; - [MaxLength(256)] public string? MimeType { get; set; } - [MaxLength(256)] public string? Hash { get; set; } + + [NotMapped] + [Column(TypeName = "jsonb")] + public Dictionary FileMeta => Object!.Meta ?? []; + [NotMapped] [MaxLength(256)] public string? MimeType => Object!.MimeType; + [NotMapped] [MaxLength(256)] public string? Hash => Object!.Hash; public Instant? ExpiredAt { get; set; } - public long Size { get; set; } + [NotMapped] public long Size => Object!.Size; public Instant? UploadedAt { get; set; } - public bool HasCompression { get; set; } = false; - public bool HasThumbnail { get; set; } = false; - public bool IsEncrypted { get; set; } = false; - + [NotMapped] public bool HasCompression => Object!.HasCompression; + [NotMapped] public bool HasThumbnail => Object!.HasThumbnail; + [MaxLength(32)] public string? ObjectId { get; set; } public SnFileObject? Object { get; set; } - + public FilePool? Pool { get; set; } public Guid? PoolId { get; set; } [JsonIgnore] public SnFileBundle? Bundle { get; set; } @@ -60,7 +62,7 @@ public class SnCloudFile : ModelBase, ICloudFile, IIdentifiedResource [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? FastUploadLink { get; set; } - public Guid AccountId { get; set; } + public Guid AccountId { get; set; } public SnCloudFileReferenceObject ToReferenceObject() { @@ -109,4 +111,4 @@ public class SnCloudFile : ModelBase, ICloudFile, IIdentifiedResource return proto; } -} +} \ No newline at end of file