♻️ I have no idea what I have done
This commit is contained in:
		| @@ -11,7 +11,11 @@ | ||||
|         <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4" /> | ||||
|         <PackageReference Include="FFMpegCore" Version="5.2.0" /> | ||||
|         <PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" /> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" /> | ||||
|         <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7"> | ||||
|           <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|           <PrivateAssets>all</PrivateAssets> | ||||
|         </PackageReference> | ||||
|         <PackageReference Include="MimeTypes" Version="2.5.2"> | ||||
|           <PrivateAssets>all</PrivateAssets> | ||||
|           <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| @@ -20,30 +24,30 @@ | ||||
|         <PackageReference Include="NetVips" Version="3.1.0" /> | ||||
|         <PackageReference Include="NetVips.Native.linux-x64" Version="8.17.1" /> | ||||
|         <PackageReference Include="NetVips.Native.osx-arm64" Version="8.17.1" /> | ||||
|         <PackageReference Include="NodaTime" Version="3.2.2"/> | ||||
|         <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0"/> | ||||
|         <PackageReference Include="NodaTime" Version="3.2.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0"/> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0"/> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4"/> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4"/> | ||||
|         <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0"/> | ||||
|         <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0"/> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0"/> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0"/> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0"/> | ||||
|         <PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1"/> | ||||
|         <PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1"/> | ||||
|         <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.1"/> | ||||
|         <PackageReference Include="prometheus-net.EntityFramework" Version="0.9.5"/> | ||||
|         <PackageReference Include="prometheus-net.SystemMetrics" Version="3.1.0"/> | ||||
|         <PackageReference Include="Quartz" Version="3.14.0"/> | ||||
|         <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/> | ||||
|         <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/> | ||||
|         <PackageReference Include="EFCore.BulkExtensions" Version="9.0.1"/> | ||||
|         <PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1"/> | ||||
|         <PackageReference Include="EFCore.NamingConventions" Version="9.0.0"/> | ||||
|         <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" /> | ||||
|         <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" /> | ||||
|         <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" /> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" /> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" /> | ||||
|         <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" /> | ||||
|         <PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" /> | ||||
|         <PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" /> | ||||
|         <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.1" /> | ||||
|         <PackageReference Include="prometheus-net.EntityFramework" Version="0.9.5" /> | ||||
|         <PackageReference Include="prometheus-net.SystemMetrics" Version="3.1.0" /> | ||||
|         <PackageReference Include="Quartz" Version="3.14.0" /> | ||||
|         <PackageReference Include="Quartz.AspNetCore" Version="3.14.0" /> | ||||
|         <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" /> | ||||
|         <PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" /> | ||||
|         <PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1" /> | ||||
|         <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" /> | ||||
|         <PackageReference Include="SkiaSharp" Version="3.119.0" /> | ||||
|         <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.0" /> | ||||
|         <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.0" /> | ||||
|   | ||||
							
								
								
									
										189
									
								
								DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Drive; | ||||
| using DysonNetwork.Drive.Storage; | ||||
| 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("20250713121317_InitialMigration")] | ||||
|     partial class InitialMigration | ||||
|     { | ||||
|         /// <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.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<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<Dictionary<string, object>>("FileMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("file_meta"); | ||||
|  | ||||
|                     b.Property<bool>("HasCompression") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("has_compression"); | ||||
|  | ||||
|                     b.Property<string>("Hash") | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("hash"); | ||||
|  | ||||
|                     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<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<string>("UploadedTo") | ||||
|                         .HasMaxLength(128) | ||||
|                         .HasColumnType("character varying(128)") | ||||
|                         .HasColumnName("uploaded_to"); | ||||
|  | ||||
|                     b.Property<Dictionary<string, object>>("UserMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("user_meta"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_files"); | ||||
|  | ||||
|                     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.CloudFileReference", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("FileId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_file_references_files_file_id"); | ||||
|  | ||||
|                     b.Navigation("File"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,89 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Drive.Storage; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Drive.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class InitialMigration : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AlterDatabase() | ||||
|                 .Annotation("Npgsql:PostgresExtension:postgis", ",,"); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "files", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false), | ||||
|                     name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     file_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     user_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     sensitive_marks = table.Column<List<ContentSensitiveMark>>(type: "jsonb", nullable: true), | ||||
|                     mime_type = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     size = table.Column<long>(type: "bigint", nullable: false), | ||||
|                     uploaded_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     uploaded_to = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true), | ||||
|                     has_compression = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     is_marked_recycle = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     storage_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), | ||||
|                     storage_url = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     account_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_files", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "file_references", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     file_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false), | ||||
|                     usage = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     resource_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     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_file_references", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_file_references_files_file_id", | ||||
|                         column: x => x.file_id, | ||||
|                         principalTable: "files", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_file_references_file_id", | ||||
|                 table: "file_references", | ||||
|                 column: "file_id"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "file_references"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "files"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										186
									
								
								DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Drive; | ||||
