From df93f28273177674e75a2a5a90d85745aa59f14b Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 26 Sep 2024 15:08:08 +0200 Subject: [PATCH] feat(backend): add short IDs to models --- .../Controllers/MembersController.cs | 6 +- Foxnouns.Backend/Database/DatabaseContext.cs | 4 + .../20240926124950_AddSids.Designer.cs | 452 +++++++++++++++++ .../Migrations/20240926124950_AddSids.cs | 98 ++++ ...20240926130208_NonNullableSids.Designer.cs | 458 ++++++++++++++++++ .../20240926130208_NonNullableSids.cs | 57 +++ .../DatabaseContextModelSnapshot.cs | 26 + Foxnouns.Backend/Database/Models/Member.cs | 1 + Foxnouns.Backend/Database/Models/User.cs | 2 + 9 files changed, 1101 insertions(+), 3 deletions(-) create mode 100644 Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.Designer.cs create mode 100644 Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.cs create mode 100644 Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.Designer.cs create mode 100644 Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.cs diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index 1ffc928..46fe51e 100644 --- a/Foxnouns.Backend/Controllers/MembersController.cs +++ b/Foxnouns.Backend/Controllers/MembersController.cs @@ -50,9 +50,9 @@ public class MembersController( ("display_name", ValidationUtils.ValidateDisplayName(req.DisplayName)), ("bio", ValidationUtils.ValidateBio(req.Bio)), ("avatar", ValidationUtils.ValidateAvatar(req.Avatar)), - ..ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences), - ..ValidationUtils.ValidateFieldEntries(req.Names?.ToArray(), CurrentUser!.CustomPreferences, "names"), - ..ValidationUtils.ValidatePronouns(req.Pronouns?.ToArray(), CurrentUser!.CustomPreferences) + .. ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences), + .. ValidationUtils.ValidateFieldEntries(req.Names?.ToArray(), CurrentUser!.CustomPreferences, "names"), + .. ValidationUtils.ValidatePronouns(req.Pronouns?.ToArray(), CurrentUser!.CustomPreferences) ]); var member = new Member diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index 70477f2..b46079b 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -60,15 +60,19 @@ public class DatabaseContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().HasIndex(u => u.Username).IsUnique(); + modelBuilder.Entity().HasIndex(u => u.Sid).IsUnique(); modelBuilder.Entity().HasIndex(m => new { m.UserId, m.Name }).IsUnique(); + modelBuilder.Entity().HasIndex(m => m.Sid).IsUnique(); modelBuilder.Entity().HasIndex(k => k.Key).IsUnique(); + modelBuilder.Entity().Property(u => u.Sid).HasDefaultValueSql("find_free_user_sid()"); modelBuilder.Entity().Property(u => u.Fields).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.Names).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.Pronouns).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.CustomPreferences).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.Settings).HasColumnType("jsonb"); + modelBuilder.Entity().Property(m => m.Sid).HasDefaultValueSql("find_free_member_sid()"); modelBuilder.Entity().Property(m => m.Fields).HasColumnType("jsonb"); modelBuilder.Entity().Property(m => m.Names).HasColumnType("jsonb"); modelBuilder.Entity().Property(m => m.Pronouns).HasColumnType("jsonb"); diff --git a/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.Designer.cs b/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.Designer.cs new file mode 100644 index 0000000..144b244 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.Designer.cs @@ -0,0 +1,452 @@ +// +using System; +using System.Collections.Generic; +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240926124950_AddSids")] + partial class AddSids + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("RedirectUris") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("redirect_uris"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.HasKey("Id") + .HasName("pk_applications"); + + b.ToTable("applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthType") + .HasColumnType("integer") + .HasColumnName("auth_type"); + + b.Property("FediverseApplicationId") + .HasColumnType("bigint") + .HasColumnName("fediverse_application_id"); + + b.Property("RemoteId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("remote_id"); + + b.Property("RemoteUsername") + .HasColumnType("text") + .HasColumnName("remote_username"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_auth_methods"); + + b.HasIndex("FediverseApplicationId") + .HasDatabaseName("ix_auth_methods_fediverse_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_auth_methods_user_id"); + + b.ToTable("auth_methods", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("text") + .HasColumnName("domain"); + + b.Property("InstanceType") + .HasColumnType("integer") + .HasColumnName("instance_type"); + + b.HasKey("Id") + .HasName("pk_fediverse_applications"); + + b.ToTable("fediverse_applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Sid") + .HasColumnType("text") + .HasColumnName("sid"); + + b.Property("Unlisted") + .HasColumnType("boolean") + .HasColumnName("unlisted"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_members"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_members_sid"); + + b.HasIndex("UserId", "Name") + .IsUnique() + .HasDatabaseName("ix_members_user_id_name"); + + b.ToTable("members", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text") + .HasColumnName("key"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_temporary_keys"); + + b.HasIndex("Key") + .IsUnique() + .HasDatabaseName("ix_temporary_keys_key"); + + b.ToTable("temporary_keys", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ApplicationId") + .HasColumnType("bigint") + .HasColumnName("application_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hash"); + + b.Property("ManuallyExpired") + .HasColumnType("boolean") + .HasColumnName("manually_expired"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tokens"); + + b.HasIndex("ApplicationId") + .HasDatabaseName("ix_tokens_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tokens_user_id"); + + b.ToTable("tokens", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property>("CustomPreferences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("custom_preferences"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasColumnName("deleted_by"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_active"); + + b.Property("LastSidReroll") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_sid_reroll"); + + b.Property("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("ListHidden") + .HasColumnType("boolean") + .HasColumnName("list_hidden"); + + b.Property("MemberTitle") + .HasColumnType("text") + .HasColumnName("member_title"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Sid") + .HasColumnType("text") + .HasColumnName("sid"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_users_sid"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") + .WithMany() + .HasForeignKey("FediverseApplicationId") + .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("AuthMethods") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_methods_users_user_id"); + + b.Navigation("FediverseApplication"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("Members") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_members_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_applications_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_users_user_id"); + + b.Navigation("Application"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Navigation("AuthMethods"); + + b.Navigation("Members"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.cs b/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.cs new file mode 100644 index 0000000..e0af60f --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20240926124950_AddSids.cs @@ -0,0 +1,98 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + public partial class AddSids : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "sid", + table: "users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "last_sid_reroll", + table: "users", + type: "timestamp with time zone", + nullable: false, + defaultValueSql: "now() - '1 hour'::interval"); + + migrationBuilder.AddColumn( + name: "sid", + table: "members", + type: "text", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_users_sid", + table: "users", + column: "sid", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_members_sid", + table: "members", + column: "sid", + unique: true); + + migrationBuilder.Sql(@"create function generate_sid(len int) returns text as $$ + select string_agg(substr('abcdefghijklmnopqrstuvwxyz', ceil(random() * 26)::integer, 1), '') from generate_series(1, len) +$$ language sql volatile; +"); + migrationBuilder.Sql(@"create function find_free_user_sid() returns text as $$ +declare new_sid text; +begin + loop + new_sid := generate_sid(5); + if not exists (select 1 from users where sid = new_sid) then return new_sid; end if; + end loop; +end +$$ language plpgsql volatile; +"); + migrationBuilder.Sql(@"create function find_free_member_sid() returns text as $$ +declare new_sid text; +begin + loop + new_sid := generate_sid(6); + if not exists (select 1 from members where sid = new_sid) then return new_sid; end if; + end loop; +end +$$ language plpgsql volatile;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("drop function find_free_member_sid;"); + migrationBuilder.Sql("drop function find_free_user_sid;"); + migrationBuilder.Sql("drop function generate_sid;"); + + migrationBuilder.DropIndex( + name: "ix_users_sid", + table: "users"); + + migrationBuilder.DropIndex( + name: "ix_members_sid", + table: "members"); + + migrationBuilder.DropColumn( + name: "sid", + table: "users"); + + migrationBuilder.DropColumn( + name: "last_sid_reroll", + table: "users"); + + migrationBuilder.DropColumn( + name: "sid", + table: "members"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.Designer.cs b/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.Designer.cs new file mode 100644 index 0000000..8c0c656 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.Designer.cs @@ -0,0 +1,458 @@ +// +using System; +using System.Collections.Generic; +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240926130208_NonNullableSids")] + partial class NonNullableSids + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("RedirectUris") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("redirect_uris"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.HasKey("Id") + .HasName("pk_applications"); + + b.ToTable("applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthType") + .HasColumnType("integer") + .HasColumnName("auth_type"); + + b.Property("FediverseApplicationId") + .HasColumnType("bigint") + .HasColumnName("fediverse_application_id"); + + b.Property("RemoteId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("remote_id"); + + b.Property("RemoteUsername") + .HasColumnType("text") + .HasColumnName("remote_username"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_auth_methods"); + + b.HasIndex("FediverseApplicationId") + .HasDatabaseName("ix_auth_methods_fediverse_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_auth_methods_user_id"); + + b.ToTable("auth_methods", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("text") + .HasColumnName("domain"); + + b.Property("InstanceType") + .HasColumnType("integer") + .HasColumnName("instance_type"); + + b.HasKey("Id") + .HasName("pk_fediverse_applications"); + + b.ToTable("fediverse_applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_member_sid()"); + + b.Property("Unlisted") + .HasColumnType("boolean") + .HasColumnName("unlisted"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_members"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_members_sid"); + + b.HasIndex("UserId", "Name") + .IsUnique() + .HasDatabaseName("ix_members_user_id_name"); + + b.ToTable("members", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text") + .HasColumnName("key"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_temporary_keys"); + + b.HasIndex("Key") + .IsUnique() + .HasDatabaseName("ix_temporary_keys_key"); + + b.ToTable("temporary_keys", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ApplicationId") + .HasColumnType("bigint") + .HasColumnName("application_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hash"); + + b.Property("ManuallyExpired") + .HasColumnType("boolean") + .HasColumnName("manually_expired"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tokens"); + + b.HasIndex("ApplicationId") + .HasDatabaseName("ix_tokens_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tokens_user_id"); + + b.ToTable("tokens", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property>("CustomPreferences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("custom_preferences"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasColumnName("deleted_by"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_active"); + + b.Property("LastSidReroll") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_sid_reroll"); + + b.Property("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("ListHidden") + .HasColumnType("boolean") + .HasColumnName("list_hidden"); + + b.Property("MemberTitle") + .HasColumnType("text") + .HasColumnName("member_title"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_user_sid()"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_users_sid"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") + .WithMany() + .HasForeignKey("FediverseApplicationId") + .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("AuthMethods") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_methods_users_user_id"); + + b.Navigation("FediverseApplication"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("Members") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_members_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_applications_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_users_user_id"); + + b.Navigation("Application"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Navigation("AuthMethods"); + + b.Navigation("Members"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.cs b/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.cs new file mode 100644 index 0000000..4f01d1d --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20240926130208_NonNullableSids.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + public partial class NonNullableSids : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "sid", + table: "users", + type: "text", + nullable: false, + defaultValueSql: "find_free_user_sid()", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "sid", + table: "members", + type: "text", + nullable: false, + defaultValueSql: "find_free_member_sid()", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "sid", + table: "users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldDefaultValueSql: "find_free_user_sid()"); + + migrationBuilder.AlterColumn( + name: "sid", + table: "members", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldDefaultValueSql: "find_free_member_sid()"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs index 36ca955..dd457cf 100644 --- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -175,6 +175,13 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("jsonb") .HasColumnName("pronouns"); + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_member_sid()"); + b.Property("Unlisted") .HasColumnType("boolean") .HasColumnName("unlisted"); @@ -186,6 +193,10 @@ namespace Foxnouns.Backend.Database.Migrations b.HasKey("Id") .HasName("pk_members"); + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_members_sid"); + b.HasIndex("UserId", "Name") .IsUnique() .HasDatabaseName("ix_members_user_id_name"); @@ -314,6 +325,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_active"); + b.Property("LastSidReroll") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_sid_reroll"); + b.Property("Links") .IsRequired() .HasColumnType("text[]") @@ -350,6 +365,13 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("jsonb") .HasColumnName("settings"); + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_user_sid()"); + b.Property("Username") .IsRequired() .HasColumnType("text") @@ -358,6 +380,10 @@ namespace Foxnouns.Backend.Database.Migrations b.HasKey("Id") .HasName("pk_users"); + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_users_sid"); + b.HasIndex("Username") .IsUnique() .HasDatabaseName("ix_users_username"); diff --git a/Foxnouns.Backend/Database/Models/Member.cs b/Foxnouns.Backend/Database/Models/Member.cs index cd0d9cd..d20d374 100644 --- a/Foxnouns.Backend/Database/Models/Member.cs +++ b/Foxnouns.Backend/Database/Models/Member.cs @@ -3,6 +3,7 @@ namespace Foxnouns.Backend.Database.Models; public class Member : BaseModel { public required string Name { get; set; } + public string Sid { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Bio { get; set; } public string? Avatar { get; set; } diff --git a/Foxnouns.Backend/Database/Models/User.cs b/Foxnouns.Backend/Database/Models/User.cs index de48944..112e560 100644 --- a/Foxnouns.Backend/Database/Models/User.cs +++ b/Foxnouns.Backend/Database/Models/User.cs @@ -8,6 +8,7 @@ namespace Foxnouns.Backend.Database.Models; public class User : BaseModel { public required string Username { get; set; } + public string Sid { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Bio { get; set; } public string? MemberTitle { get; set; } @@ -28,6 +29,7 @@ public class User : BaseModel public UserSettings Settings { get; set; } = new(); public required Instant LastActive { get; set; } + public Instant LastSidReroll { get; set; } public bool Deleted { get; set; } public Instant? DeletedAt { get; set; }