diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Sphere/Account/MagicSpellService.cs index 7e4516c..6d6cffe 100644 --- a/DysonNetwork.Sphere/Account/MagicSpellService.cs +++ b/DysonNetwork.Sphere/Account/MagicSpellService.cs @@ -22,7 +22,6 @@ public class MagicSpellService(AppDatabase db, EmailService email, ILogger SubscribePushNotification( @@ -101,16 +101,13 @@ public class NotificationService Subtitle = subtitle, Content = content, Meta = meta, - Account = account, AccountId = account.Id, }; _db.Add(notification); await _db.SaveChangesAsync(); -#pragma warning disable CS4014 - if (!isSilent) DeliveryNotification(notification); -#pragma warning restore CS4014 + if (!isSilent) _ = DeliveryNotification(notification).ConfigureAwait(false); return notification; } diff --git a/DysonNetwork.Sphere/Account/RelationshipService.cs b/DysonNetwork.Sphere/Account/RelationshipService.cs index 4ed4dc6..f5d745b 100644 --- a/DysonNetwork.Sphere/Account/RelationshipService.cs +++ b/DysonNetwork.Sphere/Account/RelationshipService.cs @@ -42,9 +42,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa var relationship = new Relationship { - Account = sender, AccountId = sender.Id, - Related = target, RelatedId = target.Id, Status = status }; @@ -66,9 +64,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa var relationship = new Relationship { - Account = sender, AccountId = sender.Id, - Related = target, RelatedId = target.Id, Status = RelationshipStatus.Pending, ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(7)) @@ -96,9 +92,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa var relationshipBackward = new Relationship { - Account = relationship.Related, AccountId = relationship.RelatedId, - Related = relationship.Account, RelatedId = relationship.AccountId, Status = status }; diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index 6ece080..c55fffb 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -37,7 +37,7 @@ public class AppDatabase( public DbSet AuthChallenges { get; set; } public DbSet Files { get; set; } - + public DbSet Activities { get; set; } public DbSet Publishers { get; set; } @@ -72,7 +72,8 @@ public class AppDatabase( Nodes = { PermissionService.NewPermissionNode("group:default", "global", "posts.create", true), - PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true) + PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true), + PermissionService.NewPermissionNode("group:default", "global", "files.create", true) } }); await context.SaveChangesAsync(cancellationToken); @@ -86,9 +87,9 @@ public class AppDatabase( { base.OnModelCreating(modelBuilder); - modelBuilder.Entity() + modelBuilder.Entity() .HasKey(pg => new { pg.GroupId, pg.Actor }); - modelBuilder.Entity() + modelBuilder.Entity() .HasOne(pg => pg.Group) .WithMany(g => g.Members) .HasForeignKey(pg => pg.GroupId) @@ -123,6 +124,10 @@ public class AppDatabase( .HasForeignKey(pm => pm.AccountId) .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content }) + .HasIndex(p => p.SearchVector) + .HasMethod("GIN"); modelBuilder.Entity() .HasOne(p => p.ThreadedPost) .WithOne() diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Sphere/Auth/AuthController.cs index 3440957..b4460fb 100644 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ b/DysonNetwork.Sphere/Auth/AuthController.cs @@ -50,7 +50,6 @@ public class AuthController( var challenge = new Challenge { - Account = account, ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), StepTotal = 1, Platform = request.Platform, @@ -59,6 +58,7 @@ public class AuthController( IpAddress = ipAddress, UserAgent = userAgent, DeviceId = request.DeviceId, + AccountId = account.Id }.Normalize(); await db.AuthChallenges.AddAsync(challenge); @@ -123,13 +123,19 @@ public class AuthController( { challenge.StepRemain--; challenge.BlacklistFactors.Add(factor.Id); + db.Update(challenge); + } + else + { + throw new ArgumentException("Invalid password."); } } catch { challenge.FailedAttempts++; + db.Update(challenge); await db.SaveChangesAsync(); - return BadRequest(); + return BadRequest("Invalid password."); } await db.SaveChangesAsync(); diff --git a/DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.Designer.cs b/DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.Designer.cs deleted file mode 100644 index 8679b25..0000000 --- a/DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.Designer.cs +++ /dev/null @@ -1,1666 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -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.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250430163514_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("IsSuperuser") - .HasColumnType("boolean") - .HasColumnName("is_superuser"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("language"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Nick") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("nick"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_accounts"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_accounts_name"); - - b.ToTable("accounts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Secret") - .HasColumnType("text") - .HasColumnName("secret"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_auth_factors"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_account_auth_factors_account_id"); - - b.ToTable("account_auth_factors", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Content") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_account_contacts"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_account_contacts_account_id"); - - b.ToTable("account_contacts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Spell") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("spell"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_magic_spells"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_magic_spells_account_id"); - - b.HasIndex("Spell") - .IsUnique() - .HasDatabaseName("ix_magic_spells_spell"); - - b.ToTable("magic_spells", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeviceId") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(128)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("FirstName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("first_name"); - - b.Property("LastName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("last_name"); - - b.Property("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasColumnType("character varying(128)") - .HasColumnName("picture_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("RelatedId") - .HasColumnType("bigint") - .HasColumnName("related_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("AccountId", "RelatedId") - .HasName("pk_account_relationships"); - - b.HasIndex("RelatedId") - .HasDatabaseName("ix_account_relationships_related_id"); - - b.ToTable("account_relationships", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property>("Audiences") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("audiences"); - - b.Property>("BlacklistFactors") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("blacklist_factors"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeviceId") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FailedAttempts") - .HasColumnType("integer") - .HasColumnName("failed_attempts"); - - b.Property("IpAddress") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("ip_address"); - - b.Property("Nonce") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nonce"); - - b.Property("Platform") - .HasColumnType("integer") - .HasColumnName("platform"); - - b.Property>("Scopes") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("scopes"); - - b.Property("StepRemain") - .HasColumnType("integer") - .HasColumnName("step_remain"); - - b.Property("StepTotal") - .HasColumnType("integer") - .HasColumnName("step_total"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UserAgent") - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_sessions_account_id"); - - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - - b.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("key"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_permission_groups"); - - b.ToTable("permission_groups", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.Property("GroupId") - .HasColumnType("uuid") - .HasColumnName("group_id"); - - b.Property("Actor") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("actor"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("GroupId", "Actor") - .HasName("pk_permission_group_members"); - - b.ToTable("permission_group_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Actor") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("actor"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("GroupId") - .HasColumnType("uuid") - .HasColumnName("group_id"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("key"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Value") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("value"); - - b.HasKey("Id") - .HasName("pk_permission_nodes"); - - b.HasIndex("GroupId") - .HasDatabaseName("ix_permission_nodes_group_id"); - - b.HasIndex("Key", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("bigint") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("bigint") - .HasColumnName("replied_post_id"); - - b.Property("ThreadedPostId") - .HasColumnType("bigint") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("bigint") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(128)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Nick") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("nick"); - - b.Property("PictureId") - .HasColumnType("character varying(128)") - .HasColumnName("picture_id"); - - b.Property("PublisherType") - .HasColumnType("integer") - .HasColumnName("publisher_type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("bigint") - .HasColumnName("post_id"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("bigint") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("bigint") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("bigint") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Related") - .WithMany("IncomingRelationships") - .HasForeignKey("RelatedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_related_id"); - - b.Navigation("Account"); - - b.Navigation("Related"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.cs b/DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.cs deleted file mode 100644 index 7044d8f..0000000 --- a/DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class UnableToNamingThing1 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "has_compression", - table: "files", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateTable( - name: "activities", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - visibility = table.Column(type: "integer", nullable: false), - account_id = table.Column(type: "bigint", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_activities", x => x.id); - table.ForeignKey( - name: "fk_activities_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_activities_account_id", - table: "activities", - column: "account_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "activities"); - - migrationBuilder.DropColumn( - name: "has_compression", - table: "files"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.Designer.cs b/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs similarity index 98% rename from DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.Designer.cs rename to DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs index ba2d3af..7ca88d0 100644 --- a/DysonNetwork.Sphere/Migrations/20250501072931_UnableToNamingThing1.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs @@ -9,14 +9,15 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using NodaTime; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; #nullable disable namespace DysonNetwork.Sphere.Migrations { [DbContext(typeof(AppDatabase))] - [Migration("20250501072931_UnableToNamingThing1")] - partial class UnableToNamingThing1 + [Migration("20250501080049_InitialMigration")] + partial class InitialMigration { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -790,8 +791,8 @@ namespace DysonNetwork.Sphere.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Content") - .HasColumnType("text") + b.Property("Content") + .HasColumnType("jsonb") .HasColumnName("content"); b.Property("CreatedAt") @@ -840,6 +841,14 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("bigint") .HasColumnName("replied_post_id"); + b.Property("SearchVector") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("tsvector") + .HasColumnName("search_vector") + .HasAnnotation("Npgsql:TsVectorConfig", "simple") + .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); + b.Property("ThreadedPostId") .HasColumnType("bigint") .HasColumnName("threaded_post_id"); @@ -885,6 +894,11 @@ namespace DysonNetwork.Sphere.Migrations b.HasIndex("RepliedPostId") .HasDatabaseName("ix_posts_replied_post_id"); + b.HasIndex("SearchVector") + .HasDatabaseName("ix_posts_search_vector"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); + b.HasIndex("ThreadedPostId") .IsUnique() .HasDatabaseName("ix_posts_threaded_post_id"); diff --git a/DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.cs b/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs similarity index 95% rename from DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.cs rename to DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs index 5c4d309..90346c4 100644 --- a/DysonNetwork.Sphere/Migrations/20250430163514_InitialMigration.cs +++ b/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs @@ -4,6 +4,7 @@ using System.Text.Json; using Microsoft.EntityFrameworkCore.Migrations; using NodaTime; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; #nullable disable @@ -162,6 +163,30 @@ namespace DysonNetwork.Sphere.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "activities", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + visibility = table.Column(type: "integer", nullable: false), + account_id = table.Column(type: "bigint", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_activities", x => x.id); + table.ForeignKey( + name: "fk_activities_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "auth_challenges", columns: table => new @@ -395,6 +420,7 @@ namespace DysonNetwork.Sphere.Migrations uploaded_at = table.Column(type: "timestamp with time zone", nullable: true), expired_at = table.Column(type: "timestamp with time zone", nullable: true), uploaded_to = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + has_compression = table.Column(type: "boolean", nullable: false), used_count = table.Column(type: "integer", nullable: false), account_id = table.Column(type: "bigint", nullable: false), post_id = table.Column(type: "bigint", nullable: true), @@ -487,7 +513,7 @@ namespace DysonNetwork.Sphere.Migrations edited_at = table.Column(type: "timestamp with time zone", nullable: true), published_at = table.Column(type: "timestamp with time zone", nullable: true), visibility = table.Column(type: "integer", nullable: false), - content = table.Column(type: "text", nullable: true), + content = table.Column(type: "jsonb", nullable: true), type = table.Column(type: "integer", nullable: false), meta = table.Column>(type: "jsonb", nullable: true), views_unique = table.Column(type: "integer", nullable: false), @@ -497,6 +523,9 @@ namespace DysonNetwork.Sphere.Migrations threaded_post_id = table.Column(type: "bigint", nullable: true), replied_post_id = table.Column(type: "bigint", nullable: true), forwarded_post_id = table.Column(type: "bigint", nullable: true), + search_vector = table.Column(type: "tsvector", nullable: false) + .Annotation("Npgsql:TsVectorConfig", "simple") + .Annotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" }), publisher_id = table.Column(type: "bigint", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false), updated_at = table.Column(type: "timestamp with time zone", nullable: false), @@ -693,6 +722,11 @@ namespace DysonNetwork.Sphere.Migrations column: "name", unique: true); + migrationBuilder.CreateIndex( + name: "ix_activities_account_id", + table: "activities", + column: "account_id"); + migrationBuilder.CreateIndex( name: "ix_auth_challenges_account_id", table: "auth_challenges", @@ -806,6 +840,12 @@ namespace DysonNetwork.Sphere.Migrations table: "posts", column: "replied_post_id"); + migrationBuilder.CreateIndex( + name: "ix_posts_search_vector", + table: "posts", + column: "search_vector") + .Annotation("Npgsql:IndexMethod", "GIN"); + migrationBuilder.CreateIndex( name: "ix_posts_threaded_post_id", table: "posts", @@ -891,6 +931,9 @@ namespace DysonNetwork.Sphere.Migrations migrationBuilder.DropTable( name: "account_relationships"); + migrationBuilder.DropTable( + name: "activities"); + migrationBuilder.DropTable( name: "auth_sessions"); diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs index 85c8a51..a3938a3 100644 --- a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using NodaTime; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; #nullable disable @@ -787,8 +788,8 @@ namespace DysonNetwork.Sphere.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Content") - .HasColumnType("text") + b.Property("Content") + .HasColumnType("jsonb") .HasColumnName("content"); b.Property("CreatedAt") @@ -837,6 +838,14 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("bigint") .HasColumnName("replied_post_id"); + b.Property("SearchVector") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("tsvector") + .HasColumnName("search_vector") + .HasAnnotation("Npgsql:TsVectorConfig", "simple") + .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); + b.Property("ThreadedPostId") .HasColumnType("bigint") .HasColumnName("threaded_post_id"); @@ -882,6 +891,11 @@ namespace DysonNetwork.Sphere.Migrations b.HasIndex("RepliedPostId") .HasDatabaseName("ix_posts_replied_post_id"); + b.HasIndex("SearchVector") + .HasDatabaseName("ix_posts_search_vector"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); + b.HasIndex("ThreadedPostId") .IsUnique() .HasDatabaseName("ix_posts_threaded_post_id"); diff --git a/DysonNetwork.Sphere/Post/Post.cs b/DysonNetwork.Sphere/Post/Post.cs index 51ec825..8ab8b3c 100644 --- a/DysonNetwork.Sphere/Post/Post.cs +++ b/DysonNetwork.Sphere/Post/Post.cs @@ -1,8 +1,10 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; using System.Text.Json.Serialization; using DysonNetwork.Sphere.Storage; using NodaTime; +using NpgsqlTypes; namespace DysonNetwork.Sphere.Post; @@ -31,8 +33,7 @@ public class Post : ModelBase public Instant? PublishedAt { get; set; } public PostVisibility Visibility { get; set; } = PostVisibility.Public; - // ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength - public string? Content { get; set; } + [Column(TypeName = "jsonb")] public JsonDocument? Content { get; set; } public PostType Type { get; set; } [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } @@ -49,6 +50,8 @@ public class Post : ModelBase public long? ForwardedPostId { get; set; } public Post? ForwardedPost { get; set; } public ICollection Attachments { get; set; } = new List(); + + public NpgsqlTsVector SearchVector { get; set; } public Publisher Publisher { get; set; } = null!; public ICollection Reactions { get; set; } = new List(); @@ -56,7 +59,7 @@ public class Post : ModelBase public ICollection Categories { get; set; } = new List(); public ICollection Collections { get; set; } = new List(); - public bool Empty => Content?.Trim() is { Length: 0 } && Attachments.Count == 0 && ForwardedPostId == null; + public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null; } public class PostTag : ModelBase diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 2b33737..28e5ff5 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Casbin; using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Permission; @@ -17,7 +18,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account.Account; - var userFriends = await rels.ListAccountFriends(currentUser!); + var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var totalCount = await db.Posts .FilterWithVisibility(currentUser, userFriends, isListing: true) @@ -48,7 +49,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account.Account; - var userFriends = await rels.ListAccountFriends(currentUser!); + var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var post = await db.Posts .Where(e => e.Id == id) @@ -74,7 +75,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account.Account; - var userFriends = await rels.ListAccountFriends(currentUser!); + var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var post = await db.Posts .Where(e => e.Id == id) @@ -110,7 +111,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService { [MaxLength(1024)] public string? Title { get; set; } [MaxLength(4096)] public string? Description { get; set; } - public string? Content { get; set; } + public JsonDocument? Content { get; set; } public PostVisibility? Visibility { get; set; } public PostType? Type { get; set; } [MaxLength(16)] public List? Tags { get; set; } diff --git a/DysonNetwork.Sphere/Post/PublisherController.cs b/DysonNetwork.Sphere/Post/PublisherController.cs index e5e8a45..6077767 100644 --- a/DysonNetwork.Sphere/Post/PublisherController.cs +++ b/DysonNetwork.Sphere/Post/PublisherController.cs @@ -102,9 +102,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic var newMember = new PublisherMember { - Account = relatedUser, AccountId = relatedUser.Id, - Publisher = publisher, PublisherId = publisher.Id, Role = request.Role, }; diff --git a/DysonNetwork.Sphere/Post/PublisherService.cs b/DysonNetwork.Sphere/Post/PublisherService.cs index c70599b..5ec48b1 100644 --- a/DysonNetwork.Sphere/Post/PublisherService.cs +++ b/DysonNetwork.Sphere/Post/PublisherService.cs @@ -22,13 +22,12 @@ public class PublisherService(AppDatabase db, FileService fs) Bio = bio ?? account.Profile.Bio, Picture = picture ?? account.Profile.Picture, Background = background ?? account.Profile.Background, - Account = account, + AccountId = account.Id, Members = new List { new() { AccountId = account.Id, - Account = account, Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } diff --git a/DysonNetwork.Sphere/Storage/CloudFile.cs b/DysonNetwork.Sphere/Storage/CloudFile.cs index 763696d..a01c8ba 100644 --- a/DysonNetwork.Sphere/Storage/CloudFile.cs +++ b/DysonNetwork.Sphere/Storage/CloudFile.cs @@ -5,7 +5,7 @@ using NodaTime; namespace DysonNetwork.Sphere.Storage; -public abstract class RemoteStorageConfig +public class RemoteStorageConfig { public string Id { get; set; } = string.Empty; public string Label { get; set; } = string.Empty; @@ -41,6 +41,7 @@ public class CloudFile : ModelBase public int UsedCount { get; set; } = 0; [JsonIgnore] public Account.Account Account { get; set; } = null!; + public long AccountId { get; set; } } public enum CloudFileSensitiveMark diff --git a/DysonNetwork.Sphere/Storage/FileService.cs b/DysonNetwork.Sphere/Storage/FileService.cs index c839df3..bb155c0 100644 --- a/DysonNetwork.Sphere/Storage/FileService.cs +++ b/DysonNetwork.Sphere/Storage/FileService.cs @@ -14,7 +14,7 @@ using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; namespace DysonNetwork.Sphere.Storage; -public class FileService(AppDatabase db, IConfiguration configuration) +public class FileService(AppDatabase db, IConfiguration configuration, ILogger logger, IServiceScopeFactory scopeFactory) { private static readonly string TempFilePrefix = "dyn-cloudfile"; @@ -46,7 +46,7 @@ public class FileService(AppDatabase db, IConfiguration configuration) MimeType = contentType, Size = fileSize, Hash = hash, - Account = account, + AccountId = account.Id }; switch (contentType.Split('/')[0]) @@ -83,24 +83,6 @@ public class FileService(AppDatabase db, IConfiguration configuration) ["ratio"] = aspectRatio, ["exif"] = exif }; - - var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); - var ogTask = imageSharp.SaveAsWebpAsync(imagePath); - modifiedResult.Add((imagePath, string.Empty)); - - var compressedClone = imageSharp.Clone(); - compressedClone.Mutate(i => i.Resize(new ResizeOptions - { - Mode = ResizeMode.Max, - Size = new Size(1024, 1024), - }) - ); - var imageCompressedPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed"); - var compressedTask = compressedClone.SaveAsWebpAsync(imagePath); - modifiedResult.Add((imageCompressedPath, ".compressed")); - file.HasCompression = true; - - await Task.WhenAll(ogTask, compressedTask); } break; @@ -131,13 +113,77 @@ public class FileService(AppDatabase db, IConfiguration configuration) db.Files.Add(file); await db.SaveChangesAsync(); -#pragma warning disable CS4014 - if (modifiedResult.Count > 0) - foreach (var result in modifiedResult) - UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true); - else - UploadFileToRemoteAsync(file, stream, null); -#pragma warning restore CS4014 + _ = Task.Run(async () => + { + using var scope = scopeFactory.CreateScope(); + var nfs = scope.ServiceProvider.GetRequiredService(); + + try + { + logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId); + + if (contentType.Split('/')[0] == "image") + { + file.MimeType = "image/webp"; + + List tasks = []; + + var ogFilePath = Path.Join(configuration.GetValue("Tus:StorePath"), file.Id); + using var imageSharp = await Image.LoadAsync(ogFilePath); + var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); + tasks.Add(imageSharp.SaveAsWebpAsync(imagePath)); + modifiedResult.Add((imagePath, string.Empty)); + + if (imageSharp.Size.Width * imageSharp.Size.Height >= 1024 * 1024) + { + var compressedClone = imageSharp.Clone(); + compressedClone.Mutate(i => i.Resize(new ResizeOptions + { + Mode = ResizeMode.Max, + Size = new Size(1024, 1024), + }) + ); + var imageCompressedPath = + Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed"); + tasks.Add(compressedClone.SaveAsWebpAsync(imageCompressedPath)); + modifiedResult.Add((imageCompressedPath, ".compressed")); + file.HasCompression = true; + } + + await Task.WhenAll(tasks); + } + + logger.LogInformation("Optimized file {fileId}, now uploading...", fileId); + + if (modifiedResult.Count > 0) + { + List> tasks = []; + tasks.AddRange(modifiedResult.Select(result => + nfs.UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true))); + + await Task.WhenAll(tasks); + file = await tasks.First(); + } + else + { + file = await nfs.UploadFileToRemoteAsync(file, stream, null); + } + + logger.LogInformation("Uploaded file {fileId} done!", fileId); + + var scopedDb = scope.ServiceProvider.GetRequiredService(); + await scopedDb.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter + .SetProperty(f => f.UploadedAt, file.UploadedAt) + .SetProperty(f => f.UploadedTo, file.UploadedTo) + .SetProperty(f => f.MimeType, file.MimeType) + .SetProperty(f => f.HasCompression, file.HasCompression) + ); + } + catch (Exception err) + { + logger.LogError(err, "Failed to process {fileId}", fileId); + } + }).ConfigureAwait(false); return file; } @@ -179,7 +225,7 @@ public class FileService(AppDatabase db, IConfiguration configuration) string? suffix = null, bool selfDestruct = false) { var fileStream = File.OpenRead(filePath); - var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote); + var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote, suffix); if (selfDestruct) File.Delete(filePath); return result; } @@ -210,10 +256,6 @@ public class FileService(AppDatabase db, IConfiguration configuration) ); file.UploadedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); - await db.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter - .SetProperty(f => f.UploadedAt, file.UploadedAt) - .SetProperty(f => f.UploadedTo, file.UploadedTo) - ); return file; }