| using DysonNetwork.Drive.Storage; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Drive.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     partial class AppDatabaseModelSnapshot : ModelSnapshot | ||||
|     { | ||||
|         protected override void BuildModel(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.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<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<Dictionary<string, object>>("FileMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("file_meta"); | ||||
|  | ||||
|                     b.Property<bool>("HasCompression") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("has_compression"); | ||||
|  | ||||
|                     b.Property<string>("Hash") | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("hash"); | ||||
|  | ||||
|                     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<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<string>("UploadedTo") | ||||
|                         .HasMaxLength(128) | ||||
|                         .HasColumnType("character varying(128)") | ||||
|                         .HasColumnName("uploaded_to"); | ||||
|  | ||||
|                     b.Property<Dictionary<string, object>>("UserMeta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("user_meta"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_files"); | ||||
|  | ||||
|                     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.CloudFileReference", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("FileId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_file_references_files_file_id"); | ||||
|  | ||||
|                     b.Navigation("File"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -10,7 +10,8 @@ | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379" | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
| @@ -125,5 +126,11 @@ | ||||
|   "KnownProxies": [ | ||||
|     "127.0.0.1", | ||||
|     "::1" | ||||
|   ] | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Drive", | ||||
|     "Url": "http://localhost:5216", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,11 +5,13 @@ using DysonNetwork.Pass.Email; | ||||
| using DysonNetwork.Pass.Localization; | ||||
| using DysonNetwork.Pass.Permission; | ||||
| using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using EFCore.BulkExtensions; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Localization; | ||||
| using NodaTime; | ||||
| using OtpNet; | ||||
| using AuthSession = DysonNetwork.Pass.Auth.AuthSession; | ||||
|  | ||||
| namespace DysonNetwork.Pass.Account; | ||||
|  | ||||
| @@ -17,8 +19,8 @@ public class AccountService( | ||||
|     AppDatabase db, | ||||
|     MagicSpellService spells, | ||||
|     AccountUsernameService uname, | ||||
|     NotificationService nty, | ||||
|     EmailService mailer, | ||||
|     PusherService.PusherServiceClient pusher, | ||||
|     IStringLocalizer<NotificationResource> localizer, | ||||
|     ICacheService cache, | ||||
|     ILogger<AccountService> logger | ||||
| @@ -353,13 +355,18 @@ public class AccountService( | ||||
|                 if (await _GetFactorCode(factor) is not null) | ||||
|                     throw new InvalidOperationException("A factor code has been sent and in active duration."); | ||||
|  | ||||
|                 await nty.SendNotification( | ||||
|                     account, | ||||
|                     "auth.verification", | ||||
|                     localizer["AuthCodeTitle"], | ||||
|                     null, | ||||
|                     localizer["AuthCodeBody", code], | ||||
|                     save: true | ||||
|                 await pusher.SendPushNotificationToUserAsync( | ||||
|                     new SendPushNotificationToUserRequest | ||||
|                     { | ||||
|                         UserId = account.Id.ToString(), | ||||
|                         Notification = new PushNotification | ||||
|                         { | ||||
|                             Topic = "auth.verification", | ||||
|                             Title = localizer["AuthCodeTitle"], | ||||
|                             Body = localizer["AuthCodeBody", code], | ||||
|                             IsSavable = false | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|                 await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); | ||||
|                 break; | ||||
| @@ -489,7 +496,7 @@ public class AccountService( | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (session.Challenge.DeviceId is not null) | ||||
|             await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); | ||||
|             await pusher.UnsubscribePushNotifications(session.Challenge.DeviceId); | ||||
|  | ||||
|         // The current session should be included in the sessions' list | ||||
|         await db.AuthSessions | ||||
|   | ||||
| @@ -1,292 +0,0 @@ | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Pass; | ||||
| using EFCore.BulkExtensions; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
|  | ||||
| namespace DysonNetwork.Pass.Account; | ||||
|  | ||||
| public class NotificationService( | ||||
|     AppDatabase db, | ||||
|     IHttpClientFactory httpFactory, | ||||
|     IConfiguration config | ||||
| ) | ||||
| { | ||||
|     private readonly string _notifyTopic = config["Notifications:Topic"]!; | ||||
|     private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); | ||||
|  | ||||
|     public async Task UnsubscribePushNotifications(string deviceId) | ||||
|     { | ||||
|         await db.NotificationPushSubscriptions | ||||
|             .Where(s => s.DeviceId == deviceId) | ||||
|             .ExecuteDeleteAsync(); | ||||
|     } | ||||
|  | ||||
|     public async Task<NotificationPushSubscription> SubscribePushNotification( | ||||
|         Account account, | ||||
|         NotificationPushProvider provider, | ||||
|         string deviceId, | ||||
|         string deviceToken | ||||
|     ) | ||||
|     { | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|  | ||||
|         // First check if a matching subscription exists | ||||
|         var existingSubscription = await db.NotificationPushSubscriptions | ||||
|             .Where(s => s.AccountId == account.Id) | ||||
|             .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) | ||||
|             .FirstOrDefaultAsync(); | ||||
|  | ||||
|         if (existingSubscription is not null) | ||||
|         { | ||||
|             // Update the existing subscription directly in the database | ||||
|             await db.NotificationPushSubscriptions | ||||
|                 .Where(s => s.Id == existingSubscription.Id) | ||||
|                 .ExecuteUpdateAsync(setters => setters | ||||
|                     .SetProperty(s => s.DeviceId, deviceId) | ||||
|                     .SetProperty(s => s.DeviceToken, deviceToken) | ||||
|                     .SetProperty(s => s.UpdatedAt, now)); | ||||
|  | ||||
|             // Return the updated subscription | ||||
|             existingSubscription.DeviceId = deviceId; | ||||
|             existingSubscription.DeviceToken = deviceToken; | ||||
|             existingSubscription.UpdatedAt = now; | ||||
|             return existingSubscription; | ||||
|         } | ||||
|  | ||||
|         var subscription = new NotificationPushSubscription | ||||
|         { | ||||
|             DeviceId = deviceId, | ||||
|             DeviceToken = deviceToken, | ||||
|             Provider = provider, | ||||
|             AccountId = account.Id, | ||||
|         }; | ||||
|  | ||||
|         db.NotificationPushSubscriptions.Add(subscription); | ||||
|         await db.SaveChangesAsync(); | ||||
|  | ||||
|         return subscription; | ||||
|     } | ||||
|  | ||||
|     public async Task<Notification> SendNotification( | ||||
|         Account account, | ||||
|         string topic, | ||||
|         string? title = null, | ||||
|         string? subtitle = null, | ||||
|         string? content = null, | ||||
|         Dictionary<string, object>? meta = null, | ||||
|         string? actionUri = null, | ||||
|         bool isSilent = false, | ||||
|         bool save = true | ||||
|     ) | ||||
|     { | ||||
|         if (title is null && subtitle is null && content is null) | ||||
|             throw new ArgumentException("Unable to send notification that completely empty."); | ||||
|  | ||||
|         meta ??= new Dictionary<string, object>(); | ||||
|         if (actionUri is not null) meta["action_uri"] = actionUri; | ||||
|  | ||||
|         var notification = new Notification | ||||
|         { | ||||
|             Topic = topic, | ||||
|             Title = title, | ||||
|             Subtitle = subtitle, | ||||
|             Content = content, | ||||
|             Meta = meta, | ||||
|             AccountId = account.Id, | ||||
|         }; | ||||
|  | ||||
|         if (save) | ||||
|         { | ||||
|             db.Add(notification); | ||||
|             await db.SaveChangesAsync(); | ||||
|         } | ||||
|  | ||||
|         if (!isSilent) _ = DeliveryNotification(notification); | ||||
|  | ||||
|         return notification; | ||||
|     } | ||||
|  | ||||
|     public async Task DeliveryNotification(Notification notification) | ||||
|     { | ||||
|         // Pushing the notification | ||||
|         var subscribers = await db.NotificationPushSubscriptions | ||||
|             .Where(s => s.AccountId == notification.AccountId) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         await _PushNotification(notification, subscribers); | ||||
|     } | ||||
|  | ||||
|     public async Task MarkNotificationsViewed(ICollection<Notification> notifications) | ||||
|     { | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|         var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); | ||||
|         if (id.Count == 0) return; | ||||
|  | ||||
|         await db.Notifications | ||||
|             .Where(n => id.Contains(n.Id)) | ||||
|             .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     public async Task BroadcastNotification(Notification notification, bool save = false) | ||||
|     { | ||||
|         var accounts = await db.Accounts.ToListAsync(); | ||||
|  | ||||
|         if (save) | ||||
|         { | ||||
|             var notifications = accounts.Select(x => | ||||
|             { | ||||
|                 var newNotification = new Notification | ||||
|                 { | ||||
|                     Topic = notification.Topic, | ||||
|                     Title = notification.Title, | ||||
|                     Subtitle = notification.Subtitle, | ||||
|                     Content = notification.Content, | ||||
|                     Meta = notification.Meta, | ||||
|                     Priority = notification.Priority, | ||||
|                     Account = x, | ||||
|                     AccountId = x.Id | ||||
|                 }; | ||||
|                 return newNotification; | ||||
|             }).ToList(); | ||||
|             await db.BulkInsertAsync(notifications); | ||||
|         } | ||||
|  | ||||
|         foreach (var account in accounts) | ||||
|         { | ||||
|             notification.Account = account; | ||||
|             notification.AccountId = account.Id; | ||||
|         } | ||||
|  | ||||
|         var subscribers = await db.NotificationPushSubscriptions | ||||
|             .ToListAsync(); | ||||
|         await _PushNotification(notification, subscribers); | ||||
|     } | ||||
|  | ||||
|     public async Task SendNotificationBatch(Notification notification, List<Account> accounts, bool save = false) | ||||
|     { | ||||
|         if (save) | ||||
|         { | ||||
|             var notifications = accounts.Select(x => | ||||
|             { | ||||
|                 var newNotification = new Notification | ||||
|                 { | ||||
|                     Topic = notification.Topic, | ||||
|                     Title = notification.Title, | ||||
|                     Subtitle = notification.Subtitle, | ||||
|                     Content = notification.Content, | ||||
|                     Meta = notification.Meta, | ||||
|                     Priority = notification.Priority, | ||||
|                     Account = x, | ||||
|                     AccountId = x.Id | ||||
|                 }; | ||||
|                 return newNotification; | ||||
|             }).ToList(); | ||||
|             await db.BulkInsertAsync(notifications); | ||||
|         } | ||||
|  | ||||
|         foreach (var account in accounts) | ||||
|         { | ||||
|             notification.Account = account; | ||||
|             notification.AccountId = account.Id; | ||||
|         } | ||||
|  | ||||
|         var accountsId = accounts.Select(x => x.Id).ToList(); | ||||
|         var subscribers = await db.NotificationPushSubscriptions | ||||
|             .Where(s => accountsId.Contains(s.AccountId)) | ||||
|             .ToListAsync(); | ||||
|         await _PushNotification(notification, subscribers); | ||||
|     } | ||||
|  | ||||
|     private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification, | ||||
|         IEnumerable<NotificationPushSubscription> subscriptions) | ||||
|     { | ||||
|         var subDict = subscriptions | ||||
|             .GroupBy(x => x.Provider) | ||||
|             .ToDictionary(x => x.Key, x => x.ToList()); | ||||
|  | ||||
|         var notifications = subDict.Select(value => | ||||
|         { | ||||
|             var platformCode = value.Key switch | ||||
|             { | ||||
|                 NotificationPushProvider.Apple => 1, | ||||
|                 NotificationPushProvider.Google => 2, | ||||
|                 _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") | ||||
|             }; | ||||
|  | ||||
|             var tokens = value.Value.Select(x => x.DeviceToken).ToList(); | ||||
|             return _BuildNotificationPayload(notification, platformCode, tokens); | ||||
|         }).ToList(); | ||||
|  | ||||
|         return notifications.ToList(); | ||||
|     } | ||||
|  | ||||
|     private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode, | ||||
|         IEnumerable<string> deviceTokens) | ||||
|     { | ||||
|         var alertDict = new Dictionary<string, object>(); | ||||
|         var dict = new Dictionary<string, object> | ||||
|         { | ||||
|             ["notif_id"] = notification.Id.ToString(), | ||||
|             ["apns_id"] = notification.Id.ToString(), | ||||
|             ["topic"] = _notifyTopic, | ||||
|             ["tokens"] = deviceTokens, | ||||
|             ["data"] = new Dictionary<string, object> | ||||
|             { | ||||
|                 ["type"] = notification.Topic, | ||||
|                 ["meta"] = notification.Meta ?? new Dictionary<string, object>(), | ||||
|             }, | ||||
|             ["mutable_content"] = true, | ||||
|             ["priority"] = notification.Priority >= 5 ? "high" : "normal", | ||||
|         }; | ||||
|  | ||||
|         if (!string.IsNullOrWhiteSpace(notification.Title)) | ||||
|         { | ||||
|             dict["title"] = notification.Title; | ||||
|             alertDict["title"] = notification.Title; | ||||
|         } | ||||
|  | ||||
|         if (!string.IsNullOrWhiteSpace(notification.Content)) | ||||
|         { | ||||
|             dict["message"] = notification.Content; | ||||
|             alertDict["body"] = notification.Content; | ||||
|         } | ||||
|  | ||||
|         if (!string.IsNullOrWhiteSpace(notification.Subtitle)) | ||||
|         { | ||||
|             dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; | ||||
|             alertDict["subtitle"] = notification.Subtitle; | ||||
|         } | ||||
|  | ||||
|         if (notification.Priority >= 5) | ||||
|             dict["name"] = "default"; | ||||
|  | ||||
|         dict["platform"] = platformCode; | ||||
|         dict["alert"] = alertDict; | ||||
|  | ||||
|         return dict; | ||||
|     } | ||||
|  | ||||
|     private async Task _PushNotification(Notification notification, | ||||
|         IEnumerable<NotificationPushSubscription> subscriptions) | ||||
|     { | ||||
|         var subList = subscriptions.ToList(); | ||||
|         if (subList.Count == 0) return; | ||||
|  | ||||
|         var requestDict = new Dictionary<string, object> | ||||
|         { | ||||
|             ["notifications"] = _BuildNotificationPayload(notification, subList) | ||||
|         }; | ||||
|  | ||||
|         var client = httpFactory.CreateClient(); | ||||
|         client.BaseAddress = _notifyEndpoint; | ||||
|         var request = await client.PostAsync("/push", new StringContent( | ||||
|             JsonSerializer.Serialize(requestDict), | ||||
|             Encoding.UTF8, | ||||
|             "application/json" | ||||
|         )); | ||||
|         request.EnsureSuccessStatusCode(); | ||||
|     } | ||||
| } | ||||
| @@ -9,6 +9,10 @@ | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" /> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" /> | ||||
|         <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7"> | ||||
|           <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|           <PrivateAssets>all</PrivateAssets> | ||||
|         </PackageReference> | ||||
|         <PackageReference Include="NodaTime" Version="3.2.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" /> | ||||
|   | ||||
| @@ -1,33 +1,23 @@ | ||||
| using dotnet_etcd; | ||||
| using dotnet_etcd.interfaces; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| namespace DysonNetwork.Pass.Email; | ||||
|  | ||||
| public class EmailService | ||||
| public class EmailService( | ||||
|     IEtcdClient etcd, | ||||
|     RazorViewRenderer viewRenderer, | ||||
|     IConfiguration configuration, | ||||
|     ILogger<EmailService> logger | ||||
| ) | ||||
| { | ||||
|     private readonly PusherService.PusherServiceClient _client; | ||||
|     private readonly RazorViewRenderer _viewRenderer; | ||||
|     private readonly ILogger<EmailService> _logger; | ||||
|     private readonly PusherService.PusherServiceClient _client = GrpcClientHelper.CreatePusherServiceClient( | ||||
|         etcd, | ||||
|         configuration["Service:CertPath"]!, | ||||
|         configuration["Service:KeyPath"]! | ||||
|     ).GetAwaiter().GetResult(); | ||||
|  | ||||
|     public EmailService( | ||||
|         EtcdClient etcd, | ||||
|         RazorViewRenderer viewRenderer, | ||||
|         IConfiguration configuration, | ||||
|         ILogger<EmailService> logger, | ||||
|         PusherService.PusherServiceClient client | ||||
|     ) | ||||
|     { | ||||
|         _client = GrpcClientHelper.CreatePusherServiceClient( | ||||
|             etcd, | ||||
|             configuration["Service:CertPath"]!, | ||||
|             configuration["Service:KeyPath"]! | ||||
|         ).GetAwaiter().GetResult(); | ||||
|         _viewRenderer = viewRenderer; | ||||
|         _logger = logger; | ||||
|         _client = client; | ||||
|     } | ||||
|      | ||||
|     public async Task SendEmailAsync( | ||||
|         string? recipientName, | ||||
|         string recipientEmail, | ||||
| @@ -57,12 +47,12 @@ public class EmailService | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var htmlBody = await _viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model); | ||||
|             var htmlBody = await viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model); | ||||
|             await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody); | ||||
|         } | ||||
|         catch (Exception err) | ||||
|         { | ||||
|             _logger.LogError(err, "Failed to render email template..."); | ||||
|             logger.LogError(err, "Failed to render email template..."); | ||||
|             throw; | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										1977
									
								
								DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1977
									
								
								DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										998
									
								
								DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										998
									
								
								DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,998 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Pass.Account; | ||||
| using DysonNetwork.Pass.Developer; | ||||
| using DysonNetwork.Pass.Wallet; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NetTopologySuite.Geometries; | ||||
| using NodaTime; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Pass.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class InitialMigration : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AlterDatabase() | ||||
|                 .Annotation("Npgsql:PostgresExtension:postgis", ",,"); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "accounts", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), | ||||
|                     nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), | ||||
|                     language = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false), | ||||
|                     activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     is_superuser = table.Column<bool>(type: "boolean", 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_accounts", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "custom_apps", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     status = table.Column<int>(type: "integer", nullable: false), | ||||
|                     picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true), | ||||
|                     background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true), | ||||
|                     verification = table.Column<VerificationMark>(type: "jsonb", nullable: true), | ||||
|                     oauth_config = table.Column<CustomAppOauthConfig>(type: "jsonb", nullable: true), | ||||
|                     links = table.Column<CustomAppLinks>(type: "jsonb", nullable: true), | ||||
|                     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_custom_apps", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "permission_groups", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, 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_permission_groups", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "wallet_coupons", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     code = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     discount_amount = table.Column<decimal>(type: "numeric", nullable: true), | ||||
|                     discount_rate = table.Column<double>(type: "double precision", nullable: true), | ||||
|                     max_usage = table.Column<int>(type: "integer", nullable: true), | ||||
|                     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_wallet_coupons", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "abuse_reports", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false), | ||||
|                     resolved_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     resolution = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: true), | ||||
|                     account_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_abuse_reports", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_abuse_reports_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_auth_factors", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     secret = table.Column<string>(type: "character varying(8196)", maxLength: 8196, nullable: true), | ||||
|                     config = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     trustworthy = table.Column<int>(type: "integer", nullable: false), | ||||
|                     enabled_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_account_auth_factors", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_auth_factors_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_check_in_results", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     level = table.Column<int>(type: "integer", nullable: false), | ||||
|                     reward_points = table.Column<decimal>(type: "numeric", nullable: true), | ||||
|                     reward_experience = table.Column<int>(type: "integer", nullable: true), | ||||
|                     tips = table.Column<ICollection<FortuneTip>>(type: "jsonb", nullable: false), | ||||
|                     account_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_account_check_in_results", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_check_in_results_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_connections", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     provider = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     provided_identifier = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     access_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     refresh_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_account_connections", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_connections_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_contacts", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     is_primary = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     account_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_account_contacts", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_contacts_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_profiles", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     first_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     middle_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     last_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     gender = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     pronouns = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     time_zone = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     location = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     verification = table.Column<VerificationMark>(type: "jsonb", nullable: true), | ||||
|                     active_badge = table.Column<BadgeReferenceObject>(type: "jsonb", nullable: true), | ||||
|                     experience = table.Column<int>(type: "integer", nullable: false), | ||||
|                     picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), | ||||
|                     background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), | ||||
|                     picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true), | ||||
|                     background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true), | ||||
|                     account_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_account_profiles", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_profiles_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_relationships", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     account_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     related_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     status = table.Column<short>(type: "smallint", 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_account_relationships", x => new { x.account_id, x.related_id }); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_relationships_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_relationships_accounts_related_id", | ||||
|                         column: x => x.related_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_statuses", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     attitude = table.Column<int>(type: "integer", nullable: false), | ||||
|                     is_invisible = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     is_not_disturb = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     cleared_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_account_statuses", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_statuses_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "action_logs", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     action = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false), | ||||
|                     user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true), | ||||
|                     ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true), | ||||
|                     location = table.Column<Point>(type: "geometry", nullable: true), | ||||
|                     account_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     session_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_action_logs", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_action_logs_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "auth_challenges", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     step_remain = table.Column<int>(type: "integer", nullable: false), | ||||
|                     step_total = table.Column<int>(type: "integer", nullable: false), | ||||
|                     failed_attempts = table.Column<int>(type: "integer", nullable: false), | ||||
|                     platform = table.Column<int>(type: "integer", nullable: false), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     blacklist_factors = table.Column<List<Guid>>(type: "jsonb", nullable: false), | ||||
|                     audiences = table.Column<List<string>>(type: "jsonb", nullable: false), | ||||
|                     scopes = table.Column<List<string>>(type: "jsonb", nullable: false), | ||||
|                     ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true), | ||||
|                     user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true), | ||||
|                     device_id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), | ||||
|                     nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     location = table.Column<Point>(type: "geometry", nullable: true), | ||||
|                     account_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_auth_challenges", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_auth_challenges_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "badges", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     caption = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false), | ||||
|                     activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_badges", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_badges_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "magic_spells", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     spell = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false), | ||||
|                     account_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_magic_spells", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_magic_spells_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "notification_push_subscriptions", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     provider = table.Column<int>(type: "integer", nullable: false), | ||||
|                     last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_notification_push_subscriptions", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_notification_push_subscriptions_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "notifications", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true), | ||||
|                     content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     priority = table.Column<int>(type: "integer", nullable: false), | ||||
|                     viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_notifications", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_notifications_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "wallets", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     account_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_wallets", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_wallets_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "custom_app_secrets", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     is_oidc = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     app_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_custom_app_secrets", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_custom_app_secrets_custom_apps_app_id", | ||||
|                         column: x => x.app_id, | ||||
|                         principalTable: "custom_apps", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "permission_group_members", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     group_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     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_permission_group_members", x => new { x.group_id, x.actor }); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_permission_group_members_permission_groups_group_id", | ||||
|                         column: x => x.group_id, | ||||
|                         principalTable: "permission_groups", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "permission_nodes", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     value = table.Column<JsonDocument>(type: "jsonb", nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     group_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_permission_nodes", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_permission_nodes_permission_groups_group_id", | ||||
|                         column: x => x.group_id, | ||||
|                         principalTable: "permission_groups", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "wallet_subscriptions", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     begun_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     ended_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     is_active = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     is_free_trial = table.Column<bool>(type: "boolean", nullable: false), | ||||
|                     status = table.Column<int>(type: "integer", nullable: false), | ||||
|                     payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), | ||||
|                     payment_details = table.Column<PaymentDetails>(type: "jsonb", nullable: false), | ||||
|                     base_price = table.Column<decimal>(type: "numeric", nullable: false), | ||||
|                     coupon_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     renewal_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_wallet_subscriptions", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_wallet_subscriptions_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_wallet_subscriptions_wallet_coupons_coupon_id", | ||||
|                         column: x => x.coupon_id, | ||||
|                         principalTable: "wallet_coupons", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "auth_sessions", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     last_granted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     challenge_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     app_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_auth_sessions", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_auth_sessions_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_auth_sessions_auth_challenges_challenge_id", | ||||
|                         column: x => x.challenge_id, | ||||
|                         principalTable: "auth_challenges", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_auth_sessions_custom_apps_app_id", | ||||
|                         column: x => x.app_id, | ||||
|                         principalTable: "custom_apps", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "payment_transactions", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false), | ||||
|                     amount = table.Column<decimal>(type: "numeric", nullable: false), | ||||
|                     remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     payer_wallet_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_payment_transactions", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_payment_transactions_wallets_payee_wallet_id", | ||||
|                         column: x => x.payee_wallet_id, | ||||
|                         principalTable: "wallets", | ||||
|                         principalColumn: "id"); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_payment_transactions_wallets_payer_wallet_id", | ||||
|                         column: x => x.payer_wallet_id, | ||||
|                         principalTable: "wallets", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "wallet_pockets", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false), | ||||
|                     amount = table.Column<decimal>(type: "numeric", nullable: false), | ||||
|                     wallet_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_wallet_pockets", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_wallet_pockets_wallets_wallet_id", | ||||
|                         column: x => x.wallet_id, | ||||
|                         principalTable: "wallets", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "payment_orders", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     status = table.Column<int>(type: "integer", nullable: false), | ||||
|                     currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false), | ||||
|                     remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     app_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     amount = table.Column<decimal>(type: "numeric", nullable: false), | ||||
|                     expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     transaction_id = table.Column<Guid>(type: "uuid", nullable: true), | ||||
|                     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_payment_orders", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_payment_orders_payment_transactions_transaction_id", | ||||
|                         column: x => x.transaction_id, | ||||
|                         principalTable: "payment_transactions", | ||||
|                         principalColumn: "id"); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_payment_orders_wallets_payee_wallet_id", | ||||
|                         column: x => x.payee_wallet_id, | ||||
|                         principalTable: "wallets", | ||||
|                         principalColumn: "id"); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_abuse_reports_account_id", | ||||
|                 table: "abuse_reports", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_auth_factors_account_id", | ||||
|                 table: "account_auth_factors", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_check_in_results_account_id", | ||||
|                 table: "account_check_in_results", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_connections_account_id", | ||||
|                 table: "account_connections", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_contacts_account_id", | ||||
|                 table: "account_contacts", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_profiles_account_id", | ||||
|                 table: "account_profiles", | ||||
|                 column: "account_id", | ||||
|                 unique: true); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_relationships_related_id", | ||||
|                 table: "account_relationships", | ||||
|                 column: "related_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_statuses_account_id", | ||||
|                 table: "account_statuses", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_accounts_name", | ||||
|                 table: "accounts", | ||||
|                 column: "name", | ||||
|                 unique: true); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_action_logs_account_id", | ||||
|                 table: "action_logs", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_auth_challenges_account_id", | ||||
|                 table: "auth_challenges", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_auth_sessions_account_id", | ||||
|                 table: "auth_sessions", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_auth_sessions_app_id", | ||||
|                 table: "auth_sessions", | ||||
|                 column: "app_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_auth_sessions_challenge_id", | ||||
|                 table: "auth_sessions", | ||||
|                 column: "challenge_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_badges_account_id", | ||||
|                 table: "badges", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_custom_app_secrets_app_id", | ||||
|                 table: "custom_app_secrets", | ||||
|                 column: "app_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_magic_spells_account_id", | ||||
|                 table: "magic_spells", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_magic_spells_spell", | ||||
|                 table: "magic_spells", | ||||
|                 column: "spell", | ||||
|                 unique: true); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_notification_push_subscriptions_account_id", | ||||
|                 table: "notification_push_subscriptions", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_notification_push_subscriptions_device_token_device_id_acco", | ||||
|                 table: "notification_push_subscriptions", | ||||
|                 columns: new[] { "device_token", "device_id", "account_id" }, | ||||
|                 unique: true); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_notifications_account_id", | ||||
|                 table: "notifications", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_payment_orders_payee_wallet_id", | ||||
|                 table: "payment_orders", | ||||
|                 column: "payee_wallet_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_payment_orders_transaction_id", | ||||
|                 table: "payment_orders", | ||||
|                 column: "transaction_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_payment_transactions_payee_wallet_id", | ||||
|                 table: "payment_transactions", | ||||
|                 column: "payee_wallet_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_payment_transactions_payer_wallet_id", | ||||
|                 table: "payment_transactions", | ||||
|                 column: "payer_wallet_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_permission_nodes_group_id", | ||||
|                 table: "permission_nodes", | ||||
|                 column: "group_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_permission_nodes_key_area_actor", | ||||
|                 table: "permission_nodes", | ||||
|                 columns: new[] { "key", "area", "actor" }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_wallet_pockets_wallet_id", | ||||
|                 table: "wallet_pockets", | ||||
|                 column: "wallet_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_wallet_subscriptions_account_id", | ||||
|                 table: "wallet_subscriptions", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_wallet_subscriptions_coupon_id", | ||||
|                 table: "wallet_subscriptions", | ||||
|                 column: "coupon_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_wallet_subscriptions_identifier", | ||||
|                 table: "wallet_subscriptions", | ||||
|                 column: "identifier"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_wallets_account_id", | ||||
|                 table: "wallets", | ||||
|                 column: "account_id"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "abuse_reports"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_auth_factors"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_check_in_results"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_connections"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_contacts"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_profiles"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_relationships"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_statuses"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "action_logs"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "auth_sessions"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "badges"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "custom_app_secrets"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "magic_spells"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "notification_push_subscriptions"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "notifications"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "payment_orders"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "permission_group_members"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "permission_nodes"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "wallet_pockets"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "wallet_subscriptions"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "auth_challenges"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "custom_apps"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "payment_transactions"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "permission_groups"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "wallet_coupons"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "wallets"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "accounts"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1974
									
								
								DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1974
									
								
								DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -28,8 +28,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration); | ||||
