Compare commits
	
		
			5 Commits
		
	
	
		
			d555fcaf17
			...
			2e52a13c30
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2e52a13c30 | |||
| 1e8e2e9ea7 | |||
| 9e8363c004 | |||
| 56c40ee001 | |||
| e3dfccfee3 | 
							
								
								
									
										324
									
								
								DysonNetwork.Develop/Migrations/20250819163227_AddBotAccount.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								DysonNetwork.Develop/Migrations/20250819163227_AddBotAccount.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using DysonNetwork.Develop; | ||||
| using DysonNetwork.Develop.Identity; | ||||
| using DysonNetwork.Shared.Data; | ||||
| 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.Develop.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     [Migration("20250819163227_AddBotAccount")] | ||||
|     partial class AddBotAccount | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.7") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.BotAccount", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<bool>("IsActive") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_active"); | ||||
|  | ||||
|                     b.Property<Guid>("ProjectId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("project_id"); | ||||
|  | ||||
|                     b.Property<string>("Slug") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("slug"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_bot_accounts"); | ||||
|  | ||||
|                     b.HasIndex("ProjectId") | ||||
|                         .HasDatabaseName("ix_bot_accounts_project_id"); | ||||
|  | ||||
|                     b.ToTable("bot_accounts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomApp", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<CloudFileReferenceObject>("Background") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("background"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<CustomAppLinks>("Links") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("links"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<CustomAppOauthConfig>("OauthConfig") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("oauth_config"); | ||||
|  | ||||
|                     b.Property<CloudFileReferenceObject>("Picture") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("picture"); | ||||
|  | ||||
|                     b.Property<Guid>("ProjectId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("project_id"); | ||||
|  | ||||
|                     b.Property<string>("Slug") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("slug"); | ||||
|  | ||||
|                     b.Property<int>("Status") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("status"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<VerificationMark>("Verification") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("verification"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_custom_apps"); | ||||
|  | ||||
|                     b.HasIndex("ProjectId") | ||||
|                         .HasDatabaseName("ix_custom_apps_project_id"); | ||||
|  | ||||
|                     b.ToTable("custom_apps", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomAppSecret", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AppId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("app_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<Instant?>("ExpiredAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expired_at"); | ||||
|  | ||||
|                     b.Property<bool>("IsOidc") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_oidc"); | ||||
|  | ||||
|                     b.Property<string>("Secret") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("secret"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_custom_app_secrets"); | ||||
|  | ||||
|                     b.HasIndex("AppId") | ||||
|                         .HasDatabaseName("ix_custom_app_secrets_app_id"); | ||||
|  | ||||
|                     b.ToTable("custom_app_secrets", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.Developer", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("PublisherId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("publisher_id"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_developers"); | ||||
|  | ||||
|                     b.ToTable("developers", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Project.DevProject", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<Guid>("DeveloperId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("developer_id"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<string>("Slug") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("slug"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_dev_projects"); | ||||
|  | ||||
|                     b.HasIndex("DeveloperId") | ||||
|                         .HasDatabaseName("ix_dev_projects_developer_id"); | ||||
|  | ||||
|                     b.ToTable("dev_projects", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.BotAccount", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Project.DevProject", "Project") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("ProjectId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_bot_accounts_dev_projects_project_id"); | ||||
|  | ||||
|                     b.Navigation("Project"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomApp", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Project.DevProject", "Project") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("ProjectId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_custom_apps_dev_projects_project_id"); | ||||
|  | ||||
|                     b.Navigation("Project"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomAppSecret", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Identity.CustomApp", "App") | ||||
|                         .WithMany("Secrets") | ||||
|                         .HasForeignKey("AppId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); | ||||
|  | ||||
|                     b.Navigation("App"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Project.DevProject", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Identity.Developer", "Developer") | ||||
|                         .WithMany("Projects") | ||||
|                         .HasForeignKey("DeveloperId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_dev_projects_developers_developer_id"); | ||||
|  | ||||
|                     b.Navigation("Developer"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomApp", b => | ||||
|                 { | ||||
|                     b.Navigation("Secrets"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.Developer", b => | ||||
|                 { | ||||
|                     b.Navigation("Projects"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,51 @@ | ||||
| using System; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Develop.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddBotAccount : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "bot_accounts", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     is_active = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     project_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_bot_accounts", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_bot_accounts_dev_projects_project_id", | ||||
|                         column: x => x.project_id, | ||||
|                         principalTable: "dev_projects", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_bot_accounts_project_id", | ||||
|                 table: "bot_accounts", | ||||
|                 column: "project_id"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "bot_accounts"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -25,6 +25,48 @@ namespace DysonNetwork.Develop.Migrations | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.BotAccount", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<bool>("IsActive") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_active"); | ||||
|  | ||||
|                     b.Property<Guid>("ProjectId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("project_id"); | ||||
|  | ||||
|                     b.Property<string>("Slug") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("slug"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_bot_accounts"); | ||||
|  | ||||
|                     b.HasIndex("ProjectId") | ||||
|                         .HasDatabaseName("ix_bot_accounts_project_id"); | ||||
|  | ||||
|                     b.ToTable("bot_accounts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomApp", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
| @@ -216,6 +258,18 @@ namespace DysonNetwork.Develop.Migrations | ||||
|                     b.ToTable("dev_projects", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.BotAccount", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Project.DevProject", "Project") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("ProjectId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_bot_accounts_dev_projects_project_id"); | ||||
|  | ||||
|                     b.Navigation("Project"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Develop.Identity.CustomApp", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Develop.Project.DevProject", "Project") | ||||
|   | ||||
| @@ -3,6 +3,7 @@ using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Develop.Startup; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
| @@ -10,6 +11,7 @@ var builder = WebApplication.CreateBuilder(args); | ||||
| builder.ConfigureAppKestrel(builder.Configuration); | ||||
|  | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| builder.Services.AddAppSwagger(); | ||||
|   | ||||
							
								
								
									
										404
									
								
								DysonNetwork.Drive/Migrations/20250819164302_RemoveUploadedTo.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								DysonNetwork.Drive/Migrations/20250819164302_RemoveUploadedTo.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,404 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Drive; | ||||
| using DysonNetwork.Drive.Storage; | ||||
| using DysonNetwork.Shared.Data; | ||||
| 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("20250819164302_RemoveUploadedTo")] | ||||
|     partial class RemoveUploadedTo | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.7") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Billing.QuotaRecord", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<Instant?>("ExpiredAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expired_at"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<long>("Quota") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("quota"); | ||||
|  | ||||
|                     b.Property<Instant>("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.CloudFile", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasMaxLength(32) | ||||
|                         .HasColumnType("character varying(32)") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<Guid?>("BundleId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("bundle_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<Instant?>("ExpiredAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expired_at"); | ||||
|  | ||||
|                     b.Property<Dictionary<string, object>>("FileMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("file_meta"); | ||||
|  | ||||
|                     b.Property<bool>("HasCompression") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("has_compression"); | ||||
|  | ||||
|                     b.Property<bool>("HasThumbnail") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("has_thumbnail"); | ||||
|  | ||||
|                     b.Property<string>("Hash") | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("hash"); | ||||
|  | ||||
|                     b.Property<bool>("IsEncrypted") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_encrypted"); | ||||
|  | ||||
|                     b.Property<bool>("IsMarkedRecycle") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_marked_recycle"); | ||||
|  | ||||
|                     b.Property<string>("MimeType") | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("mime_type"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<Guid?>("PoolId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("pool_id"); | ||||
|  | ||||
|                     b.Property<List<ContentSensitiveMark>>("SensitiveMarks") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("sensitive_marks"); | ||||
|  | ||||
|                     b.Property<long>("Size") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("size"); | ||||
|  | ||||
|                     b.Property<string>("StorageId") | ||||
|                         .HasMaxLength(32) | ||||
|                         .HasColumnType("character varying(32)") | ||||
|                         .HasColumnName("storage_id"); | ||||
|  | ||||
|                     b.Property<string>("StorageUrl") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("storage_url"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("UploadedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("uploaded_at"); | ||||
|  | ||||
|                     b.Property<Dictionary<string, object>>("UserMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("user_meta"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_files"); | ||||
|  | ||||
|                     b.HasIndex("BundleId") | ||||
|                         .HasDatabaseName("ix_files_bundle_id"); | ||||
|  | ||||
|                     b.HasIndex("PoolId") | ||||
|                         .HasDatabaseName("ix_files_pool_id"); | ||||
|  | ||||
|                     b.ToTable("files", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("ExpiredAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expired_at"); | ||||
|  | ||||
|                     b.Property<string>("FileId") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(32) | ||||
|                         .HasColumnType("character varying(32)") | ||||
|                         .HasColumnName("file_id"); | ||||
|  | ||||
|                     b.Property<string>("ResourceId") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("resource_id"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<string>("Usage") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("usage"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_file_references"); | ||||
|  | ||||
|                     b.HasIndex("FileId") | ||||
|                         .HasDatabaseName("ix_file_references_file_id"); | ||||
|  | ||||
|                     b.ToTable("file_references", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.FileBundle", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<Instant?>("ExpiredAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expired_at"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<string>("Passcode") | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("passcode"); | ||||
|  | ||||
|                     b.Property<string>("Slug") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("slug"); | ||||
|  | ||||
|                     b.Property<Instant>("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.Drive.Storage.FilePool", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid?>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<BillingConfig>("BillingConfig") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("billing_config"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Description") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("description"); | ||||
|  | ||||
|                     b.Property<bool>("IsHidden") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_hidden"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<PolicyConfig>("PolicyConfig") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("policy_config"); | ||||
|  | ||||
|                     b.Property<RemoteStorageConfig>("StorageConfig") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("storage_config"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_pools"); | ||||
|  | ||||
|                     b.ToTable("pools", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.FileBundle", "Bundle") | ||||
|                         .WithMany("Files") | ||||
|                         .HasForeignKey("BundleId") | ||||
|                         .HasConstraintName("fk_files_bundles_bundle_id"); | ||||
|  | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.FilePool", "Pool") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("PoolId") | ||||
|                         .HasConstraintName("fk_files_pools_pool_id"); | ||||
|  | ||||
|                     b.Navigation("Bundle"); | ||||
|  | ||||
|                     b.Navigation("Pool"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") | ||||
|                         .WithMany("References") | ||||
|                         .HasForeignKey("FileId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_file_references_files_file_id"); | ||||
|  | ||||
|                     b.Navigation("File"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => | ||||
|                 { | ||||
|                     b.Navigation("References"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.FileBundle", b => | ||||
|                 { | ||||
|                     b.Navigation("Files"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Drive.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class RemoveUploadedTo : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "uploaded_to", | ||||
|                 table: "files"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<string>( | ||||
|                 name: "uploaded_to", | ||||
|                 table: "files", | ||||
|                 type: "character varying(128)", | ||||
|                 maxLength: 128, | ||||
|                 nullable: true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -172,11 +172,6 @@ namespace DysonNetwork.Drive.Migrations | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("uploaded_at"); | ||||
|  | ||||
|                     b.Property<string>("UploadedTo") | ||||
|                         .HasMaxLength(128) | ||||
|                         .HasColumnType("character varying(128)") | ||||
|                         .HasColumnName("uploaded_to"); | ||||
|  | ||||
|                     b.Property<Dictionary<string, object>>("UserMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("user_meta"); | ||||
| @@ -382,7 +377,7 @@ namespace DysonNetwork.Drive.Migrations | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") | ||||
|                         .WithMany() | ||||
|                         .WithMany("References") | ||||
|                         .HasForeignKey("FileId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
| @@ -391,6 +386,11 @@ namespace DysonNetwork.Drive.Migrations | ||||
|                     b.Navigation("File"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => | ||||
|                 { | ||||
|                     b.Navigation("References"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Drive.Storage.FileBundle", b => | ||||
|                 { | ||||
|                     b.Navigation("Files"); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using tusdotnet.Stores; | ||||
|  | ||||
| @@ -15,6 +16,7 @@ builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxV | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
|   | ||||
							
								
								
									
										56
									
								
								DysonNetwork.Drive/Startup/BroadcastEventHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								DysonNetwork.Drive/Startup/BroadcastEventHandler.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Drive.Storage; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NATS.Client.Core; | ||||
|  | ||||
| namespace DysonNetwork.Drive.Startup; | ||||
|  | ||||
| public class BroadcastEventHandler( | ||||
|     INatsConnection nats, | ||||
|     ILogger<BroadcastEventHandler> logger, | ||||
|     IServiceProvider serviceProvider | ||||
| ) : BackgroundService | ||||
| { | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await foreach (var msg in nats.SubscribeAsync<byte[]>("accounts.deleted", cancellationToken: stoppingToken)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data); | ||||
|                 if (evt == null) continue; | ||||
|  | ||||
|                 logger.LogInformation("Account deleted: {AccountId}", evt.AccountId); | ||||
|  | ||||
|                 using var scope = serviceProvider.CreateScope(); | ||||
|                 var fs = scope.ServiceProvider.GetRequiredService<FileService>(); | ||||
|                 var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); | ||||
|  | ||||
|                 await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken: stoppingToken); | ||||
|                 try | ||||
|                 { | ||||
|                     var files = await db.Files | ||||
|                         .Where(p => p.AccountId == evt.AccountId) | ||||
|                         .ToListAsync(cancellationToken: stoppingToken); | ||||
|  | ||||
|                     await fs.DeleteFileDataBatchAsync(files); | ||||
|                     await db.Files | ||||
|                         .Where(p => p.AccountId == evt.AccountId) | ||||
|                         .ExecuteDeleteAsync(cancellationToken: stoppingToken); | ||||
|  | ||||
|                     await transaction.CommitAsync(cancellationToken: stoppingToken); | ||||
|                 } | ||||
|                 catch (Exception) | ||||
|                 { | ||||
|                     await transaction.RollbackAsync(cancellationToken: stoppingToken); | ||||
|                     throw; | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logger.LogError(ex, "Error processing AccountDeleted"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -141,6 +141,8 @@ public static class ServiceCollectionExtensions | ||||
|         services.AddScoped<Billing.UsageService>(); | ||||
|         services.AddScoped<Billing.QuotaService>(); | ||||
|  | ||||
|         services.AddHostedService<BroadcastEventHandler>(); | ||||
|          | ||||
|         return services; | ||||
|     } | ||||
| } | ||||
| @@ -33,10 +33,6 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource | ||||
|     [JsonIgnore] public FileBundle? Bundle { get; set; } | ||||
|     public Guid? BundleId { get; set; } | ||||
|  | ||||
|     [Obsolete("Deprecated, use PoolId instead. For database migration only.")] | ||||
|     [MaxLength(128)] | ||||
|     public string? UploadedTo { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// The field is set to true if the recycling job plans to delete the file. | ||||
|     /// Due to the unstable of the recycling job, this doesn't really delete the file until a human verifies it. | ||||
| @@ -61,6 +57,8 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource | ||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] | ||||
|     public string? FastUploadLink { get; set; } | ||||
|      | ||||
|     public ICollection<CloudFileReference> References { get; set; } = new List<CloudFileReference>(); | ||||
|  | ||||
|     public Guid AccountId { get; set; } | ||||
|  | ||||
|     public CloudFileReferenceObject ToReferenceObject() | ||||
|   | ||||
| @@ -190,10 +190,8 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach | ||||
|             .Where(r => r.ResourceId == resourceId && r.Usage == usage) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!references.Any()) | ||||
|         { | ||||
|         if (references.Count == 0) | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         var fileIds = references.Select(r => r.FileId).Distinct().ToList(); | ||||
|  | ||||
| @@ -208,6 +206,28 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach | ||||
|         return deletedCount; | ||||
|     } | ||||
|      | ||||
|     public async Task<int> DeleteResourceReferencesBatchAsync(IEnumerable<string> resourceIds, string? usage = null) | ||||
|     { | ||||
|         var references = await db.FileReferences | ||||
|             .Where(r => resourceIds.Contains(r.ResourceId)) | ||||
|             .If(usage != null, q => q.Where(q => q.Usage == usage)) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (references.Count == 0) | ||||
|             return 0; | ||||
|  | ||||
|         var fileIds = references.Select(r => r.FileId).Distinct().ToList(); | ||||
|  | ||||
|         db.FileReferences.RemoveRange(references); | ||||
|         var deletedCount = await db.SaveChangesAsync(); | ||||
|  | ||||
|         // Purge caches | ||||
|         var tasks = fileIds.Select(fileService._PurgeCacheAsync).ToList(); | ||||
|         await Task.WhenAll(tasks); | ||||
|  | ||||
|         return deletedCount; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Deletes a specific file reference | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -85,7 +85,7 @@ namespace DysonNetwork.Drive.Storage | ||||
|         public override async Task<DeleteResourceReferencesResponse> DeleteResourceReferences( | ||||
|             DeleteResourceReferencesRequest request, ServerCallContext context) | ||||
|         { | ||||
|             var deletedCount = 0; | ||||
|             int deletedCount; | ||||
|             if (request.Usage is null) | ||||
|                 deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId); | ||||
|             else | ||||
| @@ -94,6 +94,18 @@ namespace DysonNetwork.Drive.Storage | ||||
|             return new DeleteResourceReferencesResponse { DeletedCount = deletedCount }; | ||||
|         } | ||||
|          | ||||
|         public override async Task<DeleteResourceReferencesResponse> DeleteResourceReferencesBatch(DeleteResourceReferencesBatchRequest request, ServerCallContext context) | ||||
|         { | ||||
|             var resourceIds = request.ResourceIds.ToList(); | ||||
|             int deletedCount; | ||||
|             if (request.Usage is null) | ||||
|                 deletedCount = await fileReferenceService.DeleteResourceReferencesBatchAsync(resourceIds); | ||||
|             else | ||||
|                 deletedCount = | ||||
|                     await fileReferenceService.DeleteResourceReferencesBatchAsync(resourceIds, request.Usage!); | ||||
|             return new DeleteResourceReferencesResponse { DeletedCount = deletedCount }; | ||||
|         } | ||||
|  | ||||
|         public override async Task<DeleteReferenceResponse> DeleteReference(DeleteReferenceRequest request, | ||||
|             ServerCallContext context) | ||||
|         { | ||||
|   | ||||
| @@ -102,6 +102,7 @@ public class FileService( | ||||
|  | ||||
|     private static readonly string[] AnimatedImageTypes = | ||||
|         ["image/gif", "image/apng", "image/avif"]; | ||||
|  | ||||
|     private static readonly string[] AnimatedImageExtensions = | ||||
|         [".gif", ".apng", ".avif"]; | ||||
|  | ||||
| @@ -336,7 +337,8 @@ public class FileService( | ||||
|             if (!pool.PolicyConfig.NoOptimization) | ||||
|                 switch (contentType.Split('/')[0]) | ||||
|                 { | ||||
|                     case "image" when !AnimatedImageTypes.Contains(contentType) && !AnimatedImageExtensions.Contains(fileExtension): | ||||
|                     case "image" when !AnimatedImageTypes.Contains(contentType) && | ||||
|                                       !AnimatedImageExtensions.Contains(fileExtension): | ||||
|                         newMimeType = "image/webp"; | ||||
|                         using (var vipsImage = Image.NewFromFile(originalFilePath)) | ||||
|                         { | ||||
| @@ -643,7 +645,44 @@ public class FileService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<FileBundle?> GetBundleAsync(Guid id, Guid accountId) | ||||
|     /// <summary> | ||||
|     /// The most efficent way to delete file data (stored files) in batch. | ||||
|     /// But this DO NOT check the storage id, so use with caution! | ||||
|     /// </summary> | ||||
|     /// <param name="files">Files to delete</param> | ||||
|     /// <exception cref="InvalidOperationException">Something went wrong</exception> | ||||
|     public async Task DeleteFileDataBatchAsync(List<CloudFile> files) | ||||
|     { | ||||
|         files = files.Where(f => f.PoolId.HasValue).ToList(); | ||||
|  | ||||
|         foreach (var fileGroup in files.GroupBy(f => f.PoolId!.Value)) | ||||
|         { | ||||
|             // If any other file with the same storage ID is referenced, don't delete the actual file data | ||||
|             var dest = await GetRemoteStorageConfig(fileGroup.Key); | ||||
|             if (dest is null) | ||||
|                 throw new InvalidOperationException($"No remote storage configured for pool {fileGroup.Key}"); | ||||
|             var client = CreateMinioClient(dest); | ||||
|             if (client is null) | ||||
|                 throw new InvalidOperationException( | ||||
|                     $"Failed to configure client for remote destination '{fileGroup.Key}'" | ||||
|                 ); | ||||
|  | ||||
|             List<string> objectsToDelete = []; | ||||
|  | ||||
|             foreach (var file in fileGroup) | ||||
|             { | ||||
|                 objectsToDelete.Add(file.StorageId ?? file.Id); | ||||
|                 if(file.HasCompression) objectsToDelete.Add(file.StorageId ?? file.Id + ".compressed"); | ||||
|                 if(file.HasThumbnail) objectsToDelete.Add(file.StorageId ?? file.Id + ".thumbnail"); | ||||
|             } | ||||
|  | ||||
|             await client.RemoveObjectsAsync( | ||||
|                 new RemoveObjectsArgs().WithBucket(dest.Bucket).WithObjects(objectsToDelete) | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task<FileBundle?> GetBundleAsync(Guid id, Guid accountId) | ||||
|     { | ||||
|         var bundle = await db.Bundles | ||||
|             .Where(e => e.Id == id) | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System.Globalization; | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Pass.Auth; | ||||
| using DysonNetwork.Pass.Auth.OpenId; | ||||
| using DysonNetwork.Pass.Email; | ||||
| @@ -6,9 +7,11 @@ using DysonNetwork.Pass.Localization; | ||||
| using DysonNetwork.Pass.Permission; | ||||
| using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using EFCore.BulkExtensions; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Localization; | ||||
| using NATS.Client.Core; | ||||
| using NodaTime; | ||||
| using OtpNet; | ||||
| using AuthService = DysonNetwork.Pass.Auth.AuthService; | ||||
| @@ -23,7 +26,8 @@ public class AccountService( | ||||
|     PusherService.PusherServiceClient pusher, | ||||
|     IStringLocalizer<NotificationResource> localizer, | ||||
|     ICacheService cache, | ||||
|     ILogger<AccountService> logger | ||||
|     ILogger<AccountService> logger, | ||||
|     INatsConnection nats | ||||
| ) | ||||
| { | ||||
|     public static void SetCultureInfo(Account account) | ||||
| @@ -696,5 +700,11 @@ public class AccountService( | ||||
|  | ||||
|         db.Accounts.Remove(account); | ||||
|         await db.SaveChangesAsync(); | ||||
|  | ||||
|         await nats.PublishAsync(AccountDeletedEvent.Type, JsonSerializer.SerializeToUtf8Bytes(new AccountDeletedEvent | ||||
|         { | ||||
|             AccountId = account.Id, | ||||
|             DeletedAt = SystemClock.Instance.GetCurrentInstant() | ||||
|         })); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1825
									
								
								DysonNetwork.Pass/Migrations/20250819162856_AddBotAccount.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1825
									
								
								DysonNetwork.Pass/Migrations/20250819162856_AddBotAccount.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										29
									
								
								DysonNetwork.Pass/Migrations/20250819162856_AddBotAccount.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								DysonNetwork.Pass/Migrations/20250819162856_AddBotAccount.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| using System; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Pass.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddBotAccount : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<Guid>( | ||||
|                 name: "automated_id", | ||||
|                 table: "accounts", | ||||
|                 type: "uuid", | ||||
|                 nullable: true); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "automated_id", | ||||
|                 table: "accounts"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -98,6 +98,10 @@ namespace DysonNetwork.Pass.Migrations | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("activated_at"); | ||||
|  | ||||
|                     b.Property<Guid?>("AutomatedId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("automated_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ using DysonNetwork.Pass.Startup; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
| @@ -16,6 +17,7 @@ builder.Services.AddAppMetrics(); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
|   | ||||
| @@ -21,7 +21,6 @@ public static class GrpcClientHelper | ||||
|                 ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) | ||||
|                 : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) | ||||
|         ); | ||||
|         // TODO: Verify the ca in the future | ||||
|         handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; | ||||
|         var httpClient = new HttpClient(handler); | ||||
|         httpClient.DefaultRequestVersion = HttpVersion.Version20; | ||||
|   | ||||
| @@ -206,6 +206,11 @@ message DeleteResourceReferencesRequest { | ||||
|   optional string usage = 2; | ||||
| } | ||||
|  | ||||
| message DeleteResourceReferencesBatchRequest { | ||||
|   repeated string resource_ids = 1; | ||||
|   optional string usage = 2; | ||||
| } | ||||
|  | ||||
| message DeleteResourceReferencesResponse { | ||||
|   int32 deleted_count = 1; | ||||
| } | ||||
| @@ -278,6 +283,9 @@ service FileReferenceService { | ||||
|   // Deletes references for a specific resource and optional usage | ||||
|   rpc DeleteResourceReferences(DeleteResourceReferencesRequest) returns (DeleteResourceReferencesResponse); | ||||
|    | ||||
|   // Deletes references for multiple specific resources and optional usage | ||||
|   rpc DeleteResourceReferencesBatch(DeleteResourceReferencesBatchRequest) returns (DeleteResourceReferencesResponse); | ||||
|  | ||||
|   // Deletes a specific file reference | ||||
|   rpc DeleteReference(DeleteReferenceRequest) returns (DeleteReferenceResponse); | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,8 @@ namespace DysonNetwork.Shared.Stream; | ||||
|  | ||||
| public class AccountDeletedEvent | ||||
| { | ||||
|     public static string Type => "account.deleted"; | ||||
|      | ||||
|     public Guid AccountId { get; set; } = Guid.NewGuid(); | ||||
|     public Instant DeletedAt { get; set; } = SystemClock.Instance.GetCurrentInstant(); | ||||
| } | ||||
| @@ -2,6 +2,7 @@ using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using DysonNetwork.Sphere; | ||||
| using DysonNetwork.Sphere.PageData; | ||||
| using DysonNetwork.Sphere.Startup; | ||||
| @@ -18,6 +19,7 @@ builder.Services.AddAppMetrics(); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
|   | ||||
							
								
								
									
										67
									
								
								DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NATS.Client.Core; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Startup; | ||||
|  | ||||
| public class BroadcastEventHandler( | ||||
|     INatsConnection nats, | ||||
|     ILogger<BroadcastEventHandler> logger, | ||||
|     IServiceProvider serviceProvider | ||||
| ) : BackgroundService | ||||
| { | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await foreach (var msg in nats.SubscribeAsync<byte[]>("accounts.deleted", cancellationToken: stoppingToken)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data); | ||||
|                 if (evt == null) continue; | ||||
|  | ||||
|                 logger.LogInformation("Account deleted: {AccountId}", evt.AccountId); | ||||
|                  | ||||
|                 using var scope = serviceProvider.CreateScope(); | ||||
|                 var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); | ||||
|  | ||||
|                 await db.ChatMembers | ||||
|                     .Where(m => m.AccountId == evt.AccountId) | ||||
|                     .ExecuteDeleteAsync(cancellationToken: stoppingToken); | ||||
|                  | ||||
|                 await db.RealmMembers | ||||
|                     .Where(m => m.AccountId == evt.AccountId) | ||||
|                     .ExecuteDeleteAsync(cancellationToken: stoppingToken); | ||||
|  | ||||
|                 await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken: stoppingToken); | ||||
|                 try | ||||
|                 { | ||||
|                     var publishers = await db.Publishers | ||||
|                         .Where(p => p.Members.All(m => m.AccountId == evt.AccountId)) | ||||
|                         .ToListAsync(cancellationToken: stoppingToken); | ||||
|  | ||||
|                     foreach (var publisher in publishers) | ||||
|                         await db.Posts | ||||
|                             .Where(p => p.PublisherId == publisher.Id) | ||||
|                             .ExecuteDeleteAsync(cancellationToken: stoppingToken); | ||||
|                      | ||||
|                     var publisherIds = publishers.Select(p => p.Id).ToList(); | ||||
|                     await db.Publishers | ||||
|                         .Where(p => publisherIds.Contains(p.Id)) | ||||
|                         .ExecuteDeleteAsync(cancellationToken: stoppingToken); | ||||
|  | ||||
|                     await transaction.CommitAsync(cancellationToken: stoppingToken); | ||||
|                 } | ||||
|                 catch (Exception) | ||||
|                 { | ||||
|                     await transaction.RollbackAsync(cancellationToken: stoppingToken); | ||||
|                     throw; | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logger.LogError(ex, "Error processing AccountDeleted"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -73,6 +73,8 @@ public static class ServiceCollectionExtensions | ||||
|             options.SupportedUICultures = supportedCultures; | ||||
|         }); | ||||
|  | ||||
|         services.AddHostedService<BroadcastEventHandler>(); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -71,6 +71,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aa524c330cf4033930e4a8661c62bc331a00_003F24_003Fdb8cbeea_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F9f_003Fc5bde8be_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIMessage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003Ff1_003F9fbeae46_003FIMessage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINatsClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F30191878badd4d5da7e204504ae1f7c075400_003F45_003F90c0c4d0_003FINatsClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb057cac061449bbbf3252d172d0302a2ce00_003Fb4_003Fd072017c_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user