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