| // Add scheduled jobs | ||||
| builder.Services.AddAppScheduledJobs(); | ||||
|  | ||||
| builder.Services.AddHostedService<ServiceRegistrationHostedService>(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| // Run database migrations | ||||
|   | ||||
| @@ -1,56 +0,0 @@ | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace DysonNetwork.Pass.Startup; | ||||
|  | ||||
| public class ServiceRegistrationHostedService : IHostedService | ||||
| { | ||||
|     private readonly ServiceRegistry _serviceRegistry; | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly ILogger<ServiceRegistrationHostedService> _logger; | ||||
|  | ||||
|     public ServiceRegistrationHostedService( | ||||
|         ServiceRegistry serviceRegistry, | ||||
|         IConfiguration configuration, | ||||
|         ILogger<ServiceRegistrationHostedService> logger) | ||||
|     { | ||||
|         _serviceRegistry = serviceRegistry; | ||||
|         _configuration = configuration; | ||||
|         _logger = logger; | ||||
|     } | ||||
|  | ||||
|     public async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         var serviceName = "DysonNetwork.Pass"; // Preset service name | ||||
|         var serviceUrl = _configuration["Service:Url"]; | ||||
|  | ||||
|         if (string.IsNullOrEmpty(serviceName) || string.IsNullOrEmpty(serviceUrl)) | ||||
|         { | ||||
|             _logger.LogWarning("Service name or URL not configured. Skipping Etcd registration."); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); | ||||
|         try | ||||
|         { | ||||
|             await _serviceRegistry.RegisterService(serviceName, serviceUrl); | ||||
|             _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         // The lease will expire automatically if the service stops. | ||||
|         // For explicit unregistration, you would implement it here. | ||||
|         _logger.LogInformation("Service registration hosted service is stopping."); | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| } | ||||
| @@ -9,8 +9,9 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
| @@ -36,60 +37,11 @@ | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Tus": { | ||||
|     "StorePath": "Uploads" | ||||
|   }, | ||||
|   "Storage": { | ||||
|     "PreferredRemote": "minio", | ||||
|     "Remote": [ | ||||
|       { | ||||
|         "Id": "minio", | ||||
|         "Label": "Minio", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network-development", | ||||
|         "Endpoint": "localhost:9000", | ||||
|         "SecretId": "littlesheep", | ||||
|         "SecretKey": "password", | ||||
|         "EnabledSigned": true, | ||||
|         "EnableSsl": false | ||||
|       }, | ||||
|       { | ||||
|         "Id": "cloudflare", | ||||
|         "Label": "Cloudflare R2", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network", | ||||
|         "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", | ||||
|         "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", | ||||
|         "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", | ||||
|         "EnableSigned": true, | ||||
|         "EnableSsl": true | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Topic": "dev.solsynth.solian", | ||||
|     "Endpoint": "http://localhost:8088" | ||||
|   }, | ||||
|   "Email": { | ||||
|     "Server": "smtp4dev.orb.local", | ||||
|     "Port": 25, | ||||
|     "UseSsl": false, | ||||
|     "Username": "no-reply@mail.solsynth.dev", | ||||
|     "Password": "password", | ||||
|     "FromAddress": "no-reply@mail.solsynth.dev", | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "APIs6TiL8wj3A4j", | ||||
|     "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
| @@ -125,5 +77,14 @@ | ||||
|   "KnownProxies": [ | ||||
|     "127.0.0.1", | ||||
|     "::1" | ||||
|   ] | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Pass", | ||||
|     "Url": "http://localhost:5216", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,11 @@ | ||||
|         <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" /> | ||||
|         <PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" /> | ||||
|         <PackageReference Include="MailKit" Version="4.13.0" /> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" /> | ||||
|         <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7"> | ||||
|           <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|           <PrivateAssets>all</PrivateAssets> | ||||
|         </PackageReference> | ||||
|         <PackageReference Include="NodaTime" Version="3.2.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> | ||||
|   | ||||
							
								
								
									
										151
									
								
								DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Pusher; | ||||
| 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.Pusher.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     [Migration("20250713122638_InitialMigration")] | ||||
|     partial class InitialMigration | ||||
|     { | ||||
|         /// <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.Pusher.Notification.Notification", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<string>("Content") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("content"); | ||||
|  | ||||
|                     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<Dictionary<string, object>>("Meta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("meta"); | ||||
|  | ||||
|                     b.Property<int>("Priority") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("priority"); | ||||
|  | ||||
|                     b.Property<string>("Subtitle") | ||||
|                         .HasMaxLength(2048) | ||||
|                         .HasColumnType("character varying(2048)") | ||||
|                         .HasColumnName("subtitle"); | ||||
|  | ||||
|                     b.Property<string>("Title") | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("title"); | ||||
|  | ||||
|                     b.Property<string>("Topic") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("topic"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("ViewedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("viewed_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_notifications"); | ||||
|  | ||||
|                     b.ToTable("notifications", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<int>("CountDelivered") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("count_delivered"); | ||||
|  | ||||
|                     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>("DeviceId") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("device_id"); | ||||
|  | ||||
|                     b.Property<string>("DeviceToken") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("device_token"); | ||||
|  | ||||
|                     b.Property<Instant?>("LastUsedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("last_used_at"); | ||||
|  | ||||
|                     b.Property<int>("Provider") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("provider"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_push_subscriptions"); | ||||
|  | ||||
|                     b.HasIndex("AccountId", "DeviceId", "DeletedAt") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at"); | ||||
|  | ||||
|                     b.ToTable("push_subscriptions", (string)null); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,75 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Pusher.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class InitialMigration : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "notifications", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), | ||||
|                     subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true), | ||||
|                     content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true), | ||||
|                     meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true), | ||||
|                     priority = table.Column<int>(type: "integer", nullable: false), | ||||
|                     viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     account_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_notifications", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "push_subscriptions", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     account_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     device_id = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false), | ||||
|                     device_token = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false), | ||||
|                     provider = table.Column<int>(type: "integer", nullable: false), | ||||
|                     count_delivered = table.Column<int>(type: "integer", nullable: false), | ||||
|                     last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     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_push_subscriptions", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_push_subscriptions_account_id_device_id_deleted_at", | ||||
|                 table: "push_subscriptions", | ||||
|                 columns: new[] { "account_id", "device_id", "deleted_at" }, | ||||
|                 unique: true); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "notifications"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "push_subscriptions"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										148
									
								
								DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using DysonNetwork.Pusher; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Pusher.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     partial class AppDatabaseModelSnapshot : ModelSnapshot | ||||
|     { | ||||
|         protected override void BuildModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.7") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Pusher.Notification.Notification", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<string>("Content") | ||||
|                         .HasMaxLength(4096) | ||||
|                         .HasColumnType("character varying(4096)") | ||||
|                         .HasColumnName("content"); | ||||
|  | ||||
|                     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<Dictionary<string, object>>("Meta") | ||||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("meta"); | ||||
|  | ||||
|                     b.Property<int>("Priority") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("priority"); | ||||
|  | ||||
|                     b.Property<string>("Subtitle") | ||||
|                         .HasMaxLength(2048) | ||||
|                         .HasColumnType("character varying(2048)") | ||||
|                         .HasColumnName("subtitle"); | ||||
|  | ||||
|                     b.Property<string>("Title") | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("title"); | ||||
|  | ||||
|                     b.Property<string>("Topic") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("topic"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("ViewedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("viewed_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_notifications"); | ||||
|  | ||||
|                     b.ToTable("notifications", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     b.Property<Guid>("AccountId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<int>("CountDelivered") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("count_delivered"); | ||||
|  | ||||
|                     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>("DeviceId") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("device_id"); | ||||
|  | ||||
|                     b.Property<string>("DeviceToken") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(8192) | ||||
|                         .HasColumnType("character varying(8192)") | ||||
|                         .HasColumnName("device_token"); | ||||
|  | ||||
|                     b.Property<Instant?>("LastUsedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("last_used_at"); | ||||
|  | ||||
|                     b.Property<int>("Provider") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("provider"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_push_subscriptions"); | ||||
|  | ||||
|                     b.HasIndex("AccountId", "DeviceId", "DeletedAt") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at"); | ||||
|  | ||||
|                     b.ToTable("push_subscriptions", (string)null); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -19,6 +19,5 @@ public class Notification : ModelBase | ||||
|     public Instant? ViewedAt { get; set; } | ||||
|  | ||||
|     public Guid AccountId { get; set; } | ||||
|     [JsonIgnore] public Account Account { get; set; } = null!; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,11 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using DysonNetwork.Pass; | ||||
| using DysonNetwork.Pass.Auth; | ||||
| using DysonNetwork.Pass.Permission; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| 
 | ||||
| namespace DysonNetwork.Pass.Account; | ||||
| namespace DysonNetwork.Pusher.Notification; | ||||
| 
 | ||||
| [ApiController] | ||||
| [Route("/api/notifications")] | ||||
| @@ -33,8 +33,6 @@ public static class ApplicationConfiguration | ||||
|         app.UseAuthorization(); | ||||
|  | ||||
|         app.MapControllers().RequireRateLimiting("fixed"); | ||||
|         app.MapStaticAssets().RequireRateLimiting("fixed"); | ||||
|         app.MapRazorPages().RequireRateLimiting("fixed"); | ||||
|  | ||||
|         return app; | ||||
|     } | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| using System.Text.Json; | ||||
| using System.Threading.RateLimiting; | ||||
| using dotnet_etcd.interfaces; | ||||
| using DysonNetwork.Pusher.Connection; | ||||
| using DysonNetwork.Pusher.Email; | ||||
| using DysonNetwork.Pusher.Notification; | ||||
| using DysonNetwork.Pusher.Services; | ||||
| using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Microsoft.AspNetCore.RateLimiting; | ||||
| using Microsoft.OpenApi.Models; | ||||
| using NodaTime; | ||||
| @@ -44,18 +43,6 @@ public static class ServiceCollectionExtensions | ||||
|         // Register gRPC services | ||||
|         services.AddScoped<PusherServiceGrpc>(); | ||||
|  | ||||
|         // Register AuthService.AuthServiceClient for AuthMiddleware | ||||
|         services.AddSingleton(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var configuration = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = configuration["ClientCert:Path"]; | ||||
|             var clientKeyPath = configuration["ClientKey:Path"]; | ||||
|             var clientCertPassword = configuration["ClientCert:Password"]; | ||||
|  | ||||
|             return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); | ||||
|         }); | ||||
|  | ||||
|         // Register OIDC services | ||||
|         services.AddControllers().AddJsonOptions(options => | ||||
|         { | ||||
| @@ -144,6 +131,7 @@ public static class ServiceCollectionExtensions | ||||
|  | ||||
|     public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddScoped<WebSocketService>(); | ||||
|         services.AddScoped<EmailService>(); | ||||
|         services.AddScoped<PushService>(); | ||||
|  | ||||
|   | ||||
| @@ -1,56 +0,0 @@ | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace DysonNetwork.Pusher.Startup; | ||||
|  | ||||
| public class ServiceRegistrationHostedService : IHostedService | ||||
| { | ||||
|     private readonly ServiceRegistry _serviceRegistry; | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly ILogger<ServiceRegistrationHostedService> _logger; | ||||
|  | ||||
|     public ServiceRegistrationHostedService( | ||||
|         ServiceRegistry serviceRegistry, | ||||
|         IConfiguration configuration, | ||||
|         ILogger<ServiceRegistrationHostedService> logger) | ||||
|     { | ||||
|         _serviceRegistry = serviceRegistry; | ||||
|         _configuration = configuration; | ||||
|         _logger = logger; | ||||
|     } | ||||
|  | ||||
|     public async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         var serviceName = "DysonNetwork.Pusher"; // Preset service name | ||||
|         var serviceUrl = _configuration["Service:Url"]; | ||||
|  | ||||
|         if (string.IsNullOrEmpty(serviceUrl)) | ||||
|         { | ||||
|             _logger.LogWarning("Service URL not configured. Skipping Etcd registration."); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); | ||||
|         try | ||||
|         { | ||||
|             await _serviceRegistry.RegisterService(serviceName, serviceUrl); | ||||
|             _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         // The lease will expire automatically if the service stops. | ||||
|         // For explicit unregistration, you would implement it here. | ||||
|         _logger.LogInformation("Service registration hosted service is stopping."); | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5071", | ||||
|   "BaseUrl": "http://localhost:5212", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
| @@ -9,67 +9,9 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|       "Bearer": { | ||||
|         "ValidAudiences": [ | ||||
|           "http://localhost:5071", | ||||
|           "https://localhost:7099" | ||||
|         ], | ||||
|         "ValidIssuer": "solar-network" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "AuthToken": { | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem" | ||||
|   }, | ||||
|   "OidcProvider": { | ||||
|     "IssuerUri": "https://nt.solian.app", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem", | ||||
|     "AccessTokenLifetime": "01:00:00", | ||||
|     "RefreshTokenLifetime": "30.00:00:00", | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Tus": { | ||||
|     "StorePath": "Uploads" | ||||
|   }, | ||||
|   "Storage": { | ||||
|     "PreferredRemote": "minio", | ||||
|     "Remote": [ | ||||
|       { | ||||
|         "Id": "minio", | ||||
|         "Label": "Minio", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network-development", | ||||
|         "Endpoint": "localhost:9000", | ||||
|         "SecretId": "littlesheep", | ||||
|         "SecretKey": "password", | ||||
|         "EnabledSigned": true, | ||||
|         "EnableSsl": false | ||||
|       }, | ||||
|       { | ||||
|         "Id": "cloudflare", | ||||
|         "Label": "Cloudflare R2", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network", | ||||
|         "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", | ||||
|         "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", | ||||
|         "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", | ||||
|         "EnableSigned": true, | ||||
|         "EnableSsl": true | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Topic": "dev.solsynth.solian", | ||||
| @@ -85,45 +27,20 @@ | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "APIs6TiL8wj3A4j", | ||||
|     "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "Oidc": { | ||||
|     "Google": { | ||||
|       "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||
|       "ClientSecret": "" | ||||
|     }, | ||||
|     "Apple": { | ||||
|       "ClientId": "dev.solsynth.solian", | ||||
|       "TeamId": "W7HPZ53V6B", | ||||
|       "KeyId": "B668YP4KBG", | ||||
|       "PrivateKeyPath": "./Keys/Solarpass.p8" | ||||
|     }, | ||||
|     "Microsoft": { | ||||
|       "ClientId": "YOUR_MICROSOFT_CLIENT_ID", | ||||
|       "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", | ||||
|       "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" | ||||
|     } | ||||
|   }, | ||||
|   "Payment": { | ||||
|     "Auth": { | ||||
|       "Afdian": "<token here>" | ||||
|     }, | ||||
|     "Subscriptions": { | ||||
|       "Afdian": { | ||||
|         "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", | ||||
|         "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", | ||||
|         "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "KnownProxies": [ | ||||
|     "127.0.0.1", | ||||
|     "::1" | ||||
|   ] | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Pusher", | ||||
|     "Url": "http://localhost:5212", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -12,7 +12,7 @@ public static class DysonAuthStartup | ||||
|         IConfiguration configuration | ||||
|     ) | ||||
|     { | ||||
|         services.AddSingleton(sp => | ||||
|         services.AddSingleton<AuthService.AuthServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
| @@ -20,9 +20,12 @@ public static class DysonAuthStartup | ||||
|             var clientKeyPath = config["ClientKey:Path"]; | ||||
|             var clientCertPassword = config["ClientCert:Password"]; | ||||
|  | ||||
|             return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); | ||||
|             return GrpcClientHelper | ||||
|                 .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         }); | ||||
|          | ||||
|  | ||||
|         services.AddAuthentication(options => | ||||
|             { | ||||
|                 options.DefaultAuthenticateScheme = AuthConstants.SchemeName; | ||||
|   | ||||
| @@ -11,10 +11,17 @@ public class RegistryHostedService( | ||||
| ) | ||||
|     : IHostedService | ||||
| { | ||||
|     private CancellationTokenSource? _cts; | ||||
|  | ||||
|     public async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         var serviceName = configuration["Service:Name"]; | ||||
|         var serviceUrl = configuration["Service:Url"]; | ||||
|         var insecure = configuration.GetValue<bool>("Etcd:Insecure"); | ||||
|         var remote = configuration.GetConnectionString("Etcd"); | ||||
|  | ||||
|         if (insecure) | ||||
|             logger.LogWarning("Etcd is configured to use insecure channel."); | ||||
|  | ||||
|         if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName)) | ||||
|         { | ||||
| @@ -22,10 +29,16 @@ public class RegistryHostedService( | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); | ||||
|         logger.LogInformation( | ||||
|             "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).", | ||||
|             serviceName, | ||||
|             serviceUrl, | ||||
|             remote | ||||
|         ); | ||||
|         try | ||||
|         { | ||||
|             await serviceRegistry.RegisterService(serviceName, serviceUrl); | ||||
|             _cts = new CancellationTokenSource(); | ||||
|             await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token); | ||||
|             logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
| @@ -36,6 +49,8 @@ public class RegistryHostedService( | ||||
|  | ||||
|     public async Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _cts?.Cancel(); | ||||
|  | ||||
|         // The lease will expire automatically if the service stops ungracefully. | ||||
|         var serviceName = configuration["Service:Name"]; | ||||
|         if (serviceName is not null) | ||||
|   | ||||
| @@ -2,12 +2,13 @@ using System.Text; | ||||
| using dotnet_etcd.interfaces; | ||||
| using Etcdserverpb; | ||||
| using Google.Protobuf; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| namespace DysonNetwork.Shared.Registry; | ||||
|  | ||||
| public class ServiceRegistry(IEtcdClient etcd) | ||||
| public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger) | ||||
| { | ||||
|     public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60) | ||||
|     public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         var key = $"/services/{serviceName}"; | ||||
|         var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds }); | ||||
| @@ -17,7 +18,18 @@ public class ServiceRegistry(IEtcdClient etcd) | ||||
|             Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), | ||||
|             Lease = leaseResponse.ID | ||||
|         }); | ||||
|         await etcd.LeaseKeepAlive(leaseResponse.ID, CancellationToken.None); | ||||
|  | ||||
|         _ = Task.Run(async () => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logger.LogError($"Lease keep-alive failed: {ex.Message}"); | ||||
|             } | ||||
|         }, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     public async Task UnregisterService(string serviceName) | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "localhost:2379" | ||||
|     "Etcd": "etcd.orb.local:2379" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user